Collection of useful methods to run your bot on Vercel
How to ...
Install
npm i vercel-grammy
Import
import {} from "vercel-grammy"
Use
import {Bot} from "grammy"
import {getURL} from "vercel-grammy"
const url = getURL({path: "api/index"})
const bot = new Bot()
await bot.api.setWebhook(url)
Examples
Get current hostname
getHost()
export default ({headers}) => {
getHost({headers})
}
Get URL for current hostname
getURL({path: "api/index"})
export default ({headers}) => {
getURL({headers, path: "api/index"})
}
Set webhook for current hostname
bot.api.setWebhook(getURL({path: "api/index"}))
export default setWebhookCallback(bot, {path: "api/index"})
Use streaming response in webhook handler
Note that this will work only at Vercel Edge Functions
export default webhookStream(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
const allowedEnvs = [
"production",
"preview"
]
if (!allowedEnvs.includes(VERCEL_ENV)) process.exit()
const url = getURL({path: "api/index"})
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:
- Telegram sends an update request
- Proxy service passes the original request to your function
- Answer within
60
seconds will be returned to Telegram
- Otherwise, proxy responds with a
200
status to prevent a recurrence
- 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
})
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