easy-ping 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -4
- package/dist/{adapter-D3lTy-4E.d.cts → adapter-BJ8Iejvs.d.cts} +1 -1
- package/dist/{adapter-D3lTy-4E.d.ts → adapter-BJ8Iejvs.d.ts} +1 -1
- package/dist/adapters/drizzle.d.cts +1 -1
- package/dist/adapters/drizzle.d.ts +1 -1
- package/dist/adapters/mongodb.d.cts +2 -2
- package/dist/adapters/mongodb.d.ts +2 -2
- package/dist/adapters/mysql.d.cts +2 -2
- package/dist/adapters/mysql.d.ts +2 -2
- package/dist/adapters/postgres.d.cts +2 -2
- package/dist/adapters/postgres.d.ts +2 -2
- package/dist/adapters/sqlite.d.cts +2 -2
- package/dist/adapters/sqlite.d.ts +2 -2
- package/dist/chunk-R3BE67L7.js +13 -0
- package/dist/chunk-R3BE67L7.js.map +1 -0
- package/dist/chunk-SSHVZHSJ.js +100 -0
- package/dist/chunk-SSHVZHSJ.js.map +1 -0
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -11
- package/dist/index.js.map +1 -1
- package/dist/{plugin-Cg9usuUi.d.ts → plugin-CSLKKgXT.d.ts} +1 -1
- package/dist/{plugin-CHd2ZiGC.d.cts → plugin-Wwp1nvo0.d.cts} +1 -1
- package/dist/plugins/digests.d.cts +2 -2
- package/dist/plugins/digests.d.ts +2 -2
- package/dist/plugins/preferences-client.d.cts +2 -2
- package/dist/plugins/preferences-client.d.ts +2 -2
- package/dist/plugins/preferences-react.d.cts +1 -1
- package/dist/plugins/preferences-react.d.ts +1 -1
- package/dist/plugins/preferences.cjs +1 -1
- package/dist/plugins/preferences.cjs.map +1 -1
- package/dist/plugins/preferences.d.cts +2 -2
- package/dist/plugins/preferences.d.ts +2 -2
- package/dist/plugins/preferences.js +1 -1
- package/dist/plugins/preferences.js.map +1 -1
- package/dist/plugins/push.d.cts +2 -2
- package/dist/plugins/push.d.ts +2 -2
- package/dist/plugins/telegram.cjs +379 -0
- package/dist/plugins/telegram.cjs.map +1 -0
- package/dist/plugins/telegram.d.cts +153 -0
- package/dist/plugins/telegram.d.ts +153 -0
- package/dist/plugins/telegram.js +313 -0
- package/dist/plugins/telegram.js.map +1 -0
- package/dist/providers/telegram.cjs +103 -0
- package/dist/providers/telegram.cjs.map +1 -0
- package/dist/providers/telegram.d.cts +83 -0
- package/dist/providers/telegram.d.ts +83 -0
- package/dist/providers/telegram.js +3 -0
- package/dist/providers/telegram.js.map +1 -0
- package/dist/providers/web-push.d.cts +2 -2
- package/dist/providers/web-push.d.ts +2 -2
- package/dist/schema.d.cts +2 -2
- package/dist/schema.d.ts +2 -2
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/package.json +13 -2
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# easy-ping
|
|
4
4
|
|
|
5
5
|
**Own your notifications.**
|
|
6
|
-
In-app inbox, transactional email
|
|
6
|
+
In-app inbox, transactional email, web push and Telegram — running inside your app, stored in your
|
|
7
7
|
database, with no per-notification bill.
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/easy-ping)
|
|
@@ -47,7 +47,7 @@ hand-rolling it badly.
|
|
|
47
47
|
| **Cost per notification** | none | none, you pay for servers | metered per send |
|
|
48
48
|
| **License** | MIT, all of it | MIT core, commercial modules | proprietary |
|
|
49
49
|
| **Visual workflow editor** | **no**, a notification is code | yes | yes |
|
|
50
|
-
| **Channels out of the box** | **in-app, email, web push** | dozens | dozens |
|
|
50
|
+
| **Channels out of the box** | **in-app, email, web push, Telegram** | dozens | dozens |
|
|
51
51
|
|
|
52
52
|
The last two rows are the trade. If you need a workflow editor a non-developer can edit, use one
|
|
53
53
|
of the others — they are good tools solving a bigger problem.
|
|
@@ -56,7 +56,7 @@ of the others — they are good tools solving a bigger problem.
|
|
|
56
56
|
Every "no queue required" decision below follows from that.
|
|
57
57
|
|
|
58
58
|
> **Status: published, pre-1.0.** The core pipeline, all three adapters, the React client and the
|
|
59
|
-
> preferences, digests and
|
|
59
|
+
> preferences, digests, push and telegram plugins are covered by tests against real databases. Web push is
|
|
60
60
|
> verified end to end against Mozilla's production push service and cross-checked against
|
|
61
61
|
> `http_ece`. The bell updates over a server-sent event stream with polling as the fallback;
|
|
62
62
|
> batching is not built, and the Resend provider has only been exercised against a stub. Minor
|
|
@@ -379,10 +379,25 @@ single replica.
|
|
|
379
379
|
| `inApp` | ✅ built in, on by default, needs no provider |
|
|
380
380
|
| `email` | ✅ Resend provider; the interface is open for others |
|
|
381
381
|
| `push` | ✅ push plugin + `webPush()` — VAPID and aes128gcm on Web Crypto, so it runs on edge too |
|
|
382
|
+
| `telegram` | ✅ telegram plugin + `telegramBot()` — one-tap linking through the bot, webhook or long-poll |
|
|
382
383
|
| `sms` | ⬜ not implemented |
|
|
383
384
|
| `slack` | ⬜ not implemented |
|
|
384
385
|
|
|
385
|
-
A channel is usable when core carries it (`inApp`, `email`) or a plugin declares it and can `deliver` it. That is how push
|
|
386
|
+
A channel is usable when core carries it (`inApp`, `email`) or a plugin declares it and can `deliver` it. That is how push and telegram work, and how sms and slack will.
|
|
387
|
+
|
|
388
|
+
```ts
|
|
389
|
+
import { telegram } from "easy-ping/plugins/telegram";
|
|
390
|
+
import { telegramBot } from "easy-ping/providers/telegram";
|
|
391
|
+
|
|
392
|
+
telegram({
|
|
393
|
+
provider: telegramBot({ token: process.env.TELEGRAM_BOT_TOKEN! }),
|
|
394
|
+
botUsername: "your_bot",
|
|
395
|
+
webhookSecret: process.env.TELEGRAM_WEBHOOK_SECRET, // or plugin.poll() in development
|
|
396
|
+
render: ({ payload }) => ({ text: `<b>${escapeHtml(payload.authorName)}</b> replied` }),
|
|
397
|
+
});
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
`POST /telegram/link` returns a `t.me/<bot>?start=<code>` link; the user taps it, the bot stores the chat, and every `send()` with `telegram` in its channels reaches it. Blocked chats prune themselves.
|
|
386
401
|
|
|
387
402
|
Declaring a channel nothing can carry **warns at startup** and reports `skipped: "channel-unavailable"` — deliberately distinct from `"no-channels"`, so a missing provider never looks like a user opt-out.
|
|
388
403
|
|
|
@@ -475,6 +490,7 @@ Defaults to the last 24 hours, capped at 1000 rows. Wire it to an admin page or
|
|
|
475
490
|
| ✅ digests plugin, timezone-aware | |
|
|
476
491
|
| ✅ push plugin + web-push provider | VAPID + RFC 8291, no node:crypto |
|
|
477
492
|
| ✅ push verified against a live push service | Mozilla autopush, plus a cross-check against `http_ece` |
|
|
493
|
+
| ✅ telegram plugin + bot provider | one-tap linking, webhook or long-poll, blocked chats pruned |
|
|
478
494
|
| ✅ scoped plugin storage, so plugins own their tables | |
|
|
479
495
|
| ✅ additive schema migrations for the raw-SQL path | `planPostgresMigration()`; MySQL and SQLite are bootstrap-only for now |
|
|
480
496
|
| ✅ failed deliveries reachable from the instance | `notify.getFailedDeliveries()` |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type Channel = "inApp" | "email" | "push" | "sms" | "slack";
|
|
1
|
+
type Channel = "inApp" | "email" | "push" | "telegram" | "sms" | "slack";
|
|
2
2
|
type DeliveryStatus = "pending" | "claimed" | "sent" | "failed" | "skipped";
|
|
3
3
|
type DeliveryMode = "inline" | "deferred" | "worker" | "cron";
|
|
4
4
|
type Frequency = "instant" | "daily" | "weekly" | "off";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type Channel = "inApp" | "email" | "push" | "sms" | "slack";
|
|
1
|
+
type Channel = "inApp" | "email" | "push" | "telegram" | "sms" | "slack";
|
|
2
2
|
type DeliveryStatus = "pending" | "claimed" | "sent" | "failed" | "skipped";
|
|
3
3
|
type DeliveryMode = "inline" | "deferred" | "worker" | "cron";
|
|
4
4
|
type Frequency = "instant" | "daily" | "weekly" | "off";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as DatabaseAdapter, C as Channel, a as DeliveryStatus, F as Frequency } from '../adapter-
|
|
1
|
+
import { D as DatabaseAdapter, C as Channel, a as DeliveryStatus, F as Frequency } from '../adapter-BJ8Iejvs.cjs';
|
|
2
2
|
import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
|
|
3
3
|
|
|
4
4
|
type DrizzleAdapterOptions = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as DatabaseAdapter, C as Channel, a as DeliveryStatus, F as Frequency } from '../adapter-
|
|
1
|
+
import { D as DatabaseAdapter, C as Channel, a as DeliveryStatus, F as Frequency } from '../adapter-BJ8Iejvs.js';
|
|
2
2
|
import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
|
|
3
3
|
|
|
4
4
|
type DrizzleAdapterOptions = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DatabaseAdapter } from '../adapter-
|
|
2
|
-
import { S as SchemaDeclaration } from '../plugin-
|
|
1
|
+
import { D as DatabaseAdapter } from '../adapter-BJ8Iejvs.cjs';
|
|
2
|
+
import { S as SchemaDeclaration } from '../plugin-Wwp1nvo0.cjs';
|
|
3
3
|
import { S as Signals } from '../signals-kPmOlKDX.cjs';
|
|
4
4
|
|
|
5
5
|
type MongoAdapterOptions = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DatabaseAdapter } from '../adapter-
|
|
2
|
-
import { S as SchemaDeclaration } from '../plugin-
|
|
1
|
+
import { D as DatabaseAdapter } from '../adapter-BJ8Iejvs.js';
|
|
2
|
+
import { S as SchemaDeclaration } from '../plugin-CSLKKgXT.js';
|
|
3
3
|
import { S as Signals } from '../signals-kPmOlKDX.js';
|
|
4
4
|
|
|
5
5
|
type MongoAdapterOptions = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { S as SqlAdapterOptions, a as SqlExec } from '../adapter-B7R4rXtJ.cjs';
|
|
2
2
|
export { b as MysqlResult } from '../adapter-B7R4rXtJ.cjs';
|
|
3
|
-
import { D as DatabaseAdapter } from '../adapter-
|
|
4
|
-
import { S as SchemaDeclaration } from '../plugin-
|
|
3
|
+
import { D as DatabaseAdapter } from '../adapter-BJ8Iejvs.cjs';
|
|
4
|
+
import { S as SchemaDeclaration } from '../plugin-Wwp1nvo0.cjs';
|
|
5
5
|
|
|
6
6
|
type MysqlAdapterOptions = SqlAdapterOptions;
|
|
7
7
|
/**
|
package/dist/adapters/mysql.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { S as SqlAdapterOptions, a as SqlExec } from '../adapter-B7R4rXtJ.js';
|
|
2
2
|
export { b as MysqlResult } from '../adapter-B7R4rXtJ.js';
|
|
3
|
-
import { D as DatabaseAdapter } from '../adapter-
|
|
4
|
-
import { S as SchemaDeclaration } from '../plugin-
|
|
3
|
+
import { D as DatabaseAdapter } from '../adapter-BJ8Iejvs.js';
|
|
4
|
+
import { S as SchemaDeclaration } from '../plugin-CSLKKgXT.js';
|
|
5
5
|
|
|
6
6
|
type MysqlAdapterOptions = SqlAdapterOptions;
|
|
7
7
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DatabaseAdapter } from '../adapter-
|
|
2
|
-
import { S as SchemaDeclaration } from '../plugin-
|
|
1
|
+
import { D as DatabaseAdapter } from '../adapter-BJ8Iejvs.cjs';
|
|
2
|
+
import { S as SchemaDeclaration } from '../plugin-Wwp1nvo0.cjs';
|
|
3
3
|
import { S as Signals } from '../signals-kPmOlKDX.cjs';
|
|
4
4
|
|
|
5
5
|
type Row = Record<string, unknown>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DatabaseAdapter } from '../adapter-
|
|
2
|
-
import { S as SchemaDeclaration } from '../plugin-
|
|
1
|
+
import { D as DatabaseAdapter } from '../adapter-BJ8Iejvs.js';
|
|
2
|
+
import { S as SchemaDeclaration } from '../plugin-CSLKKgXT.js';
|
|
3
3
|
import { S as Signals } from '../signals-kPmOlKDX.js';
|
|
4
4
|
|
|
5
5
|
type Row = Record<string, unknown>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { S as SqlAdapterOptions, a as SqlExec } from '../adapter-B7R4rXtJ.cjs';
|
|
2
2
|
export { b as SqliteResult } from '../adapter-B7R4rXtJ.cjs';
|
|
3
|
-
import { D as DatabaseAdapter } from '../adapter-
|
|
4
|
-
import { S as SchemaDeclaration } from '../plugin-
|
|
3
|
+
import { D as DatabaseAdapter } from '../adapter-BJ8Iejvs.cjs';
|
|
4
|
+
import { S as SchemaDeclaration } from '../plugin-Wwp1nvo0.cjs';
|
|
5
5
|
|
|
6
6
|
type SqliteAdapterOptions = SqlAdapterOptions;
|
|
7
7
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { S as SqlAdapterOptions, a as SqlExec } from '../adapter-B7R4rXtJ.js';
|
|
2
2
|
export { b as SqliteResult } from '../adapter-B7R4rXtJ.js';
|
|
3
|
-
import { D as DatabaseAdapter } from '../adapter-
|
|
4
|
-
import { S as SchemaDeclaration } from '../plugin-
|
|
3
|
+
import { D as DatabaseAdapter } from '../adapter-BJ8Iejvs.js';
|
|
4
|
+
import { S as SchemaDeclaration } from '../plugin-CSLKKgXT.js';
|
|
5
5
|
|
|
6
6
|
type SqliteAdapterOptions = SqlAdapterOptions;
|
|
7
7
|
/**
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/core/html.ts
|
|
2
|
+
var ESCAPES = {
|
|
3
|
+
"&": "&",
|
|
4
|
+
"<": "<",
|
|
5
|
+
">": ">",
|
|
6
|
+
'"': """,
|
|
7
|
+
"'": "'"
|
|
8
|
+
};
|
|
9
|
+
var escapeHtml = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ESCAPES[char] ?? char);
|
|
10
|
+
|
|
11
|
+
export { escapeHtml };
|
|
12
|
+
//# sourceMappingURL=chunk-R3BE67L7.js.map
|
|
13
|
+
//# sourceMappingURL=chunk-R3BE67L7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/html.ts"],"names":[],"mappings":";AAAA,IAAM,OAAA,GAAkC;AAAA,EACtC,GAAA,EAAK,OAAA;AAAA,EACL,GAAA,EAAK,MAAA;AAAA,EACL,GAAA,EAAK,MAAA;AAAA,EACL,GAAA,EAAK,QAAA;AAAA,EACL,GAAA,EAAK;AACP,CAAA;AAGO,IAAM,UAAA,GAAa,CAAC,KAAA,KACzB,MAAA,CAAO,SAAS,EAAE,CAAA,CAAE,OAAA,CAAQ,UAAA,EAAY,CAAC,IAAA,KAAS,OAAA,CAAQ,IAAI,KAAK,IAAI","file":"chunk-R3BE67L7.js","sourcesContent":["const ESCAPES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n '\"': \""\",\n \"'\": \"'\",\n};\n\n/** For template literals: anything a user typed goes through this, or their HTML ships from your domain. */\nexport const escapeHtml = (value: unknown): string =>\n String(value ?? \"\").replace(/[&<>\"']/g, (char) => ESCAPES[char] ?? char);\n"]}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// src/providers/telegram/index.ts
|
|
2
|
+
var telegramStartLink = (botUsername, code) => `https://t.me/${botUsername.replace(/^@/, "")}?start=${encodeURIComponent(code)}`;
|
|
3
|
+
function telegramBot(options) {
|
|
4
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
5
|
+
const base = `${(options.apiBase ?? "https://api.telegram.org").replace(/\/$/, "")}/bot${options.token}`;
|
|
6
|
+
const timeoutMs = options.timeoutMs ?? 1e4;
|
|
7
|
+
const redact = (text) => text.split(options.token).join("<token>");
|
|
8
|
+
async function call(method, body, signal, budgetMs = timeoutMs) {
|
|
9
|
+
const timeout = AbortSignal.timeout(budgetMs);
|
|
10
|
+
const merged = signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
11
|
+
const response = await doFetch(`${base}/${method}`, {
|
|
12
|
+
method: "POST",
|
|
13
|
+
headers: { "content-type": "application/json" },
|
|
14
|
+
body: JSON.stringify(body),
|
|
15
|
+
signal: merged
|
|
16
|
+
});
|
|
17
|
+
const data = await response.json().catch(() => ({ ok: false }));
|
|
18
|
+
return { status: response.status, data };
|
|
19
|
+
}
|
|
20
|
+
async function must(method, body) {
|
|
21
|
+
const { data } = await call(method, body);
|
|
22
|
+
if (!data.ok)
|
|
23
|
+
throw new Error(redact(`telegram ${method} failed: ${data.description ?? "unknown"}`));
|
|
24
|
+
return data.result;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
name: "telegram",
|
|
28
|
+
async send(message) {
|
|
29
|
+
const body = {
|
|
30
|
+
chat_id: message.chatId,
|
|
31
|
+
text: message.text,
|
|
32
|
+
parse_mode: "HTML",
|
|
33
|
+
link_preview_options: { is_disabled: true }
|
|
34
|
+
};
|
|
35
|
+
if (message.buttons?.length) {
|
|
36
|
+
body.reply_markup = {
|
|
37
|
+
inline_keyboard: [
|
|
38
|
+
message.buttons.map((button) => ({ text: button.text, url: button.url }))
|
|
39
|
+
]
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
let status;
|
|
43
|
+
let data;
|
|
44
|
+
try {
|
|
45
|
+
({ status, data } = await call(
|
|
46
|
+
"sendMessage",
|
|
47
|
+
body,
|
|
48
|
+
message.signal
|
|
49
|
+
));
|
|
50
|
+
} catch (error) {
|
|
51
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
52
|
+
return { ok: false, error: redact(text), retryable: true, gone: false };
|
|
53
|
+
}
|
|
54
|
+
if (data.ok) return { ok: true, messageId: data.result.message_id };
|
|
55
|
+
const description = redact(data.description ?? `HTTP ${status}`);
|
|
56
|
+
const code = data.error_code ?? status;
|
|
57
|
+
if (code === 429) {
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
error: description,
|
|
61
|
+
retryable: true,
|
|
62
|
+
gone: false,
|
|
63
|
+
retryAfterSeconds: data.parameters?.retry_after
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const gone = code === 403 || code === 400 && /chat not found|user is deactivated|bot was kicked/i.test(description);
|
|
67
|
+
return { ok: false, error: description, retryable: code >= 500, gone };
|
|
68
|
+
},
|
|
69
|
+
async getUpdates({ offset, timeoutSeconds = 25, signal }) {
|
|
70
|
+
const { data } = await call(
|
|
71
|
+
"getUpdates",
|
|
72
|
+
{
|
|
73
|
+
...offset === void 0 ? {} : { offset },
|
|
74
|
+
timeout: timeoutSeconds,
|
|
75
|
+
allowed_updates: ["message", "my_chat_member"]
|
|
76
|
+
},
|
|
77
|
+
signal,
|
|
78
|
+
(timeoutSeconds + 10) * 1e3
|
|
79
|
+
);
|
|
80
|
+
if (!data.ok)
|
|
81
|
+
throw new Error(redact(`telegram getUpdates failed: ${data.description ?? "unknown"}`));
|
|
82
|
+
return data.result;
|
|
83
|
+
},
|
|
84
|
+
setWebhook: async (url, secretToken) => {
|
|
85
|
+
await must("setWebhook", {
|
|
86
|
+
url,
|
|
87
|
+
secret_token: secretToken,
|
|
88
|
+
allowed_updates: ["message", "my_chat_member"]
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
deleteWebhook: async () => {
|
|
92
|
+
await must("deleteWebhook", {});
|
|
93
|
+
},
|
|
94
|
+
getMe: () => must("getMe", {})
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export { telegramBot, telegramStartLink };
|
|
99
|
+
//# sourceMappingURL=chunk-SSHVZHSJ.js.map
|
|
100
|
+
//# sourceMappingURL=chunk-SSHVZHSJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/providers/telegram/index.ts"],"names":[],"mappings":"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","file":"chunk-SSHVZHSJ.js","sourcesContent":["export type TelegramButton = { text: string; url: string };\n\nexport type TelegramMessage = {\n chatId: string;\n /** Telegram HTML: <b>, <i>, <a href>, <code>, <pre>. Escape user text with escapeHtml(). */\n text: string;\n buttons?: readonly TelegramButton[] | undefined;\n signal?: AbortSignal | undefined;\n};\n\nexport type TelegramSendResult =\n | { ok: true; messageId: number }\n | {\n ok: false;\n error: string;\n /** Worth another attempt: rate limit, 5xx, network. */\n retryable: boolean;\n /** The chat can never receive again (user blocked the bot, chat deleted). Prune it. */\n gone: boolean;\n retryAfterSeconds?: number | undefined;\n };\n\n/** The slice of a Bot API update the plugin reads. Everything else passes through untouched. */\nexport type TelegramUpdate = {\n update_id: number;\n message?: {\n text?: string;\n chat: { id: number | string; type: string; username?: string; title?: string };\n from?: { id: number; username?: string; first_name?: string };\n };\n my_chat_member?: {\n chat: { id: number | string; type: string; username?: string; title?: string };\n new_chat_member: { status: string };\n };\n};\n\nexport type TelegramProvider = {\n name: string;\n send(message: TelegramMessage): Promise<TelegramSendResult>;\n /** Long-polls for updates. For development or hosts without a public URL. */\n getUpdates(options: {\n offset?: number | undefined;\n timeoutSeconds?: number | undefined;\n signal?: AbortSignal | undefined;\n }): Promise<TelegramUpdate[]>;\n /** Points Telegram at your webhook route. Run once per deployment URL. */\n setWebhook(url: string, secretToken: string): Promise<void>;\n deleteWebhook(): Promise<void>;\n getMe(): Promise<{ id: number; username: string }>;\n};\n\nexport type TelegramBotOptions = {\n /** From @BotFather. Never appears in errors or logs. */\n token: string;\n fetch?: typeof globalThis.fetch | undefined;\n /** Default https://api.telegram.org. A local fake for tests. */\n apiBase?: string | undefined;\n timeoutMs?: number | undefined;\n};\n\ntype ApiResponse<T> =\n | { ok: true; result: T }\n | { ok: false; description?: string; error_code?: number; parameters?: { retry_after?: number } };\n\n/** Deep link that opens the bot with a start parameter. Codes are [A-Za-z0-9_-], at most 64 chars. */\nexport const telegramStartLink = (botUsername: string, code: string) =>\n `https://t.me/${botUsername.replace(/^@/, \"\")}?start=${encodeURIComponent(code)}`;\n\n/** Bot API over fetch, so it runs on edge runtimes. Failures are classified, never thrown, on send. */\nexport function telegramBot(options: TelegramBotOptions): TelegramProvider {\n const doFetch = options.fetch ?? globalThis.fetch;\n const base = `${(options.apiBase ?? \"https://api.telegram.org\").replace(/\\/$/, \"\")}/bot${options.token}`;\n const timeoutMs = options.timeoutMs ?? 10_000;\n const redact = (text: string) => text.split(options.token).join(\"<token>\");\n\n async function call<T>(\n method: string,\n body: Record<string, unknown>,\n signal?: AbortSignal,\n budgetMs = timeoutMs,\n ): Promise<{ status: number; data: ApiResponse<T> }> {\n const timeout = AbortSignal.timeout(budgetMs);\n const merged = signal ? AbortSignal.any([signal, timeout]) : timeout;\n const response = await doFetch(`${base}/${method}`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n signal: merged,\n });\n const data = (await response.json().catch(() => ({ ok: false }))) as ApiResponse<T>;\n return { status: response.status, data };\n }\n\n async function must<T>(method: string, body: Record<string, unknown>): Promise<T> {\n const { data } = await call<T>(method, body);\n if (!data.ok)\n throw new Error(redact(`telegram ${method} failed: ${data.description ?? \"unknown\"}`));\n return data.result;\n }\n\n return {\n name: \"telegram\",\n\n async send(message) {\n const body: Record<string, unknown> = {\n chat_id: message.chatId,\n text: message.text,\n parse_mode: \"HTML\",\n link_preview_options: { is_disabled: true },\n };\n if (message.buttons?.length) {\n body.reply_markup = {\n inline_keyboard: [\n message.buttons.map((button) => ({ text: button.text, url: button.url })),\n ],\n };\n }\n\n let status: number;\n let data: ApiResponse<{ message_id: number }>;\n try {\n ({ status, data } = await call<{ message_id: number }>(\n \"sendMessage\",\n body,\n message.signal,\n ));\n } catch (error) {\n const text = error instanceof Error ? error.message : String(error);\n return { ok: false, error: redact(text), retryable: true, gone: false };\n }\n\n if (data.ok) return { ok: true, messageId: data.result.message_id };\n\n const description = redact(data.description ?? `HTTP ${status}`);\n const code = data.error_code ?? status;\n if (code === 429) {\n return {\n ok: false,\n error: description,\n retryable: true,\n gone: false,\n retryAfterSeconds: data.parameters?.retry_after,\n };\n }\n // 403: the user blocked the bot or was deactivated. 400 \"chat not found\": deleted or never existed.\n const gone =\n code === 403 ||\n (code === 400 && /chat not found|user is deactivated|bot was kicked/i.test(description));\n return { ok: false, error: description, retryable: code >= 500, gone };\n },\n\n async getUpdates({ offset, timeoutSeconds = 25, signal }) {\n // Telegram holds the request open for `timeout` seconds; the budget must outlive it.\n const { data } = await call<TelegramUpdate[]>(\n \"getUpdates\",\n {\n ...(offset === undefined ? {} : { offset }),\n timeout: timeoutSeconds,\n allowed_updates: [\"message\", \"my_chat_member\"],\n },\n signal,\n (timeoutSeconds + 10) * 1000,\n );\n if (!data.ok)\n throw new Error(redact(`telegram getUpdates failed: ${data.description ?? \"unknown\"}`));\n return data.result;\n },\n\n setWebhook: async (url, secretToken) => {\n await must(\"setWebhook\", {\n url,\n secret_token: secretToken,\n allowed_updates: [\"message\", \"my_chat_member\"],\n });\n },\n deleteWebhook: async () => {\n await must(\"deleteWebhook\", {});\n },\n getMe: () => must(\"getMe\", {}),\n };\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { C as Channel, b as DeliveryMode, S as SkipReason, c as DeliveryRecord, D as DatabaseAdapter, R as Recipient } from './adapter-
|
|
2
|
-
export { d as ClaimArgs, e as ClaimedDelivery, f as DeliveryOutcome, g as DeliveryRelease, a as DeliveryStatus, h as FeedPage, i as FeedQuery, F as Frequency, I as InsertDelivery, j as InsertNotification, N as NotificationRecord, P as PreferenceRecord } from './adapter-
|
|
1
|
+
import { C as Channel, b as DeliveryMode, S as SkipReason, c as DeliveryRecord, D as DatabaseAdapter, R as Recipient } from './adapter-BJ8Iejvs.cjs';
|
|
2
|
+
export { d as ClaimArgs, e as ClaimedDelivery, f as DeliveryOutcome, g as DeliveryRelease, a as DeliveryStatus, h as FeedPage, i as FeedQuery, F as Frequency, I as InsertDelivery, j as InsertNotification, N as NotificationRecord, P as PreferenceRecord } from './adapter-BJ8Iejvs.cjs';
|
|
3
3
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
4
|
-
import { R as RouteScope, A as AnyPlugin, P as Promisable, L as Logger } from './plugin-
|
|
5
|
-
export { a as ActionsOf, b as AfterDeliverContext, c as AnyClientPlugin, B as BeforeSendContext, d as BeforeSendResult, C as ChannelDecision, e as ClientFetch, f as ClientPlugin, g as ConfigError, D as DEFAULT_TOKEN_TTL_SECONDS, E as EasyPingError, h as EasyPingPlugin, F as FieldDeclaration, i as FieldType, M as MAX_TOKEN_TTL_SECONDS, j as PluginHooks, k as PrepareContext, l as ResolveChannelsContext, m as RouteContext, n as RouteDefinition, S as SchemaDeclaration, o as SignRequest, T as TableDeclaration, p as TokenClaims, U as UnionToIntersection, V as ValidationError, q as consoleLogger, r as defineClientPlugin, s as definePlugin, t as expiresIn, u as signToken, v as verifyToken } from './plugin-
|
|
4
|
+
import { R as RouteScope, A as AnyPlugin, P as Promisable, L as Logger } from './plugin-Wwp1nvo0.cjs';
|
|
5
|
+
export { a as ActionsOf, b as AfterDeliverContext, c as AnyClientPlugin, B as BeforeSendContext, d as BeforeSendResult, C as ChannelDecision, e as ClientFetch, f as ClientPlugin, g as ConfigError, D as DEFAULT_TOKEN_TTL_SECONDS, E as EasyPingError, h as EasyPingPlugin, F as FieldDeclaration, i as FieldType, M as MAX_TOKEN_TTL_SECONDS, j as PluginHooks, k as PrepareContext, l as ResolveChannelsContext, m as RouteContext, n as RouteDefinition, S as SchemaDeclaration, o as SignRequest, T as TableDeclaration, p as TokenClaims, U as UnionToIntersection, V as ValidationError, q as consoleLogger, r as defineClientPlugin, s as definePlugin, t as expiresIn, u as signToken, v as verifyToken } from './plugin-Wwp1nvo0.cjs';
|
|
6
6
|
import { E as EmailProvider } from './provider-CA45WVqt.cjs';
|
|
7
7
|
export { a as EmailMessage, P as ProviderSendResult } from './provider-CA45WVqt.cjs';
|
|
8
8
|
import { S as Signals } from './signals-kPmOlKDX.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { C as Channel, b as DeliveryMode, S as SkipReason, c as DeliveryRecord, D as DatabaseAdapter, R as Recipient } from './adapter-
|
|
2
|
-
export { d as ClaimArgs, e as ClaimedDelivery, f as DeliveryOutcome, g as DeliveryRelease, a as DeliveryStatus, h as FeedPage, i as FeedQuery, F as Frequency, I as InsertDelivery, j as InsertNotification, N as NotificationRecord, P as PreferenceRecord } from './adapter-
|
|
1
|
+
import { C as Channel, b as DeliveryMode, S as SkipReason, c as DeliveryRecord, D as DatabaseAdapter, R as Recipient } from './adapter-BJ8Iejvs.js';
|
|
2
|
+
export { d as ClaimArgs, e as ClaimedDelivery, f as DeliveryOutcome, g as DeliveryRelease, a as DeliveryStatus, h as FeedPage, i as FeedQuery, F as Frequency, I as InsertDelivery, j as InsertNotification, N as NotificationRecord, P as PreferenceRecord } from './adapter-BJ8Iejvs.js';
|
|
3
3
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
4
|
-
import { R as RouteScope, A as AnyPlugin, P as Promisable, L as Logger } from './plugin-
|
|
5
|
-
export { a as ActionsOf, b as AfterDeliverContext, c as AnyClientPlugin, B as BeforeSendContext, d as BeforeSendResult, C as ChannelDecision, e as ClientFetch, f as ClientPlugin, g as ConfigError, D as DEFAULT_TOKEN_TTL_SECONDS, E as EasyPingError, h as EasyPingPlugin, F as FieldDeclaration, i as FieldType, M as MAX_TOKEN_TTL_SECONDS, j as PluginHooks, k as PrepareContext, l as ResolveChannelsContext, m as RouteContext, n as RouteDefinition, S as SchemaDeclaration, o as SignRequest, T as TableDeclaration, p as TokenClaims, U as UnionToIntersection, V as ValidationError, q as consoleLogger, r as defineClientPlugin, s as definePlugin, t as expiresIn, u as signToken, v as verifyToken } from './plugin-
|
|
4
|
+
import { R as RouteScope, A as AnyPlugin, P as Promisable, L as Logger } from './plugin-CSLKKgXT.js';
|
|
5
|
+
export { a as ActionsOf, b as AfterDeliverContext, c as AnyClientPlugin, B as BeforeSendContext, d as BeforeSendResult, C as ChannelDecision, e as ClientFetch, f as ClientPlugin, g as ConfigError, D as DEFAULT_TOKEN_TTL_SECONDS, E as EasyPingError, h as EasyPingPlugin, F as FieldDeclaration, i as FieldType, M as MAX_TOKEN_TTL_SECONDS, j as PluginHooks, k as PrepareContext, l as ResolveChannelsContext, m as RouteContext, n as RouteDefinition, S as SchemaDeclaration, o as SignRequest, T as TableDeclaration, p as TokenClaims, U as UnionToIntersection, V as ValidationError, q as consoleLogger, r as defineClientPlugin, s as definePlugin, t as expiresIn, u as signToken, v as verifyToken } from './plugin-CSLKKgXT.js';
|
|
6
6
|
import { E as EmailProvider } from './provider-CA45WVqt.js';
|
|
7
7
|
export { a as EmailMessage, P as ProviderSendResult } from './provider-CA45WVqt.js';
|
|
8
8
|
import { S as Signals } from './signals-kPmOlKDX.js';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createRunner } from './chunk-JKTMUMQK.js';
|
|
2
2
|
export { createRunner, redactErrorMessage } from './chunk-JKTMUMQK.js';
|
|
3
|
+
export { escapeHtml } from './chunk-R3BE67L7.js';
|
|
3
4
|
export { defineClientPlugin, definePlugin } from './chunk-E4IZ2CQC.js';
|
|
4
5
|
import { createHandler, createScopedSigner } from './chunk-JOVIZXO6.js';
|
|
5
6
|
export { DEFAULT_TOKEN_TTL_SECONDS, MAX_BODY_BYTES, MAX_TOKEN_TTL_SECONDS, expiresIn, readJsonBody, signToken, verifyToken } from './chunk-JOVIZXO6.js';
|
|
@@ -18,16 +19,6 @@ function defineNotification(definition) {
|
|
|
18
19
|
return definition;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
// src/core/html.ts
|
|
22
|
-
var ESCAPES = {
|
|
23
|
-
"&": "&",
|
|
24
|
-
"<": "<",
|
|
25
|
-
">": ">",
|
|
26
|
-
'"': """,
|
|
27
|
-
"'": "'"
|
|
28
|
-
};
|
|
29
|
-
var escapeHtml = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ESCAPES[char] ?? char);
|
|
30
|
-
|
|
31
22
|
// src/core/rate-limit.ts
|
|
32
23
|
var clientAddress = (request) => {
|
|
33
24
|
const headers = request.headers;
|
|
@@ -535,6 +526,6 @@ function easyPing(config) {
|
|
|
535
526
|
};
|
|
536
527
|
}
|
|
537
528
|
|
|
538
|
-
export { INBOX_VERSION_HEADER, MIN_SECRET_LENGTH, createRateLimiter, defineNotification, easyPing
|
|
529
|
+
export { INBOX_VERSION_HEADER, MIN_SECRET_LENGTH, createRateLimiter, defineNotification, easyPing };
|
|
539
530
|
//# sourceMappingURL=index.js.map
|
|
540
531
|
//# sourceMappingURL=index.js.map
|