@suphark/notify 0.5.0 → 0.6.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/LICENSE +21 -21
- package/README.md +23 -0
- package/dist/{chunk-FL5ZV3S2.js → chunk-AQWFHD5T.js} +4 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -1
- package/dist/next.d.ts +1 -1
- package/dist/next.js +1 -1
- package/dist/{notify-L7JnpG0t.d.ts → notify-BM6FBQVP.d.ts} +18 -1
- package/package.json +2 -5
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Suphark
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Suphark
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -94,6 +94,24 @@ const { url, code } = await createLink(user); // รับ use
|
|
|
94
94
|
const { linked, label, manageUrl } = await getChannelStatus(user, "telegram");
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
### ให้ hub ส่งซ้ำเอง: `deliverWithin` + `idempotencyKey` (0.6.0)
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const r = await notify({
|
|
101
|
+
to: user,
|
|
102
|
+
title: "ถึงกำหนดงาน",
|
|
103
|
+
body: "…",
|
|
104
|
+
channels: ["telegram"],
|
|
105
|
+
deliverWithin: 3600, // ให้ hub พยายามส่งภายใน 1 ชม.
|
|
106
|
+
idempotencyKey: `pi-done:reminder:${user.id}:${runId}`, // cron รันซ้ำก็ไม่เตือนซ้ำ
|
|
107
|
+
});
|
|
108
|
+
// r.results[i].status: "sent" | "queued" (hub รับไว้ส่งซ้ำเอง) | "failed" | "skipped"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
- นับ `queued` เป็น "รับไว้แล้ว" — แอปไม่ต้องส่งซ้ำเอง; ดูสถานะที่หน้า Logs / คิวส่งซ้ำของ Pi Notify
|
|
112
|
+
- `failed` = ผิดถาวร / ลองครบแล้ว / เลยเวลา — แอปตัดสินใจเอง (อย่าสลับไปอีเมลถ้าผู้ใช้ไม่ได้ขอ)
|
|
113
|
+
- ใช้ `idempotencyKey` ที่ผูกกับเหตุการณ์ (ไม่ใช่สุ่มใหม่ทุกครั้ง) จึงกันซ้ำได้ — ส่งเนื้อหาเดิมด้วย key เดิมซ้ำจะได้ผลเดิมกลับมา
|
|
114
|
+
|
|
97
115
|
### เขียนให้สั้น: ส่งแบบไม่โยน / ส่งเบื้องหลัง / `to` เป็น user object (0.5.0)
|
|
98
116
|
|
|
99
117
|
`to` ของ `notify()` รับ **user object ที่มี `email`** (และ array ของมัน) ได้เลย — ไม่ต้องแกะ `.email` เอง
|
|
@@ -373,6 +391,11 @@ Pi Notify มี contract test (`apps/notify/src/test/contract/`) เก็บ b
|
|
|
373
391
|
|
|
374
392
|
## Changelog
|
|
375
393
|
|
|
394
|
+
- 0.6.0 — **hub ส่งซ้ำเอง** (เพิ่มอย่างเดียว — แอปที่ไม่ส่งตัวเลือกใหม่ได้พฤติกรรมเดิมทุกอย่าง; ต้องใช้ Pi Notify 2026-10-14+)
|
|
395
|
+
- `notify({ deliverWithin })` (วินาที 60–86400): รายการที่ค้างหรือพลาดชั่วคราวเข้าคิวถาวรของ hub แล้วส่งซ้ำตาม backoff (1 นาที → 5 นาที → 30 นาที → 2 ชม., สูงสุด 5 ครั้ง) · ผิดถาวรไม่ส่งซ้ำ · เลยเวลาแล้วไม่ส่ง · ไม่สลับช่องทาง
|
|
396
|
+
- `notify({ idempotencyKey })` → หัว `Idempotency-Key`: key+เนื้อหาเดิมได้ผลเดิม ไม่ส่งซ้ำ (จำ 7 วัน) · เนื้อหาต่างแต่ key เดิม = `invalid` (422) · คำขอเดิมยังทำอยู่ = `rate_limit` (409, `retryAfterSec` 5)
|
|
397
|
+
- `NotifyResultItem.status` เพิ่ม `"queued"` (+ `deliveryId`), `NotifyResult.queued?` / `NotifySafeResult.queued?` — `queued` นับเป็น "รับไว้แล้ว" ไม่ใช่ failed (`notifySafe().ok` เป็น true) · `status` ใหม่เกิดเฉพาะเมื่อแอปส่ง `deliverWithin` (ออกเป็น minor เพราะ union ของ `status` ที่แอปอาจ switch)
|
|
398
|
+
|
|
376
399
|
- 0.5.0 — **เขียนฝั่งแอปให้สั้นลง** (เพิ่มอย่างเดียว — ฟอร์มของ 0.4.0 ใช้ได้ทุกแบบ; Pi Notify รุ่นเก่าใช้ได้ ยกเว้น `code` ที่แกะจาก url ให้เอง)
|
|
377
400
|
- `notify({ to })` รับ user object `{ email }` และ array (กรอง null); ผู้รับเดี่ยวไม่มีอีเมล = `MailError` `invalid`
|
|
378
401
|
- `notifySafe()` / `sendMailSafe()` ไม่โยน; ไม่มี key/provider = `disabled`
|
|
@@ -1101,6 +1101,7 @@ async function hubRequest(opts, path, init) {
|
|
|
1101
1101
|
method: init.method ?? "GET",
|
|
1102
1102
|
headers: {
|
|
1103
1103
|
Authorization: `Bearer ${apiKey}`,
|
|
1104
|
+
...init.headers,
|
|
1104
1105
|
...init.body !== void 0 ? { "Content-Type": "application/json" } : {}
|
|
1105
1106
|
},
|
|
1106
1107
|
body: init.body !== void 0 ? JSON.stringify(init.body) : void 0,
|
|
@@ -1128,9 +1129,11 @@ async function hubRequest(opts, path, init) {
|
|
|
1128
1129
|
return data;
|
|
1129
1130
|
}
|
|
1130
1131
|
async function notify(input, opts = {}) {
|
|
1132
|
+
const { idempotencyKey, ...body } = input;
|
|
1131
1133
|
const data = await hubRequest(opts, "/api/v1/notify", {
|
|
1132
1134
|
method: "POST",
|
|
1133
|
-
body: { ...
|
|
1135
|
+
body: { ...body, to: toEmails(input.to) },
|
|
1136
|
+
headers: idempotencyKey ? { "Idempotency-Key": idempotencyKey } : void 0,
|
|
1134
1137
|
timeoutMs: NOTIFY_TIMEOUT_MS.notify
|
|
1135
1138
|
});
|
|
1136
1139
|
return { results: data.results, ...data.summary };
|
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 { j as Attachment, C as ChannelStatus, k as NOTIFY_TIMEOUT_MS, l as NormalizedMessage, m as NotifyChannel, n as NotifyResult, o as PushSubscriptionJson, p as QuotaUsage, R as Recipient, q as RecipientChannelInfo, r as RecipientInfo, s as SkipReason, t as createLink, u as deletePushSubscription, v as getChannelStatus, w as getPushConfig, x as getRecipient, y as hasNotifyKey, z as listPushDevices, B as notify, D as savePushSubscription, F as toEmails } from './notify-
|
|
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-BM6FBQVP.js';
|
|
2
|
+
export { j as Attachment, C as ChannelStatus, k as NOTIFY_TIMEOUT_MS, l as NormalizedMessage, m as NotifyChannel, n as NotifyResult, o as PushSubscriptionJson, p as QuotaUsage, R as Recipient, q as RecipientChannelInfo, r as RecipientInfo, s as SkipReason, t as createLink, u as deletePushSubscription, v as getChannelStatus, w as getPushConfig, x as getRecipient, y as hasNotifyKey, z as listPushDevices, B as notify, D as savePushSubscription, F as toEmails } from './notify-BM6FBQVP.js';
|
|
3
3
|
|
|
4
4
|
/** แปลง "ชื่อ <a@b.com>" / "a@b.com" / { name, email } → { name, email } */
|
|
5
5
|
declare function parseAddress(input: Address): ParsedAddress;
|
|
@@ -403,6 +403,8 @@ interface NotifySafeResult {
|
|
|
403
403
|
failed: number;
|
|
404
404
|
skipped: number;
|
|
405
405
|
deferred?: number;
|
|
406
|
+
/** (0.6.0) hub รับไว้ส่งซ้ำเอง — ไม่นับเป็นความล้มเหลว */
|
|
407
|
+
queued?: number;
|
|
406
408
|
/** ผลรายผู้รับ/ช่องทาง (มีแม้ Pi Notify ตอบ 502 — ดึงจาก body) */
|
|
407
409
|
results: NotifyResultItem[];
|
|
408
410
|
/** ข้อความ error (ไม่มีอีเมลผู้รับ/เนื้อหา) */
|
package/dist/index.js
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
sendMail,
|
|
38
38
|
sendMailSafe,
|
|
39
39
|
toEmails
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-AQWFHD5T.js";
|
|
41
41
|
|
|
42
42
|
// src/format.ts
|
|
43
43
|
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;
|
package/dist/next.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { N as NotifyOptions, i as NotifyInput, M as Message } from './notify-
|
|
1
|
+
import { N as NotifyOptions, i as NotifyInput, M as Message } from './notify-BM6FBQVP.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* ส่งหลังตอบ response แล้ว (`after()` ของ Next.js) — entry แยก `@suphark/notify/next` เพราะ core ของ SDK
|
package/dist/next.js
CHANGED
|
@@ -204,11 +204,24 @@ interface NotifyInput {
|
|
|
204
204
|
channels?: NotifyChannel[];
|
|
205
205
|
/** ชื่อ provider ฝั่ง hub ที่อยากให้ลองก่อน */
|
|
206
206
|
prefer?: string;
|
|
207
|
+
/**
|
|
208
|
+
* (0.6.0) วินาที 60–86400 — ให้ hub รับผิดชอบส่งให้ถึงภายในเวลานี้: รายการที่ค้างหรือพลาดชั่วคราว (network/5xx/429)
|
|
209
|
+
* hub เก็บในคิวแล้วส่งซ้ำเองตาม backoff (ผลเป็น `status: "queued"` นับเป็น "รับไว้แล้ว") · ผิดถาวร (ผู้รับบล็อก ฯลฯ) ไม่ส่งซ้ำ
|
|
210
|
+
* เลยเวลาแล้วไม่ส่ง · ไม่สลับช่องทาง (ขอ telegram จะไม่ตกไปอีเมล) · ไม่ระบุ = พฤติกรรมเดิม (แอปส่งซ้ำเอง)
|
|
211
|
+
*/
|
|
212
|
+
deliverWithin?: number;
|
|
213
|
+
/**
|
|
214
|
+
* (0.6.0) กันส่งซ้ำ — ส่งเป็นหัว `Idempotency-Key` (1–200 ตัวอักษร ต่อแอป) เรียกซ้ำด้วย key และเนื้อหาเดิม
|
|
215
|
+
* ได้ผลเดิมโดย hub ไม่ส่งอะไรอีก (เนื้อหาต่างแต่ key เดิม = MailError `invalid` 422) · จำไว้ 7 วัน
|
|
216
|
+
* ควรเป็นค่าที่ผูกกับเหตุการณ์ เช่น `pi-done:reminder:<userId>:<runId>`
|
|
217
|
+
*/
|
|
218
|
+
idempotencyKey?: string;
|
|
207
219
|
}
|
|
208
220
|
interface NotifyResultItem {
|
|
209
221
|
to: string;
|
|
210
222
|
channel: NotifyChannel;
|
|
211
|
-
|
|
223
|
+
/** `queued` (0.6.0) = hub รับไว้ส่งซ้ำเอง — เกิดเฉพาะเมื่อแอปส่ง `deliverWithin` */
|
|
224
|
+
status: "sent" | "failed" | "skipped" | "queued";
|
|
212
225
|
provider?: string;
|
|
213
226
|
id?: string;
|
|
214
227
|
failedOver?: boolean;
|
|
@@ -220,6 +233,8 @@ interface NotifyResultItem {
|
|
|
220
233
|
reason?: "no_link" | "channel_not_linked" | "no_chat_provider" | "no_push_provider" | "app_daily_cap";
|
|
221
234
|
/** (status failed) คำขอหมดงบเวลาก่อนได้เริ่มส่งรายการนี้ — ส่งซ้ำเฉพาะรายการนี้ได้ */
|
|
222
235
|
deferred?: boolean;
|
|
236
|
+
/** (0.6.0) id ของรายการในคิวส่งซ้ำของ hub — มีเมื่อ status เป็น queued */
|
|
237
|
+
deliveryId?: string;
|
|
223
238
|
}
|
|
224
239
|
interface NotifyResult {
|
|
225
240
|
results: NotifyResultItem[];
|
|
@@ -228,6 +243,8 @@ interface NotifyResult {
|
|
|
228
243
|
skipped: number;
|
|
229
244
|
/** จำนวนที่ยังไม่ได้ส่งเพราะหมดงบเวลา (นับซ้อนอยู่ใน failed; Pi Notify รุ่นเก่าไม่ส่งมา) */
|
|
230
245
|
deferred?: number;
|
|
246
|
+
/** (0.6.0) จำนวนที่ hub รับไว้ส่งซ้ำเอง (ไม่นับใน failed) — Pi Notify รุ่นเก่าไม่ส่งมา */
|
|
247
|
+
queued?: number;
|
|
231
248
|
}
|
|
232
249
|
interface NotifyOptions {
|
|
233
250
|
/** ค่าเริ่มต้น PI_NOTIFY_KEY */
|
package/package.json
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suphark/notify",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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 อัตโนมัติ",
|
|
7
|
-
"main": "./dist/index.js",
|
|
8
|
-
"module": "./dist/index.js",
|
|
9
|
-
"types": "./dist/index.d.ts",
|
|
10
7
|
"exports": {
|
|
11
8
|
".": {
|
|
12
9
|
"types": "./dist/index.d.ts",
|
|
@@ -51,7 +48,7 @@
|
|
|
51
48
|
},
|
|
52
49
|
"devDependencies": {
|
|
53
50
|
"@biomejs/biome": "^2.5.15",
|
|
54
|
-
"@pi/
|
|
51
|
+
"@pi/tsconfig": "workspace:*",
|
|
55
52
|
"@types/node": "^24.13.4",
|
|
56
53
|
"@types/nodemailer": "^8.0.1",
|
|
57
54
|
"next": "^16.3.8",
|