@suphark/notify 0.6.0 → 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 +43 -1
- package/dist/{chunk-AQWFHD5T.js → chunk-2RXN5HXT.js} +20 -5
- package/dist/index.d.ts +6 -3
- package/dist/index.js +5 -1
- package/dist/next.d.ts +8 -2
- package/dist/next.js +17 -1
- package/dist/{notify-BM6FBQVP.d.ts → notify-D4auDRfk.d.ts} +29 -5
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @suphark/notify
|
|
2
2
|
|
|
3
|
-
SDK ส่งอีเมล + แจ้งเตือน (Telegram / Web Push) ของแอปทุกตัว — ค่าเริ่มต้นส่งผ่าน **Pi Notify hub** (`https://notify.suphark.com`) ซึ่งเป็นคนถือ credential ของ Resend/SMTP/Zoho/Telegram bot ทำ failover นับโควต้า เก็บ log และเก็บว่าผู้รับผูกแชทไหนไว้ แอปใส่แค่ key ตัวเดียว
|
|
3
|
+
SDK ส่งอีเมล + แจ้งเตือน (Telegram / LINE / Web Push) ของแอปทุกตัว — ค่าเริ่มต้นส่งผ่าน **Pi Notify hub** (`https://notify.suphark.com`) ซึ่งเป็นคนถือ credential ของ Resend/SMTP/Zoho/Telegram bot/LINE OA ทำ failover นับโควต้า เก็บ log และเก็บว่าผู้รับผูกแชทไหนไว้ แอปใส่แค่ key ตัวเดียว
|
|
4
4
|
|
|
5
5
|
> 📖 **คู่มือการใช้งานและสูตรการเขียนโค้ดฉบับเต็ม:** ดูที่ [docs/guide.md](../../docs/guide.md)
|
|
6
6
|
|
|
@@ -94,6 +94,43 @@ const { url, code } = await createLink(user); // รับ use
|
|
|
94
94
|
const { linked, label, manageUrl } = await getChannelStatus(user, "telegram");
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
### LINE (0.7.0)
|
|
98
|
+
|
|
99
|
+
ใช้เหมือน Telegram ทุกอย่าง — ผู้รับผูก LINE ของตัวเองครั้งเดียว (ที่ `/me` ของ hub หรือปุ่มในแอป) แล้วส่งด้วยอีเมล:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const { url, code } = await createLink(user, "line");
|
|
103
|
+
// url เปิดแชทกับ OA พร้อมพิมพ์ "ผูก Pi Notify <code>" ไว้ — ผู้ใช้กดส่ง (ยังไม่เป็นเพื่อนกับ OA ให้เพิ่มเพื่อนด้วย)
|
|
104
|
+
const { linked } = await getChannelStatus(user, "line");
|
|
105
|
+
await notify({ to: user, title: "PO รออนุมัติ", body: "…", link, channels: ["line"] });
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- ข้อความ LINE เป็น text ล้วน: หัวเรื่อง / เนื้อหา / ลิงก์บรรทัดสุดท้าย (กดได้) — body ที่เป็น HTML ถูกแปลงเป็นข้อความ, ยาวเกิน 5,000 ตัดเนื้อหาแต่คงลิงก์
|
|
109
|
+
- ผู้ใช้บล็อก OA → hub ปิดการผูกให้เอง (ปลดบล็อกแล้วกลับมาเอง) · พิมพ์ "หยุดแจ้งเตือน" ในแชท OA = เลิกรับ
|
|
110
|
+
|
|
111
|
+
### ส่งเข้ากลุ่ม: `notifyTopic()` (0.7.0)
|
|
112
|
+
|
|
113
|
+
แอปไม่ต้องถือ group id — admin เชิญ OA/bot เข้ากลุ่ม LINE/Telegram (hub เห็นกลุ่มเองจาก webhook) แล้วผูกกลุ่มกับ **topic ของแอป** ที่หน้า Topics ของ Pi Notify
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { notifyTopic } from "@suphark/notify";
|
|
117
|
+
|
|
118
|
+
const r = await notifyTopic({
|
|
119
|
+
topic: "po-overdue", // topic ของแอปนี้ (a-z 0-9 . _ -)
|
|
120
|
+
title: "PO เกินกำหนด 3 รายการ",
|
|
121
|
+
body: "PO-2611-001, …",
|
|
122
|
+
link: "https://app.example.com/po?status=overdue",
|
|
123
|
+
deliverWithin: 3600, // ใช้ได้เหมือน notify()
|
|
124
|
+
idempotencyKey: `po-overdue:${date}`,
|
|
125
|
+
});
|
|
126
|
+
// r.results = [{ to: "topic:po-overdue", channel: "line", status: "sent", target: "ทีมจัดซื้อ" }, …] (รายกลุ่ม)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
- topic ไม่พบ → `MailError` `invalid` (404) · topic ถูกปิด/ยังไม่ผูกกลุ่ม → `status: "skipped", reason: "no_target"` (ไม่โยน)
|
|
130
|
+
- `channels: ["line"]` = เฉพาะกลุ่ม LINE ของ topic · ไม่ระบุ = ทุกกลุ่ม
|
|
131
|
+
- แบบไม่โยน/เบื้องหลัง: `notifyTopicSafe()` และ `notifyTopicLater()` จาก `@suphark/notify/next`
|
|
132
|
+
- LINE คิดโควต้าตามข้อความ push — ยอดของเดือนดูที่หน้า Providers ของ Pi Notify
|
|
133
|
+
|
|
97
134
|
### ให้ hub ส่งซ้ำเอง: `deliverWithin` + `idempotencyKey` (0.6.0)
|
|
98
135
|
|
|
99
136
|
```ts
|
|
@@ -391,6 +428,11 @@ Pi Notify มี contract test (`apps/notify/src/test/contract/`) เก็บ b
|
|
|
391
428
|
|
|
392
429
|
## Changelog
|
|
393
430
|
|
|
431
|
+
- 0.7.0 — **LINE + ส่งเข้ากลุ่มด้วย topic** (เพิ่มอย่างเดียว; ต้องใช้ Pi Notify 2026-11-11+)
|
|
432
|
+
- `createLink(email, "line")` / `getChannelStatus(email, "line")` / `notify({ channels: ["line"] })` — ช่องทาง LINE รายคน
|
|
433
|
+
- `notifyTopic({ topic, title, body, … })` + `notifyTopicSafe()` + `notifyTopicLater()` (`/next`) — ส่งเข้าทุกกลุ่ม LINE/Telegram ที่ admin ผูกกับ topic ของแอป
|
|
434
|
+
- `NotifyResultItem.target?` (ชื่อกลุ่ม) และ `reason` เพิ่ม `"no_target"` (ออกเป็น minor เพราะ union ของ `reason` ที่แอปอาจ switch)
|
|
435
|
+
|
|
394
436
|
- 0.6.0 — **hub ส่งซ้ำเอง** (เพิ่มอย่างเดียว — แอปที่ไม่ส่งตัวเลือกใหม่ได้พฤติกรรมเดิมทุกอย่าง; ต้องใช้ Pi Notify 2026-10-14+)
|
|
395
437
|
- `notify({ deliverWithin })` (วินาที 60–86400): รายการที่ค้างหรือพลาดชั่วคราวเข้าคิวถาวรของ hub แล้วส่งซ้ำตาม backoff (1 นาที → 5 นาที → 30 นาที → 2 ชม., สูงสุด 5 ครั้ง) · ผิดถาวรไม่ส่งซ้ำ · เลยเวลาแล้วไม่ส่ง · ไม่สลับช่องทาง
|
|
396
438
|
- `notify({ idempotencyKey })` → หัว `Idempotency-Key`: key+เนื้อหาเดิมได้ผลเดิม ไม่ส่งซ้ำ (จำ 7 วัน) · เนื้อหาต่างแต่ key เดิม = `invalid` (422) · คำขอเดิมยังทำอยู่ = `rate_limit` (409, `retryAfterSec` 5)
|
|
@@ -1128,16 +1128,23 @@ async function hubRequest(opts, path, init) {
|
|
|
1128
1128
|
}
|
|
1129
1129
|
return data;
|
|
1130
1130
|
}
|
|
1131
|
-
async function
|
|
1132
|
-
const { idempotencyKey, ...body } = input;
|
|
1131
|
+
async function postNotify(body, idempotencyKey, opts) {
|
|
1133
1132
|
const data = await hubRequest(opts, "/api/v1/notify", {
|
|
1134
1133
|
method: "POST",
|
|
1135
|
-
body
|
|
1134
|
+
body,
|
|
1136
1135
|
headers: idempotencyKey ? { "Idempotency-Key": idempotencyKey } : void 0,
|
|
1137
1136
|
timeoutMs: NOTIFY_TIMEOUT_MS.notify
|
|
1138
1137
|
});
|
|
1139
1138
|
return { results: data.results, ...data.summary };
|
|
1140
1139
|
}
|
|
1140
|
+
async function notify(input, opts = {}) {
|
|
1141
|
+
const { idempotencyKey, ...body } = input;
|
|
1142
|
+
return postNotify({ ...body, to: toEmails(input.to) }, idempotencyKey, opts);
|
|
1143
|
+
}
|
|
1144
|
+
async function notifyTopic(input, opts = {}) {
|
|
1145
|
+
const { idempotencyKey, ...body } = input;
|
|
1146
|
+
return postNotify(body, idempotencyKey, opts);
|
|
1147
|
+
}
|
|
1141
1148
|
async function getRecipient(email, opts = {}) {
|
|
1142
1149
|
return hubRequest(
|
|
1143
1150
|
opts,
|
|
@@ -1216,13 +1223,19 @@ function resetSafeWarnings() {
|
|
|
1216
1223
|
}
|
|
1217
1224
|
var messageOf = (err) => err instanceof Error ? err.message : String(err);
|
|
1218
1225
|
async function notifySafe(input, opts = {}) {
|
|
1226
|
+
return safely("notify()", () => notify(input, opts), opts);
|
|
1227
|
+
}
|
|
1228
|
+
async function notifyTopicSafe(input, opts = {}) {
|
|
1229
|
+
return safely("notifyTopic()", () => notifyTopic(input, opts), opts);
|
|
1230
|
+
}
|
|
1231
|
+
async function safely(what, run, opts) {
|
|
1219
1232
|
const none = { sent: 0, failed: 0, skipped: 0, results: [] };
|
|
1220
1233
|
if (!hasNotifyKey(opts)) {
|
|
1221
|
-
warnDisabled(
|
|
1234
|
+
warnDisabled(what, opts.env ?? process.env);
|
|
1222
1235
|
return { ok: false, disabled: true, ...none };
|
|
1223
1236
|
}
|
|
1224
1237
|
try {
|
|
1225
|
-
const r = await
|
|
1238
|
+
const r = await run();
|
|
1226
1239
|
return { ok: r.failed === 0, ...r };
|
|
1227
1240
|
} catch (err) {
|
|
1228
1241
|
const body = err instanceof MailError ? err.cause : void 0;
|
|
@@ -1280,6 +1293,7 @@ export {
|
|
|
1280
1293
|
NOTIFY_TIMEOUT_MS,
|
|
1281
1294
|
hasNotifyKey,
|
|
1282
1295
|
notify,
|
|
1296
|
+
notifyTopic,
|
|
1283
1297
|
getRecipient,
|
|
1284
1298
|
createLink,
|
|
1285
1299
|
getChannelStatus,
|
|
@@ -1289,5 +1303,6 @@ export {
|
|
|
1289
1303
|
listPushDevices,
|
|
1290
1304
|
resetSafeWarnings,
|
|
1291
1305
|
notifySafe,
|
|
1306
|
+
notifyTopicSafe,
|
|
1292
1307
|
sendMailSafe
|
|
1293
1308
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { P as ParsedAddress, A as Address, a as Provider, E as ErrorKind, Q as Quota, M as Message, S as SendResult, b as ProviderStatus, c as ProviderInfo, d as Store, e as MailerEvent, f as Attempt, g as PushDevice, N as NotifyOptions, h as NotifyResultItem, i as NotifyInput } from './notify-
|
|
2
|
-
export {
|
|
1
|
+
import { P as ParsedAddress, A as Address, a as Provider, E as ErrorKind, Q as Quota, M as Message, S as SendResult, b as ProviderStatus, c as ProviderInfo, d as Store, e as MailerEvent, f as Attempt, g as PushDevice, N as NotifyOptions, h as NotifyResultItem, i as NotifyInput, j as NotifyTopicInput } from './notify-D4auDRfk.js';
|
|
2
|
+
export { k as Attachment, C as ChannelStatus, l as NOTIFY_TIMEOUT_MS, m as NormalizedMessage, n as NotifyChannel, o as NotifyResult, p as PushSubscriptionJson, q as QuotaUsage, R as Recipient, r as RecipientChannelInfo, s as RecipientInfo, t as SkipReason, u as createLink, v as deletePushSubscription, w as getChannelStatus, x as getPushConfig, y as getRecipient, z as hasNotifyKey, B as listPushDevices, D as notify, F as notifyTopic, G as savePushSubscription, H as toEmails } from './notify-D4auDRfk.js';
|
|
3
3
|
|
|
4
4
|
/** แปลง "ชื่อ <a@b.com>" / "a@b.com" / { name, email } → { name, email } */
|
|
5
5
|
declare function parseAddress(input: Address): ParsedAddress;
|
|
@@ -420,6 +420,8 @@ declare function resetSafeWarnings(): void;
|
|
|
420
420
|
* ไม่มี PI_NOTIFY_KEY → { ok: false, disabled: true } ไม่ส่ง
|
|
421
421
|
*/
|
|
422
422
|
declare function notifySafe(input: NotifyInput, opts?: NotifyOptions): Promise<NotifySafeResult>;
|
|
423
|
+
/** (0.7.0) notifyTopic() ที่ไม่โยนเด็ดขาด (ดู notifySafe) — topic ไม่พบก็คืน { ok: false, error } */
|
|
424
|
+
declare function notifyTopicSafe(input: NotifyTopicInput, opts?: NotifyOptions): Promise<NotifySafeResult>;
|
|
423
425
|
interface SendMailSafeResult {
|
|
424
426
|
ok: boolean;
|
|
425
427
|
provider?: string;
|
|
@@ -455,4 +457,5 @@ declare function createRedisStore(client: RedisLike, opts?: {
|
|
|
455
457
|
prefix?: string;
|
|
456
458
|
}): Store;
|
|
457
459
|
|
|
458
|
-
export { Address, AllProvidersFailedError, Attempt,
|
|
460
|
+
export { Address, AllProvidersFailedError, Attempt, DEFAULT_COOLDOWN, DEFAULT_HUB_URL, ErrorKind, MailError, MailerEvent, Message, NotifyInput, NotifyOptions, NotifyResultItem, NotifyTopicInput, ParsedAddress, Provider, ProviderInfo, ProviderStatus, PushDevice, Quota, SMTP_PRESETS, SendResult, Store, classifyError, configureMailer, createConsoleProvider, createHubProvider, createMailer, createMemoryStore, createPresetSmtpProvider, createPushHandlers, createRedisStore, createResendProvider, createSmtpProvider, createZeptoMailProvider, formatAddress, getMailer, listProviders, looksLikeHtml, notifySafe, notifyTopicSafe, parseAddress, parseAddressList, providersFromEnv, pushServiceWorker, pushServiceWorkerSource, renderNotifyEmail, requireFrom, resetMailer, resetSafeWarnings, resolveFormat, sendMail, sendMailSafe, toPlainText };
|
|
461
|
+
export type { BodyFormat, BrowserPushSubscription, ConsoleOptions, Env, HubErrorResponse, HubOptions, HubSendRequest, HubSendResponse, Mailer, MailerOptions, NotifyEmailInput, NotifySafeResult, ProviderConfig, PushHandlers, PushHandlersOptions, PushServiceWorkerOptions, PushUser, RedisLike, ResendOptions, SendMailSafeResult, SmtpOptions, SmtpPreset, ZeptoMailOptions };
|
package/dist/index.js
CHANGED
|
@@ -27,6 +27,8 @@ import {
|
|
|
27
27
|
listPushDevices,
|
|
28
28
|
notify,
|
|
29
29
|
notifySafe,
|
|
30
|
+
notifyTopic,
|
|
31
|
+
notifyTopicSafe,
|
|
30
32
|
parseAddress,
|
|
31
33
|
parseAddressList,
|
|
32
34
|
providersFromEnv,
|
|
@@ -37,7 +39,7 @@ import {
|
|
|
37
39
|
sendMail,
|
|
38
40
|
sendMailSafe,
|
|
39
41
|
toEmails
|
|
40
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-2RXN5HXT.js";
|
|
41
43
|
|
|
42
44
|
// src/format.ts
|
|
43
45
|
var KNOWN_TAG_RE = /<\/?(b|strong|i|em|u|ins|s|strike|del|a|code|pre|p|br|div|span|ul|ol|li|h[1-6]|table|tr|td)(?=[\s/>])[^>]*>/i;
|
|
@@ -219,6 +221,8 @@ export {
|
|
|
219
221
|
looksLikeHtml,
|
|
220
222
|
notify,
|
|
221
223
|
notifySafe,
|
|
224
|
+
notifyTopic,
|
|
225
|
+
notifyTopicSafe,
|
|
222
226
|
parseAddress,
|
|
223
227
|
parseAddressList,
|
|
224
228
|
providersFromEnv,
|
package/dist/next.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NotifyOptions, NotifyInput, NotifyTopicInput, Message } from './index.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* ส่งหลังตอบ response แล้ว (`after()` ของ Next.js) — entry แยก `@suphark/notify/next` เพราะ core ของ SDK
|
|
@@ -19,10 +19,16 @@ declare const LATER_TIMEOUT_MS = 20000;
|
|
|
19
19
|
* @example notifyLater({ to: user, title: "บันทึกแล้ว", body: "ระบบอัปเดตงานแล้ว" });
|
|
20
20
|
*/
|
|
21
21
|
declare function notifyLater(input: NotifyInput, opts?: LaterOptions): void;
|
|
22
|
+
/**
|
|
23
|
+
* (0.7.0) notifyTopic() เบื้องหลัง (ดู notifyLater) — คืน void
|
|
24
|
+
* @example notifyTopicLater({ topic: "po-overdue", title: "PO เกินกำหนด", body: "…" });
|
|
25
|
+
*/
|
|
26
|
+
declare function notifyTopicLater(input: NotifyTopicInput, opts?: LaterOptions): void;
|
|
22
27
|
/**
|
|
23
28
|
* sendMail() เบื้องหลัง (ดู notifyLater) — คืน void
|
|
24
29
|
* @example sendMailLater({ to: user.email, subject: "ยินดีต้อนรับ", html: "<p>…</p>" });
|
|
25
30
|
*/
|
|
26
31
|
declare function sendMailLater(message: Message, opts?: Pick<LaterOptions, "onError">): void;
|
|
27
32
|
|
|
28
|
-
export { LATER_TIMEOUT_MS,
|
|
33
|
+
export { LATER_TIMEOUT_MS, notifyLater, notifyTopicLater, sendMailLater };
|
|
34
|
+
export type { LaterOptions };
|
package/dist/next.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
notifySafe,
|
|
3
|
+
notifyTopicSafe,
|
|
3
4
|
sendMailSafe
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-2RXN5HXT.js";
|
|
5
6
|
|
|
6
7
|
// src/next.ts
|
|
7
8
|
import { after } from "next/server";
|
|
@@ -27,6 +28,20 @@ function notifyLater(input, opts = {}) {
|
|
|
27
28
|
report("notify()", r.error ?? (r.failed > 0 ? `${r.failed} \u0E23\u0E32\u0E22\u0E01\u0E32\u0E23\u0E25\u0E49\u0E21\u0E40\u0E2B\u0E25\u0E27` : void 0), onError);
|
|
28
29
|
});
|
|
29
30
|
}
|
|
31
|
+
function notifyTopicLater(input, opts = {}) {
|
|
32
|
+
const { onError, ...rest } = opts;
|
|
33
|
+
runLater(async () => {
|
|
34
|
+
const r = await notifyTopicSafe(input, {
|
|
35
|
+
timeoutMs: LATER_TIMEOUT_MS,
|
|
36
|
+
...rest
|
|
37
|
+
});
|
|
38
|
+
report(
|
|
39
|
+
"notifyTopic()",
|
|
40
|
+
r.error ?? (r.failed > 0 ? `${r.failed} \u0E23\u0E32\u0E22\u0E01\u0E32\u0E23\u0E25\u0E49\u0E21\u0E40\u0E2B\u0E25\u0E27` : void 0),
|
|
41
|
+
onError
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
30
45
|
function sendMailLater(message, opts = {}) {
|
|
31
46
|
runLater(async () => {
|
|
32
47
|
const r = await sendMailSafe(message);
|
|
@@ -36,5 +51,6 @@ function sendMailLater(message, opts = {}) {
|
|
|
36
51
|
export {
|
|
37
52
|
LATER_TIMEOUT_MS,
|
|
38
53
|
notifyLater,
|
|
54
|
+
notifyTopicLater,
|
|
39
55
|
sendMailLater
|
|
40
56
|
};
|
|
@@ -217,7 +217,18 @@ interface NotifyInput {
|
|
|
217
217
|
*/
|
|
218
218
|
idempotencyKey?: string;
|
|
219
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* (0.7.0) ส่งเข้า "topic" ของแอป — กลุ่ม/ห้อง LINE หรือกลุ่ม Telegram ที่ admin ผูกไว้ที่หน้า Topics ของ Pi Notify
|
|
222
|
+
* แอปไม่ต้องรู้ group id: เชิญ OA/bot เข้ากลุ่ม → hub เห็นกลุ่ม → admin ผูกกับ topic
|
|
223
|
+
*/
|
|
224
|
+
interface NotifyTopicInput extends Omit<NotifyInput, "to" | "channels" | "prefer"> {
|
|
225
|
+
/** ชื่อ topic ของแอปนี้ (a-z 0-9 . _ -) เช่น "po-overdue" — ไม่พบ = MailError invalid (404) */
|
|
226
|
+
topic: string;
|
|
227
|
+
/** ไม่ระบุ = ทุกปลายทางของ topic · ระบุ = เฉพาะแพลตฟอร์มนี้ */
|
|
228
|
+
channels?: ("line" | "telegram")[];
|
|
229
|
+
}
|
|
220
230
|
interface NotifyResultItem {
|
|
231
|
+
/** อีเมลผู้รับ หรือ "topic:<key>" เมื่อส่งด้วย notifyTopic() */
|
|
221
232
|
to: string;
|
|
222
233
|
channel: NotifyChannel;
|
|
223
234
|
/** `queued` (0.6.0) = hub รับไว้ส่งซ้ำเอง — เกิดเฉพาะเมื่อแอปส่ง `deliverWithin` */
|
|
@@ -229,12 +240,15 @@ interface NotifyResultItem {
|
|
|
229
240
|
/**
|
|
230
241
|
* app_daily_cap = แอปส่งอีเมลแทนแชทครบเพดานรายวันที่ตั้งใน Pi Notify แล้ว
|
|
231
242
|
* no_push_provider = hub ยังไม่ได้ตั้ง provider web push
|
|
243
|
+
* no_target (0.7.0) = topic ถูกปิด หรือยังไม่มีกลุ่มที่ใช้ได้ผูกอยู่
|
|
232
244
|
*/
|
|
233
|
-
reason?: "no_link" | "channel_not_linked" | "no_chat_provider" | "no_push_provider" | "app_daily_cap";
|
|
245
|
+
reason?: "no_link" | "channel_not_linked" | "no_chat_provider" | "no_push_provider" | "app_daily_cap" | "no_target";
|
|
234
246
|
/** (status failed) คำขอหมดงบเวลาก่อนได้เริ่มส่งรายการนี้ — ส่งซ้ำเฉพาะรายการนี้ได้ */
|
|
235
247
|
deferred?: boolean;
|
|
236
248
|
/** (0.6.0) id ของรายการในคิวส่งซ้ำของ hub — มีเมื่อ status เป็น queued */
|
|
237
249
|
deliveryId?: string;
|
|
250
|
+
/** (0.7.0) notifyTopic(): ชื่อกลุ่ม/ปลายทางของรายการนี้ */
|
|
251
|
+
target?: string;
|
|
238
252
|
}
|
|
239
253
|
interface NotifyResult {
|
|
240
254
|
results: NotifyResultItem[];
|
|
@@ -296,16 +310,25 @@ interface RecipientInfo {
|
|
|
296
310
|
/** มี PI_NOTIFY_KEY (ใน opts หรือ env) หรือไม่ — ให้ helper แบบ safe/later ใช้ตัดสินว่า "ปิดอยู่" */
|
|
297
311
|
declare function hasNotifyKey(opts?: NotifyOptions): boolean;
|
|
298
312
|
/**
|
|
299
|
-
* แจ้งเตือนผู้รับผ่าน Pi Notify hub — Telegram
|
|
313
|
+
* แจ้งเตือนผู้รับผ่าน Pi Notify hub — แชทที่ผู้รับผูกไว้ (Telegram / LINE / web push) หรืออีเมล
|
|
300
314
|
* ใช้ env เดียวกับ sendMail(): PI_NOTIFY_KEY (+ PI_NOTIFY_URL)
|
|
301
315
|
* โยน MailError เมื่อ hub ตอบ non-2xx (502 = ไม่มีช่องทางไหนส่งสำเร็จเลย; results อยู่ใน err.cause)
|
|
302
316
|
*/
|
|
303
317
|
declare function notify(input: NotifyInput, opts?: NotifyOptions): Promise<NotifyResult>;
|
|
318
|
+
/**
|
|
319
|
+
* (0.7.0) ส่งเข้า topic ของแอป — hub กระจายไปทุกกลุ่ม LINE/Telegram ที่ admin ผูกไว้ (ผลรายกลุ่มมี `target`)
|
|
320
|
+
* topic ไม่พบ → MailError invalid (404) · topic ปิด/ยังไม่ผูกกลุ่ม → ผล skipped reason "no_target" (ไม่โยน)
|
|
321
|
+
* ใช้ deliverWithin / idempotencyKey ได้เหมือน notify()
|
|
322
|
+
* @example await notifyTopic({ topic: "po-overdue", title: "PO เกินกำหนด 3 รายการ", body: "…", link });
|
|
323
|
+
*/
|
|
324
|
+
declare function notifyTopic(input: NotifyTopicInput, opts?: NotifyOptions): Promise<NotifyResult>;
|
|
304
325
|
/** ช่องทางที่ผู้รับผูกไว้ — ไว้แสดง "ผูกแล้ว/ยังไม่ผูก" ในหน้าตั้งค่าของแอป */
|
|
305
326
|
declare function getRecipient(email: Recipient, opts?: NotifyOptions): Promise<RecipientInfo>;
|
|
306
327
|
/**
|
|
307
|
-
*
|
|
308
|
-
*
|
|
328
|
+
* ขอลิงก์ผูกแชทให้ผู้รับ (หมดอายุ 10 นาที) — แอปวางเป็นปุ่ม/QR ในหน้าตั้งค่าของตัวเอง แล้วโพล getRecipient() จนเห็นช่องทางโผล่
|
|
329
|
+
* - telegram: `t.me/<bot>?start=<code>` (ผู้ใช้กด Start)
|
|
330
|
+
* - line (0.7.0): `line.me/R/oaMessage/@<oa>/?ผูก Pi Notify <code>` — เปิดแชท OA พร้อมพิมพ์ข้อความไว้ ผู้ใช้กดส่ง
|
|
331
|
+
* (ยังไม่เป็นเพื่อนกับ OA ต้องเพิ่มเพื่อนด้วยจึงจะรับ push ได้) · `code` ใช้แสดงให้พิมพ์ "ผูก Pi Notify <code>" เอง
|
|
309
332
|
* `opts.piUserId` = user id ใน Pi Auth (sub) ของผู้ใช้ — ให้ Pi Notify ลบการผูกเมื่อบัญชีถูกลบ
|
|
310
333
|
*/
|
|
311
334
|
declare function createLink(email: Recipient, channel?: Exclude<NotifyChannel, "email">, opts?: NotifyOptions & {
|
|
@@ -354,4 +377,5 @@ declare function listPushDevices(email: string, opts?: NotifyOptions): Promise<{
|
|
|
354
377
|
devices: PushDevice[];
|
|
355
378
|
}>;
|
|
356
379
|
|
|
357
|
-
export {
|
|
380
|
+
export { listPushDevices as B, notify as D, notifyTopic as F, savePushSubscription as G, toEmails as H, NOTIFY_TIMEOUT_MS as l, createLink as u, deletePushSubscription as v, getChannelStatus as w, getPushConfig as x, getRecipient as y, hasNotifyKey as z };
|
|
381
|
+
export type { Address as A, ChannelStatus as C, ErrorKind as E, Message as M, NotifyOptions as N, ParsedAddress as P, Quota as Q, Recipient as R, SendResult as S, Provider as a, ProviderStatus as b, ProviderInfo as c, Store as d, MailerEvent as e, Attempt as f, PushDevice as g, NotifyResultItem as h, NotifyInput as i, NotifyTopicInput as j, Attachment as k, NormalizedMessage as m, NotifyChannel as n, NotifyResult as o, PushSubscriptionJson as p, QuotaUsage as q, RecipientChannelInfo as r, RecipientInfo as s, SkipReason as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suphark/notify",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "ส่งอีเมล + แจ้งเตือน Telegram ผ่าน Pi Notify hub (notify.suphark.com) หรือส่งอีเมลตรงไป Resend/SMTP/Zoho/ZeptoMail ใน API เดียว พร้อม failover อัตโนมัติ",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"prepack": "pnpm run build"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"nodemailer": "^10.
|
|
39
|
+
"nodemailer": "^10.2.1"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"next": ">=15"
|
|
@@ -47,14 +47,14 @@
|
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@biomejs/biome": "^2.
|
|
50
|
+
"@biomejs/biome": "^2.6.2",
|
|
51
51
|
"@pi/tsconfig": "workspace:*",
|
|
52
52
|
"@types/node": "^24.13.4",
|
|
53
53
|
"@types/nodemailer": "^8.0.1",
|
|
54
|
-
"next": "^16.
|
|
55
|
-
"tsup": "^
|
|
54
|
+
"next": "^16.4.2",
|
|
55
|
+
"tsup": "^9.0.0",
|
|
56
56
|
"typescript": "^6.0.3",
|
|
57
|
-
"vitest": "^
|
|
57
|
+
"vitest": "^5.0.5"
|
|
58
58
|
},
|
|
59
59
|
"license": "MIT",
|
|
60
60
|
"repository": {
|