Skip to content

PonomareVlad/Vercel-grammY

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

42 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

grammY helpers for Vercel

Collection of useful methods to run your bot on Vercel

How to ...

Install

npm i vercel-grammy

Import

import {/* methods */} from "vercel-grammy"

Use

import {Bot} from "grammy"
import {getURL} from "vercel-grammy"

const url = getURL({path: "api/index"})

const bot = new Bot(/* token */)

await bot.api.setWebhook(url)

Examples

Get current hostname

// Anywhere in your code
getHost() // *.vercel.app (from `process.env.VERCEL_URL`)

// At your function handler 
export default ({headers}) => {
    getHost({headers}) // domain.com (from `x-forwarded-host` header)
}

Get URL for current hostname

// Anywhere in your code
getURL({path: "api/index"}) // https://*.vercel.app/api/index

// At your function handler 
export default ({headers}) => {
    getURL({headers, path: "api/index"}) // https://domain.com/api/index
}

Set webhook for current hostname

// Anywhere in your code
bot.api.setWebhook(getURL({path: "api/index"}))

// As function handler
export default setWebhookCallback(bot, {path: "api/index"})

Use streaming response in webhook handler

Note that this will work only at Vercel Edge Functions

// As function handler
export default webhookStream(bot) // Instead of webhookCallback(bot)

export const config = {
    runtime: "edge"
}

Guides

Sets webhook URL automatically

When you deploy a project to Vercel, one of these environments is installed for it:

  • production β€” default for main or master branches
  • preview β€” for all other branches in your repository
  • development β€” when using the vercel dev command

In the early stages of bot development, it is enough to install a webhook on the main (production) domain, such as project.vercel.app

However, if you want to test new changes without stopping the bot, then you can simply use a separate (test) bot (for example @awesome_beta_bot) and set the webhook to the URL of the branch β€” project-git-branch-username.vercel.app

But what if you have several separate branches with different changes and want to test them without creating a separate bot for each or manually managing webhooks ?

Q: You didn't make a separate plugin for this, right ?
A: 😏
Q: Didn't do it, right ?

Thanks to the Vercel build step, we can run some code before a new version of the bot is published and no one will stop us from using it

Just add this code to a new JavaScript file:

const {
    VERCEL_ENV,
} = process.env

// List of allowed environments
const allowedEnvs = [
    "production",
    "preview"
]

// Exit in case of unsuitable environments
if (!allowedEnvs.includes(VERCEL_ENV)) process.exit()

// Webhook URL generation
const url = getURL({path: "api/index"})

// Installing a webhook
await bot.api.setWebhook(url)

And specify the path to it in the vercel.json file:

{
  "buildCommand": "node path/to/new/file.js"
}

By the way, you can manage tokens for each environment (or even branch) in the project settings

Avoiding invocation timeouts

By default, Vercel limits the invocation time for your code:

So, without streaming (and paying) you can get up to 25 seconds with default grammY webhookCallback adapter at Edge Functions

On the other hand, we also have a time limit for responding to incoming requests from Telegram β€” 60 seconds, after which, the request will be considered unsuccessful and will be retried, which you probably don't want

To get around these limitations you can proxy the request before calling the function by following scheme:

  1. Telegram sends an update request
  2. Proxy service passes the original request to your function
  3. Answer within 60 seconds will be returned to Telegram
  4. Otherwise, proxy responds with a 200 status to prevent a recurrence
  5. Your function may continue to work for the next 940 seconds

Q: What proxy server is suitable for this ?
A: I don't know, but I made it πŸ™‚

Proxy

Source: ProlongRequest

Endpoint: https://prolong-request.fly.dev

Reference:

  • /domain.com
  • /http://domain.com
  • /https://domain.com
  • /https://domain.com/path/to/file.txt
  • /https://domain.com/route?with=parameters

Also supports any HTTP methods and transmits raw headers and body

How to use this for bot

Just prepend proxy endpoint to webhook URL:

https://prolong-request.fly.dev/https://*.vercel.app/api/index

Or do it automatically:

const proxy = "https://prolong-request.fly.dev"

const url = getURL({path: "api/index"})

bot.api.setWebhook(`${proxy}/${url}`)

And use streaming response in webhook handler:

export default webhookStream(bot, {
    timeoutMilliseconds: 999 // where you can also control timeout
})

export const config = {
    runtime: "edge"
}

Limitations

  • Processing updates will overlap
  • States and sessions will be inconsistent
  • Request may break and will not be retried

Benefits

  • You can do anything during this time
  • You can wait anything within this time
  • You can solve anything using this time

API

getHost([options])

  • options (object, optional) β€” Options for hostname
    • headers (Headers, optional) β€” Headers from incoming request
    • header (string, optional) β€” Header name which contains the hostname
    • fallback (string, optional) β€” Fallback hostname (process.env.VERCEL_URL by default)
  • returns string β€” Target hostname

This method generates a hostname from the options passed to it

getURL([options])

  • options (object, optional) β€” Options for URL
    • path (string, optional) β€” Path to a function that receives updates
    • host (string, optional) β€” Hostname without protocol (replaces getHost options)
    • ...options (object, optional) β€” Options for getHost
  • returns string β€” Target URL

This method generates a URL from the options passed to it

setWebhookCallback(bot[, options])

  • bot (Bot, required) β€” grammY bot instance
  • options (object, optional) β€” Options for webhooks
    • url (string, optional) β€” URL for webhooks (replaces getURL options)
    • onError ("throw" | "return", optional) β€” Strategy for handling errors
    • allowedEnvs (array, optional) β€” List of environments where this method allowed
    • ...options (object, optional) β€” Options for bot.api.setWebhook
    • ...options (object, optional) β€” Options for getURL
  • returns () => Promise<Response> β€” Target callback method

Callback factory for grammY bot.api.setWebhook method

webhookStream(bot[, options])

  • bot (Bot, required) β€” grammY bot instance
  • options (object, optional) β€” Options for stream
    • chunk (string, optional) β€” Content for chunks
    • intervalMilliseconds (number, optional) β€” Interval for writing chunks to stream
    • ...options (object, optional) β€” Options for webhookCallback
  • returns () => Response β€” Target callback method

Callback factory for streaming webhook response

jsonResponse(value[, options])

  • value (any, required) β€” Serializable value
  • options (object, optional) β€” Options for JSON response
    • replacer ((string | number)[] | null | undefined, optional)
    • space (string | number | undefined, optional)
    • ...options (ResponseInit, optional)
  • returns Response β€” Target JSON Response

This method generates Response objects for JSON

Templates using this package

Made with πŸ’œ by Vladislav Ponomarev