@suphark/notify 0.4.0 → 0.5.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 +132 -8
- package/dist/chunk-FL5ZV3S2.js +1290 -0
- package/dist/index.d.ts +114 -298
- package/dist/index.js +154 -1127
- package/dist/next.d.ts +28 -0
- package/dist/next.js +40 -0
- package/dist/notify-L7JnpG0t.d.ts +340 -0
- package/package.json +18 -1
package/dist/next.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { N as NotifyOptions, i as NotifyInput, M as Message } from './notify-L7JnpG0t.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ส่งหลังตอบ response แล้ว (`after()` ของ Next.js) — entry แยก `@suphark/notify/next` เพราะ core ของ SDK
|
|
5
|
+
* ถูกใช้นอก Next ด้วย (hub, auth, สคริปต์) จึงต้องไม่ import `next/*`
|
|
6
|
+
*/
|
|
7
|
+
interface LaterOptions extends NotifyOptions {
|
|
8
|
+
/** เรียกเมื่อส่งไม่สำเร็จ (ไม่เรียกเมื่อ disabled) — ไว้ส่งเข้า logger/monitor ของแอป */
|
|
9
|
+
onError?: (error: string) => void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* งานใน after() นับรวมใน maxDuration ของ function — ค่าเริ่มต้นของ notify() 65 วิจะถูกแพลตฟอร์มตัดก่อนมี log
|
|
13
|
+
* จึงใช้ 20 วิ (เท่ากับที่ Pi Auth ใช้); ตั้ง timeoutMs เองได้
|
|
14
|
+
*/
|
|
15
|
+
declare const LATER_TIMEOUT_MS = 20000;
|
|
16
|
+
/**
|
|
17
|
+
* notify() เบื้องหลัง ไม่บล็อกหน้าเว็บและไม่ทำให้ Server Action/Route พัง — คืน void
|
|
18
|
+
* ใช้เมื่อการแจ้งเตือนเป็นผลพลอยได้ (best-effort); ถ้าต้องรู้ผลหรือให้ cron บันทึกสถานะ → await notify()
|
|
19
|
+
* @example notifyLater({ to: user, title: "บันทึกแล้ว", body: "ระบบอัปเดตงานแล้ว" });
|
|
20
|
+
*/
|
|
21
|
+
declare function notifyLater(input: NotifyInput, opts?: LaterOptions): void;
|
|
22
|
+
/**
|
|
23
|
+
* sendMail() เบื้องหลัง (ดู notifyLater) — คืน void
|
|
24
|
+
* @example sendMailLater({ to: user.email, subject: "ยินดีต้อนรับ", html: "<p>…</p>" });
|
|
25
|
+
*/
|
|
26
|
+
declare function sendMailLater(message: Message, opts?: Pick<LaterOptions, "onError">): void;
|
|
27
|
+
|
|
28
|
+
export { LATER_TIMEOUT_MS, type LaterOptions, notifyLater, sendMailLater };
|
package/dist/next.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
notifySafe,
|
|
3
|
+
sendMailSafe
|
|
4
|
+
} from "./chunk-FL5ZV3S2.js";
|
|
5
|
+
|
|
6
|
+
// src/next.ts
|
|
7
|
+
import { after } from "next/server";
|
|
8
|
+
var LATER_TIMEOUT_MS = 2e4;
|
|
9
|
+
function report(what, error, onError) {
|
|
10
|
+
if (!error) return;
|
|
11
|
+
console.error(`[pi-notify] ${what} \u0E2A\u0E48\u0E07\u0E44\u0E21\u0E48\u0E2A\u0E33\u0E40\u0E23\u0E47\u0E08: ${error}`);
|
|
12
|
+
onError?.(error);
|
|
13
|
+
}
|
|
14
|
+
function runLater(task) {
|
|
15
|
+
const run = () => task().catch(() => {
|
|
16
|
+
});
|
|
17
|
+
try {
|
|
18
|
+
after(run);
|
|
19
|
+
} catch {
|
|
20
|
+
void run();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function notifyLater(input, opts = {}) {
|
|
24
|
+
const { onError, ...rest } = opts;
|
|
25
|
+
runLater(async () => {
|
|
26
|
+
const r = await notifySafe(input, { timeoutMs: LATER_TIMEOUT_MS, ...rest });
|
|
27
|
+
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
|
+
}
|
|
30
|
+
function sendMailLater(message, opts = {}) {
|
|
31
|
+
runLater(async () => {
|
|
32
|
+
const r = await sendMailSafe(message);
|
|
33
|
+
report("sendMail()", r.error, opts.onError);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
LATER_TIMEOUT_MS,
|
|
38
|
+
notifyLater,
|
|
39
|
+
sendMailLater
|
|
40
|
+
};
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/** ที่อยู่อีเมล: "a@b.com" | "ชื่อ <a@b.com>" | { name, email } */
|
|
2
|
+
type Address = string | {
|
|
3
|
+
name?: string;
|
|
4
|
+
email: string;
|
|
5
|
+
};
|
|
6
|
+
interface Attachment {
|
|
7
|
+
filename: string;
|
|
8
|
+
/** Buffer หรือ string (utf8) — ถ้าเป็น base64 อยู่แล้วให้ระบุ encoding: "base64" */
|
|
9
|
+
content: Buffer | string;
|
|
10
|
+
contentType?: string;
|
|
11
|
+
encoding?: "base64" | "utf8";
|
|
12
|
+
}
|
|
13
|
+
/** ข้อความที่แอปส่งเข้ามา (ไม่ผูกกับ provider ใด ๆ) */
|
|
14
|
+
interface Message {
|
|
15
|
+
to: Address | Address[];
|
|
16
|
+
subject: string;
|
|
17
|
+
html?: string;
|
|
18
|
+
text?: string;
|
|
19
|
+
/** override from ของ provider/ค่าเริ่มต้น */
|
|
20
|
+
from?: Address;
|
|
21
|
+
cc?: Address | Address[];
|
|
22
|
+
bcc?: Address | Address[];
|
|
23
|
+
replyTo?: Address | Address[];
|
|
24
|
+
attachments?: Attachment[];
|
|
25
|
+
headers?: Record<string, string>;
|
|
26
|
+
/** metadata แนบไปกับอีเมล (provider ที่รองรับ เช่น Resend tags) */
|
|
27
|
+
tags?: Record<string, string>;
|
|
28
|
+
/** บังคับใช้ provider ตัวนี้ตัวเดียว (ไม่ failover) — โหมด local เท่านั้น */
|
|
29
|
+
provider?: string;
|
|
30
|
+
/** อยากให้ลองตัวนี้ก่อน (ยัง failover ตามปกติถ้าไม่ได้) — ส่งต่อไป hub ด้วย */
|
|
31
|
+
prefer?: string;
|
|
32
|
+
}
|
|
33
|
+
/** ข้อมูลตัวเลือกที่แอปควรรู้ — แค่ชื่อ/ประเภท/พร้อมใช้ไหม */
|
|
34
|
+
interface ProviderInfo {
|
|
35
|
+
name: string;
|
|
36
|
+
type: string;
|
|
37
|
+
available: boolean;
|
|
38
|
+
/** ชื่อ provider ที่เป็นทางผ่าน (เช่น "hub") เมื่อรายการนี้อยู่หลัง hub */
|
|
39
|
+
via?: string;
|
|
40
|
+
}
|
|
41
|
+
interface ParsedAddress {
|
|
42
|
+
name?: string;
|
|
43
|
+
email: string;
|
|
44
|
+
}
|
|
45
|
+
/** ข้อความหลัง normalize แล้ว — ทุก provider ได้รับรูปแบบนี้ */
|
|
46
|
+
interface NormalizedMessage {
|
|
47
|
+
/** ไม่มีได้เฉพาะ provider ที่ประกาศ optionalFrom (hub เติมให้เอง) */
|
|
48
|
+
from?: ParsedAddress;
|
|
49
|
+
to: ParsedAddress[];
|
|
50
|
+
cc: ParsedAddress[];
|
|
51
|
+
bcc: ParsedAddress[];
|
|
52
|
+
replyTo: ParsedAddress[];
|
|
53
|
+
subject: string;
|
|
54
|
+
html?: string;
|
|
55
|
+
text?: string;
|
|
56
|
+
attachments: {
|
|
57
|
+
filename: string;
|
|
58
|
+
base64: string;
|
|
59
|
+
contentType?: string;
|
|
60
|
+
}[];
|
|
61
|
+
headers: Record<string, string>;
|
|
62
|
+
tags: Record<string, string>;
|
|
63
|
+
prefer?: string;
|
|
64
|
+
/**
|
|
65
|
+
* ยกเลิกเมื่อ mailer หมดเวลา (timeoutMs) — provider ที่ใช้ fetch ควรส่งต่อเข้า fetch
|
|
66
|
+
* เพื่อไม่ให้คำขอค้างต่อหลัง mailer ไป failover แล้ว (ไม่ส่งมา = provider เขียนเองรุ่นเก่า ใช้ได้ตามเดิม)
|
|
67
|
+
*/
|
|
68
|
+
signal?: AbortSignal;
|
|
69
|
+
}
|
|
70
|
+
/** ประเภทความผิดพลาด — ใช้ตัดสินใจว่าจะพัก provider นานแค่ไหน */
|
|
71
|
+
type ErrorKind = "quota" | "rate_limit" | "auth" | "network" | "server" | "invalid" | "unknown";
|
|
72
|
+
type SkipReason = "cooldown" | "quota" | "disabled";
|
|
73
|
+
interface Attempt {
|
|
74
|
+
provider: string;
|
|
75
|
+
ok: boolean;
|
|
76
|
+
/** ข้ามเพราะ circuit เปิด/โควต้าเต็ม (ไม่ได้ลองส่งจริง) */
|
|
77
|
+
skipped?: SkipReason;
|
|
78
|
+
error?: string;
|
|
79
|
+
kind?: ErrorKind;
|
|
80
|
+
ms: number;
|
|
81
|
+
}
|
|
82
|
+
interface SendResult {
|
|
83
|
+
/** id จาก provider (ถ้ามี) */
|
|
84
|
+
id?: string;
|
|
85
|
+
/** ชื่อ provider ที่ส่งสำเร็จ */
|
|
86
|
+
provider: string;
|
|
87
|
+
/** true ถ้าตัวแรกที่ควรใช้ล้ม/ถูกข้าม แล้วไปสำเร็จที่ตัวสำรอง */
|
|
88
|
+
failedOver: boolean;
|
|
89
|
+
attempts: Attempt[];
|
|
90
|
+
}
|
|
91
|
+
interface Quota {
|
|
92
|
+
perHour?: number;
|
|
93
|
+
perDay?: number;
|
|
94
|
+
perMonth?: number;
|
|
95
|
+
}
|
|
96
|
+
/** สิ่งที่ provider ต้อง implement — เท่านี้พอ ที่เหลือ mailer จัดการ */
|
|
97
|
+
interface Provider {
|
|
98
|
+
readonly name: string;
|
|
99
|
+
readonly type: string;
|
|
100
|
+
/** from เริ่มต้นของ provider นี้ (ชนะ from ของ mailer) */
|
|
101
|
+
readonly from?: ParsedAddress;
|
|
102
|
+
/** true = ส่งได้แม้ไม่มี from (provider ปลายทางเติมค่าเริ่มต้นให้ เช่น hub) */
|
|
103
|
+
readonly optionalFrom?: boolean;
|
|
104
|
+
send(msg: NormalizedMessage): Promise<{
|
|
105
|
+
id?: string;
|
|
106
|
+
}>;
|
|
107
|
+
/** ทดสอบการเชื่อมต่อ/credential (ไม่ส่งอีเมล) */
|
|
108
|
+
verify?(): Promise<void>;
|
|
109
|
+
/** provider ตัวนี้เป็นทางผ่านไปยังตัวอื่น (hub) — คืนรายการข้างหลัง */
|
|
110
|
+
listProviders?(): Promise<ProviderInfo[]>;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* state ที่ mailer ต้องจำข้ามคำขอ (นับโควต้า, cooldown)
|
|
114
|
+
* default เก็บใน memory ของ process; ต่อ Redis ได้เพื่อแชร์ข้ามแอป/instance (ดู createRedisStore)
|
|
115
|
+
*/
|
|
116
|
+
interface Store {
|
|
117
|
+
get(key: string): Promise<string | null>;
|
|
118
|
+
/** ตั้งค่า พร้อมอายุ (วินาที) */
|
|
119
|
+
set(key: string, value: string, ttlSec: number): Promise<void>;
|
|
120
|
+
/** เพิ่มค่าทีละ 1 (สร้างเป็น 1 ถ้ายังไม่มี) พร้อมอายุ (วินาที) คืนค่าใหม่ */
|
|
121
|
+
incr(key: string, ttlSec: number): Promise<number>;
|
|
122
|
+
/** ลดค่าทีละ 1 (ไม่ต่ำกว่า 0) — คืนโควต้าที่จองไว้เมื่อส่งไม่สำเร็จ; store ที่ไม่ implement จะนับความล้มเหลวเป็นโควต้าด้วย */
|
|
123
|
+
decr?(key: string): Promise<void>;
|
|
124
|
+
del(key: string): Promise<void>;
|
|
125
|
+
}
|
|
126
|
+
type MailerEvent = {
|
|
127
|
+
type: "sent";
|
|
128
|
+
provider: string;
|
|
129
|
+
id?: string;
|
|
130
|
+
ms: number;
|
|
131
|
+
failedOver: boolean;
|
|
132
|
+
} | {
|
|
133
|
+
type: "failed";
|
|
134
|
+
provider: string;
|
|
135
|
+
kind: ErrorKind;
|
|
136
|
+
error: string;
|
|
137
|
+
ms: number;
|
|
138
|
+
} | {
|
|
139
|
+
type: "skipped";
|
|
140
|
+
provider: string;
|
|
141
|
+
reason: SkipReason;
|
|
142
|
+
} | {
|
|
143
|
+
type: "cooldown";
|
|
144
|
+
provider: string;
|
|
145
|
+
kind: ErrorKind;
|
|
146
|
+
untilMs: number;
|
|
147
|
+
failures: number;
|
|
148
|
+
} | {
|
|
149
|
+
type: "recovered";
|
|
150
|
+
provider: string;
|
|
151
|
+
} | {
|
|
152
|
+
type: "exhausted";
|
|
153
|
+
attempts: Attempt[];
|
|
154
|
+
};
|
|
155
|
+
interface QuotaUsage {
|
|
156
|
+
window: "hour" | "day" | "month";
|
|
157
|
+
used: number;
|
|
158
|
+
limit: number;
|
|
159
|
+
}
|
|
160
|
+
interface ProviderStatus {
|
|
161
|
+
name: string;
|
|
162
|
+
type: string;
|
|
163
|
+
/** พร้อมใช้ (ไม่อยู่ใน cooldown และโควต้ายังไม่เต็ม) */
|
|
164
|
+
available: boolean;
|
|
165
|
+
cooldownUntil?: number;
|
|
166
|
+
cooldownKind?: ErrorKind;
|
|
167
|
+
failures: number;
|
|
168
|
+
quota: QuotaUsage[];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** ผู้รับที่แอปมีอยู่ในมือ: อีเมล หรือ user object (session/DB) ที่มี `email` — null/undefined ได้เพื่อไม่ต้องเช็กก่อนเรียก */
|
|
172
|
+
type Recipient = string | {
|
|
173
|
+
email?: string | null;
|
|
174
|
+
} | null | undefined;
|
|
175
|
+
/**
|
|
176
|
+
* แปลงผู้รับหลายรูปแบบเป็นอีเมลที่ Pi Notify รับ — คงรูปเดิม: ผู้รับเดี่ยว → string, array → array (ไม่ซ้ำ)
|
|
177
|
+
* - ผู้รับเดี่ยวที่ไม่มีอีเมล → โยน MailError "invalid": ผู้ใช้ที่ไม่มีอีเมลเป็น bug ของแอป อย่าให้เงียบ
|
|
178
|
+
* - ใน array กรอง null/undefined/ไม่มีอีเมลทิ้งได้ แต่ถ้าไม่เหลือสักคนก็ "invalid" เช่นกัน
|
|
179
|
+
*/
|
|
180
|
+
declare function toEmails(to: Recipient | Recipient[]): string | string[];
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* ช่องทางแจ้งเตือนที่ hub รองรับ — ผู้รับผูกแชทเองที่ hub (/me) หรือผ่านลิงก์ที่แอปขอจาก hub
|
|
184
|
+
* `push` = web push ของเบราว์เซอร์ ผูกต่อแอป (service worker อยู่ที่โดเมนของแอปเอง)
|
|
185
|
+
*/
|
|
186
|
+
type NotifyChannel = "telegram" | "line" | "push" | "email";
|
|
187
|
+
interface NotifyInput {
|
|
188
|
+
/**
|
|
189
|
+
* ผู้รับ (อีเมลตรงกับบัญชี Pi Auth) — ระบุได้สูงสุด 50 คน; ส่ง user object ที่มี `email` ได้เลย
|
|
190
|
+
* ผู้รับเดี่ยวที่ไม่มีอีเมลโยน MailError "invalid"; ใน array กรอง null/undefined ทิ้งให้
|
|
191
|
+
*/
|
|
192
|
+
to: Recipient | Recipient[];
|
|
193
|
+
title: string;
|
|
194
|
+
/** ข้อความล้วน หรือ HTML จำกัดแท็ก (b/i/u/s/a/code/pre) — ระบุ format เมื่อไม่อยากให้ hub เดา */
|
|
195
|
+
body: string;
|
|
196
|
+
/** text = แสดงตามตัวอักษร (hub escape ให้), html = ตีความแท็ก; ไม่ระบุ = hub เดาจากเนื้อหา */
|
|
197
|
+
format?: "text" | "html";
|
|
198
|
+
/** ลิงก์ท้ายข้อความ (Telegram = ปุ่ม "เปิดลิงก์", อีเมล = ปุ่ม) */
|
|
199
|
+
link?: string;
|
|
200
|
+
/**
|
|
201
|
+
* ไม่ระบุ = ส่งทุกแชทที่ผู้รับผูกไว้; ไม่มีแชท → อีเมล
|
|
202
|
+
* ระบุ = เฉพาะที่ขอ; แชทที่ขอแต่ไม่ได้ผูก (และไม่ได้ขอ email) → skipped
|
|
203
|
+
*/
|
|
204
|
+
channels?: NotifyChannel[];
|
|
205
|
+
/** ชื่อ provider ฝั่ง hub ที่อยากให้ลองก่อน */
|
|
206
|
+
prefer?: string;
|
|
207
|
+
}
|
|
208
|
+
interface NotifyResultItem {
|
|
209
|
+
to: string;
|
|
210
|
+
channel: NotifyChannel;
|
|
211
|
+
status: "sent" | "failed" | "skipped";
|
|
212
|
+
provider?: string;
|
|
213
|
+
id?: string;
|
|
214
|
+
failedOver?: boolean;
|
|
215
|
+
error?: string;
|
|
216
|
+
/**
|
|
217
|
+
* app_daily_cap = แอปส่งอีเมลแทนแชทครบเพดานรายวันที่ตั้งใน Pi Notify แล้ว
|
|
218
|
+
* no_push_provider = hub ยังไม่ได้ตั้ง provider web push
|
|
219
|
+
*/
|
|
220
|
+
reason?: "no_link" | "channel_not_linked" | "no_chat_provider" | "no_push_provider" | "app_daily_cap";
|
|
221
|
+
/** (status failed) คำขอหมดงบเวลาก่อนได้เริ่มส่งรายการนี้ — ส่งซ้ำเฉพาะรายการนี้ได้ */
|
|
222
|
+
deferred?: boolean;
|
|
223
|
+
}
|
|
224
|
+
interface NotifyResult {
|
|
225
|
+
results: NotifyResultItem[];
|
|
226
|
+
sent: number;
|
|
227
|
+
failed: number;
|
|
228
|
+
skipped: number;
|
|
229
|
+
/** จำนวนที่ยังไม่ได้ส่งเพราะหมดงบเวลา (นับซ้อนอยู่ใน failed; Pi Notify รุ่นเก่าไม่ส่งมา) */
|
|
230
|
+
deferred?: number;
|
|
231
|
+
}
|
|
232
|
+
interface NotifyOptions {
|
|
233
|
+
/** ค่าเริ่มต้น PI_NOTIFY_KEY */
|
|
234
|
+
apiKey?: string;
|
|
235
|
+
/** ค่าเริ่มต้น PI_NOTIFY_URL หรือ https://notify.suphark.com */
|
|
236
|
+
url?: string;
|
|
237
|
+
fetch?: typeof fetch;
|
|
238
|
+
env?: Record<string, string | undefined>;
|
|
239
|
+
/**
|
|
240
|
+
* เวลารอ Pi Notify สูงสุด (ms) หมดแล้วโยน MailError kind "network"
|
|
241
|
+
* ค่าเริ่มต้น notify() 65000 (ยาวกว่า maxDuration 60 วิของ /api/v1/notify เล็กน้อย) · getRecipient()/createLink() 10000
|
|
242
|
+
*/
|
|
243
|
+
timeoutMs?: number;
|
|
244
|
+
}
|
|
245
|
+
/** เวลารอเริ่มต้นของแต่ละคำสั่ง (ms) */
|
|
246
|
+
declare const NOTIFY_TIMEOUT_MS: {
|
|
247
|
+
readonly notify: 65000;
|
|
248
|
+
readonly recipient: 10000;
|
|
249
|
+
};
|
|
250
|
+
/** subscription จากเบราว์เซอร์ (`PushSubscription.toJSON()`) */
|
|
251
|
+
interface PushSubscriptionJson {
|
|
252
|
+
endpoint: string;
|
|
253
|
+
keys: {
|
|
254
|
+
p256dh: string;
|
|
255
|
+
auth: string;
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
interface PushDevice {
|
|
259
|
+
id: string;
|
|
260
|
+
label: string | null;
|
|
261
|
+
enabled: boolean;
|
|
262
|
+
/** ท่อนท้ายของ endpoint ไว้แยกเครื่อง (ไม่คืนค่าเต็ม) */
|
|
263
|
+
endpointTail: string;
|
|
264
|
+
}
|
|
265
|
+
/** ช่องทางที่ผู้รับผูกไว้ (จาก GET /api/v1/recipients/:email) — ไม่มี chat id */
|
|
266
|
+
interface RecipientChannelInfo {
|
|
267
|
+
type: string;
|
|
268
|
+
label: string | null;
|
|
269
|
+
enabled: boolean;
|
|
270
|
+
disabledReason: string | null;
|
|
271
|
+
linkedAt: string;
|
|
272
|
+
}
|
|
273
|
+
interface RecipientInfo {
|
|
274
|
+
email: string;
|
|
275
|
+
channels: RecipientChannelInfo[];
|
|
276
|
+
/** หน้า hub ให้ผู้รับผูกเอง */
|
|
277
|
+
linkUrl: string;
|
|
278
|
+
}
|
|
279
|
+
/** มี PI_NOTIFY_KEY (ใน opts หรือ env) หรือไม่ — ให้ helper แบบ safe/later ใช้ตัดสินว่า "ปิดอยู่" */
|
|
280
|
+
declare function hasNotifyKey(opts?: NotifyOptions): boolean;
|
|
281
|
+
/**
|
|
282
|
+
* แจ้งเตือนผู้รับผ่าน Pi Notify hub — Telegram (ที่ผู้รับผูกไว้) หรืออีเมล
|
|
283
|
+
* ใช้ env เดียวกับ sendMail(): PI_NOTIFY_KEY (+ PI_NOTIFY_URL)
|
|
284
|
+
* โยน MailError เมื่อ hub ตอบ non-2xx (502 = ไม่มีช่องทางไหนส่งสำเร็จเลย; results อยู่ใน err.cause)
|
|
285
|
+
*/
|
|
286
|
+
declare function notify(input: NotifyInput, opts?: NotifyOptions): Promise<NotifyResult>;
|
|
287
|
+
/** ช่องทางที่ผู้รับผูกไว้ — ไว้แสดง "ผูกแล้ว/ยังไม่ผูก" ในหน้าตั้งค่าของแอป */
|
|
288
|
+
declare function getRecipient(email: Recipient, opts?: NotifyOptions): Promise<RecipientInfo>;
|
|
289
|
+
/**
|
|
290
|
+
* ขอลิงก์ผูก Telegram ให้ผู้รับ (t.me/<bot>?start=<code>, หมดอายุ 10 นาที)
|
|
291
|
+
* แอปวางเป็นปุ่ม/QR ในหน้าตั้งค่าของตัวเอง แล้วโพล getRecipient() จนเห็น telegram
|
|
292
|
+
* `opts.piUserId` = user id ใน Pi Auth (sub) ของผู้ใช้ — ให้ Pi Notify ลบการผูกเมื่อบัญชีถูกลบ
|
|
293
|
+
*/
|
|
294
|
+
declare function createLink(email: Recipient, channel?: Exclude<NotifyChannel, "email">, opts?: NotifyOptions & {
|
|
295
|
+
piUserId?: string;
|
|
296
|
+
}): Promise<{
|
|
297
|
+
channel: string;
|
|
298
|
+
url: string;
|
|
299
|
+
code: string;
|
|
300
|
+
expiresAt: string;
|
|
301
|
+
}>;
|
|
302
|
+
/** สถานะการผูกช่องทางหนึ่งของผู้รับ — แทนการ find ใน `getRecipient().channels` เองในแต่ละแอป */
|
|
303
|
+
interface ChannelStatus {
|
|
304
|
+
linked: boolean;
|
|
305
|
+
label: string | null;
|
|
306
|
+
linkedAt: string | null;
|
|
307
|
+
/** หน้า Pi Notify ที่ผู้รับจัดการช่องทางเอง */
|
|
308
|
+
manageUrl: string;
|
|
309
|
+
}
|
|
310
|
+
/** ผู้รับที่ Pi Notify ยังไม่รู้จัก = channels ว่าง (`linked: false`) ไม่ใช่ error */
|
|
311
|
+
declare function getChannelStatus(email: Recipient, channel?: Exclude<NotifyChannel, "email">, opts?: NotifyOptions): Promise<ChannelStatus>;
|
|
312
|
+
/**
|
|
313
|
+
* VAPID public key ของ hub — ใช้เป็น `applicationServerKey` ตอน `pushManager.subscribe()`
|
|
314
|
+
* (ค่าสาธารณะ แคชฝั่งแอปได้) · hub ยังไม่ได้ตั้ง provider push → MailError kind "server"
|
|
315
|
+
*/
|
|
316
|
+
declare function getPushConfig(opts?: NotifyOptions): Promise<{
|
|
317
|
+
publicKey: string;
|
|
318
|
+
}>;
|
|
319
|
+
/**
|
|
320
|
+
* เก็บ subscription ของเบราว์เซอร์หนึ่งเครื่องไว้ที่ hub (ผูกกับแอปที่เรียกเสมอ)
|
|
321
|
+
* เรียกซ้ำด้วย endpoint เดิมได้ = อัปเดตกุญแจ/เปิดใช้คืน · `piUserId` ให้ hub ลบตามบัญชีที่ถูกลบได้
|
|
322
|
+
*/
|
|
323
|
+
declare function savePushSubscription(email: string, subscription: PushSubscriptionJson, opts?: NotifyOptions & {
|
|
324
|
+
label?: string;
|
|
325
|
+
piUserId?: string;
|
|
326
|
+
}): Promise<{
|
|
327
|
+
ok: true;
|
|
328
|
+
created: boolean;
|
|
329
|
+
}>;
|
|
330
|
+
/** ผู้ใช้ปิดแจ้งเตือนที่เครื่องนั้น (ลบได้เฉพาะ subscription ของแอปตัวเอง) */
|
|
331
|
+
declare function deletePushSubscription(email: string, endpoint: string, opts?: NotifyOptions): Promise<{
|
|
332
|
+
ok: true;
|
|
333
|
+
deleted: number;
|
|
334
|
+
}>;
|
|
335
|
+
/** เครื่องที่ผู้รับเปิดแจ้งเตือนไว้กับแอปนี้ — ไว้แสดงรายการในหน้าตั้งค่า */
|
|
336
|
+
declare function listPushDevices(email: string, opts?: NotifyOptions): Promise<{
|
|
337
|
+
devices: PushDevice[];
|
|
338
|
+
}>;
|
|
339
|
+
|
|
340
|
+
export { type Address as A, notify as B, type ChannelStatus as C, savePushSubscription as D, type ErrorKind as E, toEmails as F, type Message as M, type NotifyOptions as N, type ParsedAddress as P, type Quota as Q, type Recipient as R, type SendResult as S, type Provider as a, type ProviderStatus as b, type ProviderInfo as c, type Store as d, type MailerEvent as e, type Attempt as f, type PushDevice as g, type NotifyResultItem as h, type NotifyInput as i, type Attachment as j, NOTIFY_TIMEOUT_MS as k, type NormalizedMessage as l, type NotifyChannel as m, type NotifyResult as n, type PushSubscriptionJson as o, type QuotaUsage as p, type RecipientChannelInfo as q, type RecipientInfo as r, type SkipReason as s, createLink as t, deletePushSubscription as u, getChannelStatus as v, getPushConfig as w, getRecipient as x, hasNotifyKey as y, listPushDevices as z };
|
package/package.json
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suphark/notify",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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",
|
|
7
10
|
"exports": {
|
|
8
11
|
".": {
|
|
9
12
|
"types": "./dist/index.d.ts",
|
|
10
13
|
"import": "./dist/index.js",
|
|
11
14
|
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./next": {
|
|
17
|
+
"types": "./dist/next.d.ts",
|
|
18
|
+
"import": "./dist/next.js",
|
|
19
|
+
"default": "./dist/next.js"
|
|
12
20
|
}
|
|
13
21
|
},
|
|
14
22
|
"files": [
|
|
@@ -33,11 +41,20 @@
|
|
|
33
41
|
"dependencies": {
|
|
34
42
|
"nodemailer": "^10.0.14"
|
|
35
43
|
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"next": ">=15"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"next": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
36
52
|
"devDependencies": {
|
|
37
53
|
"@biomejs/biome": "^2.5.15",
|
|
38
54
|
"@pi/config": "workspace:*",
|
|
39
55
|
"@types/node": "^24.13.4",
|
|
40
56
|
"@types/nodemailer": "^8.0.1",
|
|
57
|
+
"next": "^16.3.8",
|
|
41
58
|
"tsup": "^8.5.1",
|
|
42
59
|
"typescript": "^6.0.3",
|
|
43
60
|
"vitest": "^4.1.11"
|