@suphark/notify 0.10.1 → 0.11.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.
@@ -158,6 +158,11 @@ function sampleData(variables) {
158
158
  }
159
159
  return out;
160
160
  }
161
+ function htmlToText(html) {
162
+ return toPlainText(
163
+ html.replace(/<(head|style|script|title)\b[^>]*>[\s\S]*?<\/\1\s*>/gi, "").replace(/<\/(p|div|li|h[1-6]|tr|table|ul|ol)\s*>/gi, "$&\n")
164
+ ).replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
165
+ }
161
166
  function wrapEmailLayout(body, opts = {}) {
162
167
  const footer = opts.appName ? `<p style="margin:24px 0 0;font-size:12px;color:#64748b">\u0E2A\u0E48\u0E07\u0E08\u0E32\u0E01 ${escapeHtml(opts.appName)}</p>` : "";
163
168
  return `<!doctype html>
@@ -175,7 +180,7 @@ function renderEmailTemplate(t, data, opts = {}) {
175
180
  const subject = renderTemplate(t.subject, data, { escape: false }).replace(/\s+/g, " ").trim();
176
181
  const body = renderTemplate(t.html, data, { escape: true });
177
182
  const html = /<html[\s>]/i.test(body) ? body : wrapEmailLayout(body, { ...opts, title: subject });
178
- const text = t.text?.trim() ? renderTemplate(t.text, data, { escape: false }) : toPlainText(body);
183
+ const text = t.text?.trim() ? renderTemplate(t.text, data, { escape: false }) : htmlToText(body);
179
184
  return { subject, html, text };
180
185
  }
181
186
  var BUILTIN_TEMPLATE_KEYS = [
@@ -275,6 +280,7 @@ export {
275
280
  renderTemplate,
276
281
  templateVariableNames,
277
282
  sampleData,
283
+ htmlToText,
278
284
  wrapEmailLayout,
279
285
  renderEmailTemplate,
280
286
  BUILTIN_TEMPLATE_KEYS,
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, j as NotifyTopicInput } from './notify-D-PhiDzT.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 RequestStatus, u as SkipReason, v as createLink, w as deletePushSubscription, x as getChannelStatus, y as getPushConfig, z as getRecipient, B as getRequestStatus, D as hasNotifyKey, F as listPushDevices, G as notify, H as notifyTopic, I as savePushSubscription, J as toEmails } from './notify-D-PhiDzT.js';
1
+ import { P as ParsedAddress, A as Address, a as PushDevice, N as NotifyOptions, C as ChannelStatus, b as Provider, E as ErrorKind, Q as Quota, M as Message, S as SendResult, c as ProviderStatus, d as ProviderInfo, e as Store, f as MailerEvent, g as Attempt, h as NotifyResultItem, i as NotifyInput, j as NotifyTopicInput } from './notify-DA7BsQBD.js';
2
+ export { k as Attachment, 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 RequestStatus, u as SkipReason, v as absoluteLink, w as createLink, x as deletePushSubscription, y as getChannelStatus, z as getPushConfig, B as getRecipient, D as getRequestStatus, F as hasNotifyKey, G as listPushDevices, H as notify, I as notifyTopic, J as pauseChannel, K as savePushSubscription, L as toEmails } from './notify-DA7BsQBD.js';
3
3
  import { TemplateVariable } from './template.js';
4
4
  export { BUILTIN_TEMPLATE_KEYS, BuiltinTemplateKey, TemplateError, builtinFallback, isBuiltinTemplate, renderEmailTemplate } from './template.js';
5
5
 
@@ -13,6 +13,100 @@ declare function parseAddressList(input: Address | Address[] | undefined): Parse
13
13
  /** { name, email } → "ชื่อ <a@b.com>" (สำหรับ API ที่รับ string) */
14
14
  declare function formatAddress(a: ParsedAddress): string;
15
15
 
16
+ /** ผู้ใช้ที่ล็อกอินอยู่ในแอป — **ต้องมาจาก session ฝั่ง server เท่านั้น** ห้ามรับอีเมลจาก client */
17
+ interface PushUser {
18
+ email: string;
19
+ /** user id ใน Pi Auth (sub) — ให้ Pi Notify ลบ subscription ตามบัญชีที่ถูกลบ */
20
+ id?: string;
21
+ }
22
+ /** รูปที่ `PushSubscription.toJSON()` ของเบราว์เซอร์ส่งมา (field optional ตาม DOM type) */
23
+ interface BrowserPushSubscription {
24
+ endpoint?: string;
25
+ keys?: Record<string, string | undefined>;
26
+ }
27
+ interface PushHandlersOptions extends NotifyOptions {
28
+ /** ดึงผู้ใช้จาก session ของแอป — คืน null/undefined = ยังไม่ล็อกอิน */
29
+ getUser: () => PushUser | null | undefined | Promise<PushUser | null | undefined>;
30
+ }
31
+ interface PushHandlers {
32
+ /** VAPID public key สำหรับ `pushManager.subscribe({ applicationServerKey })` */
33
+ getPublicKey(): Promise<string>;
34
+ subscribe(subscription: BrowserPushSubscription, opts?: {
35
+ label?: string;
36
+ }): Promise<{
37
+ ok: true;
38
+ created: boolean;
39
+ }>;
40
+ unsubscribe(endpoint: string): Promise<{
41
+ ok: true;
42
+ deleted: number;
43
+ }>;
44
+ listDevices(): Promise<{
45
+ devices: PushDevice[];
46
+ }>;
47
+ }
48
+ /**
49
+ * ชุดฟังก์ชันสำหรับ Server Actions ของ `<WebPushRow />` — แอปส่งแค่ `getUser`
50
+ * (ใน `"use server"` ห่อเป็น `export async function` ทีละตัว; ห้าม import SDK ใน Client Component)
51
+ */
52
+ declare function createPushHandlers(options: PushHandlersOptions): PushHandlers;
53
+ interface PushServiceWorkerOptions {
54
+ /** ไอคอนของการแจ้งเตือน (path ในแอป) ค่าเริ่มต้น /icon-192.png */
55
+ icon?: string;
56
+ /** ลิงก์ที่เปิดเมื่อ payload ไม่มี link ค่าเริ่มต้น / */
57
+ defaultLink?: string;
58
+ }
59
+ /** ซอร์ส service worker ที่รับ push ของ Pi Notify (payload { title, body, link? }) — เปิดเฉพาะลิงก์ใน origin ของแอป */
60
+ declare function pushServiceWorkerSource(options?: PushServiceWorkerOptions): string;
61
+ /**
62
+ * Route Handler ที่เสิร์ฟ service worker จากตัวแพ็กเกจเอง (same-origin, ล็อกเวอร์ชันผ่าน npm)
63
+ * @example // app/sw.js/route.ts
64
+ * export const GET = pushServiceWorker();
65
+ * ไม่แนะนำให้ importScripts จากโดเมนอื่น: service worker คุมทั้ง origin ของแอปและดักทุก request
66
+ */
67
+ declare function pushServiceWorker(options?: PushServiceWorkerOptions): () => Response;
68
+
69
+ /** ช่องทางแชทที่ผู้ใช้ผูกเองได้ */
70
+ type ChatLinkChannel = "telegram" | "line";
71
+ /** ลิงก์ผูกที่ส่งให้ UI (`ChatLinkRow` ของ @suphark/ui/pwa) */
72
+ interface ChatLink {
73
+ url: string;
74
+ code: string;
75
+ expiresAt: string;
76
+ /** ชื่อ bot/OA ให้ผู้ใช้ค้นเองเมื่อเปิดลิงก์ไม่ได้ (Telegram "@xxx_bot" · LINE "@xxx") */
77
+ botName?: string;
78
+ /** ข้อความที่ผู้ใช้พิมพ์เองได้ (Telegram `/start <code>` · LINE `ผูก Pi Notify <code>`) */
79
+ command: string;
80
+ }
81
+ interface ChatLinkHandlersOptions extends NotifyOptions {
82
+ /** ดึงผู้ใช้จาก session ของแอป (ฝั่ง server เท่านั้น) — คืน null/undefined = ยังไม่ล็อกอิน */
83
+ getUser: () => PushUser | null | undefined | Promise<PushUser | null | undefined>;
84
+ }
85
+ interface ChatLinkHandlers {
86
+ /** สถานะการผูกของผู้ใช้ที่ล็อกอินอยู่ — UI โพลทุก 3 วิหลังออกลิงก์ */
87
+ getStatus(channel: ChatLinkChannel): Promise<ChannelStatus>;
88
+ /** ออกลิงก์ผูก (หมดอายุ 10 นาที) */
89
+ createLink(channel: ChatLinkChannel): Promise<ChatLink>;
90
+ /**
91
+ * "ยกเลิกการผูก" = พักไว้ (ไม่ลบ) — การผูกใช้ร่วมทุกแอป จึงหยุดรับจากทุกแอป
92
+ * ผู้ใช้เปิดรับคืนได้ด้วยการกดผูกใหม่ หรือที่หน้า ช่องทางของฉัน ของ Pi Notify
93
+ */
94
+ unlink(channel: ChatLinkChannel): Promise<{
95
+ paused: number;
96
+ }>;
97
+ }
98
+ /**
99
+ * (0.11.0) ชุดฟังก์ชันสำหรับ Server Actions ของ `<ChatLinkRow />` (คู่ createPushHandlers) — แอปส่งแค่ `getUser`
100
+ * อีเมลมาจาก session ฝั่ง server เสมอ (ไม่รับจาก client) · ห่อเป็น `export async function` ทีละตัวในไฟล์ `"use server"`
101
+ *
102
+ * @example
103
+ * const chat = createChatLinkHandlers({ getUser: async () => (await auth())?.user });
104
+ * export async function chatStatus(c: "telegram" | "line") { return chat.getStatus(c); }
105
+ * export async function chatCreateLink(c: "telegram" | "line") { return chat.createLink(c); }
106
+ * export async function chatUnlink(c: "telegram" | "line") { return chat.unlink(c); }
107
+ */
108
+ declare function createChatLinkHandlers(options: ChatLinkHandlersOptions): ChatLinkHandlers;
109
+
16
110
  interface ConsoleOptions {
17
111
  name?: string;
18
112
  from?: ParsedAddress;
@@ -330,59 +424,6 @@ declare function toPlainText(html: string): string;
330
424
  /** เวลาพัก (วินาที) ต่อ error แต่ละแบบ ก่อนกลับมาลอง provider นั้นอีก — คูณสองทุกครั้งที่พังซ้ำ */
331
425
  declare const DEFAULT_COOLDOWN: Record<ErrorKind, number>;
332
426
 
333
- /** ผู้ใช้ที่ล็อกอินอยู่ในแอป — **ต้องมาจาก session ฝั่ง server เท่านั้น** ห้ามรับอีเมลจาก client */
334
- interface PushUser {
335
- email: string;
336
- /** user id ใน Pi Auth (sub) — ให้ Pi Notify ลบ subscription ตามบัญชีที่ถูกลบ */
337
- id?: string;
338
- }
339
- /** รูปที่ `PushSubscription.toJSON()` ของเบราว์เซอร์ส่งมา (field optional ตาม DOM type) */
340
- interface BrowserPushSubscription {
341
- endpoint?: string;
342
- keys?: Record<string, string | undefined>;
343
- }
344
- interface PushHandlersOptions extends NotifyOptions {
345
- /** ดึงผู้ใช้จาก session ของแอป — คืน null/undefined = ยังไม่ล็อกอิน */
346
- getUser: () => PushUser | null | undefined | Promise<PushUser | null | undefined>;
347
- }
348
- interface PushHandlers {
349
- /** VAPID public key สำหรับ `pushManager.subscribe({ applicationServerKey })` */
350
- getPublicKey(): Promise<string>;
351
- subscribe(subscription: BrowserPushSubscription, opts?: {
352
- label?: string;
353
- }): Promise<{
354
- ok: true;
355
- created: boolean;
356
- }>;
357
- unsubscribe(endpoint: string): Promise<{
358
- ok: true;
359
- deleted: number;
360
- }>;
361
- listDevices(): Promise<{
362
- devices: PushDevice[];
363
- }>;
364
- }
365
- /**
366
- * ชุดฟังก์ชันสำหรับ Server Actions ของ `<WebPushRow />` — แอปส่งแค่ `getUser`
367
- * (ใน `"use server"` ห่อเป็น `export async function` ทีละตัว; ห้าม import SDK ใน Client Component)
368
- */
369
- declare function createPushHandlers(options: PushHandlersOptions): PushHandlers;
370
- interface PushServiceWorkerOptions {
371
- /** ไอคอนของการแจ้งเตือน (path ในแอป) ค่าเริ่มต้น /icon-192.png */
372
- icon?: string;
373
- /** ลิงก์ที่เปิดเมื่อ payload ไม่มี link ค่าเริ่มต้น / */
374
- defaultLink?: string;
375
- }
376
- /** ซอร์ส service worker ที่รับ push ของ Pi Notify (payload { title, body, link? }) — เปิดเฉพาะลิงก์ใน origin ของแอป */
377
- declare function pushServiceWorkerSource(options?: PushServiceWorkerOptions): string;
378
- /**
379
- * Route Handler ที่เสิร์ฟ service worker จากตัวแพ็กเกจเอง (same-origin, ล็อกเวอร์ชันผ่าน npm)
380
- * @example // app/sw.js/route.ts
381
- * export const GET = pushServiceWorker();
382
- * ไม่แนะนำให้ importScripts จากโดเมนอื่น: service worker คุมทั้ง origin ของแอปและดักทุก request
383
- */
384
- declare function pushServiceWorker(options?: PushServiceWorkerOptions): () => Response;
385
-
386
427
  interface NotifyEmailInput {
387
428
  title: string;
388
429
  body: string;
@@ -517,5 +558,5 @@ declare function previewTemplate(key: string, input?: {
517
558
  text: string;
518
559
  }>;
519
560
 
520
- 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, TemplateVariable, classifyError, configureMailer, createConsoleProvider, createHubProvider, createMailer, createMemoryStore, createPresetSmtpProvider, createPushHandlers, createRedisStore, createResendProvider, createSmtpProvider, createZeptoMailProvider, deleteTemplate, formatAddress, getMailer, getTemplate, listProviders, listTemplates, looksLikeHtml, notifySafe, notifyTopicSafe, parseAddress, parseAddressList, previewTemplate, providersFromEnv, pushServiceWorker, pushServiceWorkerSource, renderNotifyEmail, requireFrom, resetMailer, resetSafeWarnings, resolveFormat, saveTemplate, sendMail, sendMailSafe, toPlainText };
521
- export type { BodyFormat, BrowserPushSubscription, ConsoleOptions, EmailTemplateInfo, EmailTemplateInput, Env, HubErrorResponse, HubOptions, HubSendRequest, HubSendResponse, Mailer, MailerOptions, NotifyEmailInput, NotifySafeResult, ProviderConfig, PushHandlers, PushHandlersOptions, PushServiceWorkerOptions, PushUser, RedisLike, ResendOptions, SendMailSafeResult, SmtpOptions, SmtpPreset, ZeptoMailOptions };
561
+ export { Address, AllProvidersFailedError, Attempt, ChannelStatus, DEFAULT_COOLDOWN, DEFAULT_HUB_URL, ErrorKind, MailError, MailerEvent, Message, NotifyInput, NotifyOptions, NotifyResultItem, NotifyTopicInput, ParsedAddress, Provider, ProviderInfo, ProviderStatus, PushDevice, Quota, SMTP_PRESETS, SendResult, Store, TemplateVariable, classifyError, configureMailer, createChatLinkHandlers, createConsoleProvider, createHubProvider, createMailer, createMemoryStore, createPresetSmtpProvider, createPushHandlers, createRedisStore, createResendProvider, createSmtpProvider, createZeptoMailProvider, deleteTemplate, formatAddress, getMailer, getTemplate, listProviders, listTemplates, looksLikeHtml, notifySafe, notifyTopicSafe, parseAddress, parseAddressList, previewTemplate, providersFromEnv, pushServiceWorker, pushServiceWorkerSource, renderNotifyEmail, requireFrom, resetMailer, resetSafeWarnings, resolveFormat, saveTemplate, sendMail, sendMailSafe, toPlainText };
562
+ export type { BodyFormat, BrowserPushSubscription, ChatLink, ChatLinkChannel, ChatLinkHandlers, ChatLinkHandlersOptions, ConsoleOptions, EmailTemplateInfo, EmailTemplateInput, 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
@@ -5,6 +5,7 @@ import {
5
5
  MailError,
6
6
  NOTIFY_TIMEOUT_MS,
7
7
  SMTP_PRESETS,
8
+ absoluteLink,
8
9
  classifyError,
9
10
  configureMailer,
10
11
  createConsoleProvider,
@@ -33,6 +34,7 @@ import {
33
34
  notifyTopicSafe,
34
35
  parseAddress,
35
36
  parseAddressList,
37
+ pauseChannel,
36
38
  providersFromEnv,
37
39
  requireFrom,
38
40
  resetMailer,
@@ -41,7 +43,7 @@ import {
41
43
  sendMail,
42
44
  sendMailSafe,
43
45
  toEmails
44
- } from "./chunk-JN7P2CVF.js";
46
+ } from "./chunk-SBQOHUW4.js";
45
47
  import {
46
48
  BUILTIN_TEMPLATE_KEYS,
47
49
  TemplateError,
@@ -51,7 +53,54 @@ import {
51
53
  renderEmailTemplate,
52
54
  resolveFormat,
53
55
  toPlainText
54
- } from "./chunk-NNMV7FZ3.js";
56
+ } from "./chunk-WXINJXF4.js";
57
+
58
+ // src/chat-link.ts
59
+ var COMMAND = {
60
+ telegram: (code) => `/start ${code}`,
61
+ line: (code) => `\u0E1C\u0E39\u0E01 Pi Notify ${code}`
62
+ };
63
+ function assertChannel(channel) {
64
+ if (channel !== "telegram" && channel !== "line")
65
+ throw new MailError("chat link: channel \u0E23\u0E2D\u0E07\u0E23\u0E31\u0E1A\u0E40\u0E09\u0E1E\u0E32\u0E30 telegram, line", {
66
+ kind: "invalid",
67
+ provider: "hub"
68
+ });
69
+ }
70
+ function createChatLinkHandlers(options) {
71
+ const { getUser, ...opts } = options;
72
+ const requireUser = async () => {
73
+ const user = await getUser();
74
+ if (!user?.email)
75
+ throw new MailError("chat link: \u0E22\u0E31\u0E07\u0E44\u0E21\u0E48\u0E44\u0E14\u0E49\u0E25\u0E47\u0E2D\u0E01\u0E2D\u0E34\u0E19", { kind: "auth", status: 401, provider: "hub" });
76
+ return user;
77
+ };
78
+ return {
79
+ async getStatus(channel) {
80
+ assertChannel(channel);
81
+ return getChannelStatus((await requireUser()).email, channel, opts);
82
+ },
83
+ async createLink(channel) {
84
+ assertChannel(channel);
85
+ const user = await requireUser();
86
+ const link = await createLink(user.email, channel, {
87
+ ...opts,
88
+ ...user.id ? { piUserId: user.id } : {}
89
+ });
90
+ return {
91
+ url: link.url,
92
+ code: link.code,
93
+ expiresAt: link.expiresAt,
94
+ ...link.botName ? { botName: link.botName } : {},
95
+ command: COMMAND[channel](link.code)
96
+ };
97
+ },
98
+ async unlink(channel) {
99
+ assertChannel(channel);
100
+ return pauseChannel((await requireUser()).email, channel, opts);
101
+ }
102
+ };
103
+ }
55
104
 
56
105
  // src/push.ts
57
106
  function createPushHandlers(options) {
@@ -237,9 +286,11 @@ export {
237
286
  NOTIFY_TIMEOUT_MS,
238
287
  SMTP_PRESETS,
239
288
  TemplateError,
289
+ absoluteLink,
240
290
  builtinFallback,
241
291
  classifyError,
242
292
  configureMailer,
293
+ createChatLinkHandlers,
243
294
  createConsoleProvider,
244
295
  createHubProvider,
245
296
  createLink,
@@ -272,6 +323,7 @@ export {
272
323
  notifyTopicSafe,
273
324
  parseAddress,
274
325
  parseAddressList,
326
+ pauseChannel,
275
327
  previewTemplate,
276
328
  providersFromEnv,
277
329
  pushServiceWorker,
package/dist/next.js CHANGED
@@ -2,8 +2,8 @@ import {
2
2
  notifySafe,
3
3
  notifyTopicSafe,
4
4
  sendMailSafe
5
- } from "./chunk-JN7P2CVF.js";
6
- import "./chunk-NNMV7FZ3.js";
5
+ } from "./chunk-SBQOHUW4.js";
6
+ import "./chunk-WXINJXF4.js";
7
7
 
8
8
  // src/next.ts
9
9
  import { after } from "next/server";
@@ -315,6 +315,11 @@ interface NotifyOptions {
315
315
  * ค่าเริ่มต้น notify() 65000 (ยาวกว่า maxDuration 60 วิของ /api/v1/notify เล็กน้อย) · getRecipient()/createLink() 10000
316
316
  */
317
317
  timeoutMs?: number;
318
+ /**
319
+ * (0.11.0) โดเมนของแอป ไว้เติมหน้าลิงก์แบบ relative ("/user/account") ใน notify/notifyTopic
320
+ * ค่าเริ่มต้น APP_URL หรือ NEXT_PUBLIC_APP_URL
321
+ */
322
+ appUrl?: string;
318
323
  }
319
324
  /** เวลารอเริ่มต้นของแต่ละคำสั่ง (ms) */
320
325
  declare const NOTIFY_TIMEOUT_MS: {
@@ -352,6 +357,11 @@ interface RecipientInfo {
352
357
  }
353
358
  /** มี PI_NOTIFY_KEY (ใน opts หรือ env) หรือไม่ — ให้ helper แบบ safe/later ใช้ตัดสินว่า "ปิดอยู่" */
354
359
  declare function hasNotifyKey(opts?: NotifyOptions): boolean;
360
+ /**
361
+ * (0.11.0) ลิงก์ relative ("/memo/12") → URL เต็มจาก opts.appUrl / APP_URL / NEXT_PUBLIC_APP_URL
362
+ * (hub ต้องการ URL เต็ม เพราะลิงก์ถูกเปิดจาก Telegram/LINE/อีเมล ไม่ใช่จากในแอป) — ไม่มีโดเมน = MailError invalid
363
+ */
364
+ declare function absoluteLink(link: string, opts?: NotifyOptions): string;
355
365
  /**
356
366
  * แจ้งเตือนผู้รับผ่าน Pi Notify hub — แชทที่ผู้รับผูกไว้ (Telegram / LINE / web push) หรืออีเมล
357
367
  * ใช้ env เดียวกับ sendMail(): PI_NOTIFY_KEY (+ PI_NOTIFY_URL)
@@ -370,8 +380,11 @@ interface RequestStatus {
370
380
  key: string;
371
381
  /** mail = sendMail, notify = notify()/notifyTopic() */
372
382
  type: "mail" | "notify";
373
- /** processing = ยังตอบคำขอแรกไม่เสร็จ · queued = ยังมีรายการรอส่งซ้ำ · partial = สำเร็จบางรายการ */
374
- status: "processing" | "queued" | "sent" | "failed" | "partial";
383
+ /**
384
+ * processing = ยังตอบคำขอแรกไม่เสร็จ · queued = ยังมีรายการรอส่งซ้ำ · partial = สำเร็จบางรายการ
385
+ * · skipped (0.10.2) = ไม่มีรายการไหนถูกส่ง (topic ไม่มีปลายทาง / ผู้รับไม่ได้ผูกช่องทาง)
386
+ */
387
+ status: "processing" | "queued" | "sent" | "failed" | "partial" | "skipped";
375
388
  /** รายการต่อผู้รับ/ช่องทาง — รายการที่เคย queued แสดงสถานะล่าสุดจริง (sent/failed/queued) */
376
389
  results: NotifyResultItem[];
377
390
  summary: {
@@ -407,10 +420,20 @@ declare function createLink(email: Recipient, channel?: Exclude<NotifyChannel, "
407
420
  url: string;
408
421
  code: string;
409
422
  expiresAt: string;
423
+ botName?: string;
424
+ }>;
425
+ /**
426
+ * (0.11.0) "ยกเลิกการผูก" จากหน้าตั้งค่าของแอป = พักไว้ (ไม่ลบ) — การผูก Telegram/LINE ใช้ร่วมทุกแอป
427
+ * จึงหยุดรับจากทุกแอป ผู้ใช้เปิดรับคืนได้ที่หน้า ช่องทางของฉัน หรือกดผูกใหม่ · คืนจำนวนที่พัก
428
+ */
429
+ declare function pauseChannel(email: Recipient, channel: Exclude<NotifyChannel, "email" | "push">, opts?: NotifyOptions): Promise<{
430
+ paused: number;
410
431
  }>;
411
432
  /** สถานะการผูกช่องทางหนึ่งของผู้รับ — แทนการ find ใน `getRecipient().channels` เองในแต่ละแอป */
412
433
  interface ChannelStatus {
413
434
  linked: boolean;
435
+ /** (0.11.0) ผูกไว้แต่พักอยู่ (ผู้ใช้กดยกเลิก/พักไว้) — กดผูกใหม่ = เปิดรับคืน */
436
+ paused?: boolean;
414
437
  label: string | null;
415
438
  linkedAt: string | null;
416
439
  /** หน้า Pi Notify ที่ผู้รับจัดการช่องทางเอง */
@@ -446,5 +469,5 @@ declare function listPushDevices(email: string, opts?: NotifyOptions): Promise<{
446
469
  devices: PushDevice[];
447
470
  }>;
448
471
 
449
- export { getRequestStatus as B, hasNotifyKey as D, listPushDevices as F, notify as G, notifyTopic as H, savePushSubscription as I, toEmails as J, NOTIFY_TIMEOUT_MS as l, createLink as v, deletePushSubscription as w, getChannelStatus as x, getPushConfig as y, getRecipient as z };
450
- 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, RequestStatus as t, SkipReason as u };
472
+ export { getRecipient as B, getRequestStatus as D, hasNotifyKey as F, listPushDevices as G, notify as H, notifyTopic as I, pauseChannel as J, savePushSubscription as K, toEmails as L, NOTIFY_TIMEOUT_MS as l, absoluteLink as v, createLink as w, deletePushSubscription as x, getChannelStatus as y, getPushConfig as z };
473
+ 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, PushDevice as a, Provider as b, ProviderStatus as c, ProviderInfo as d, Store as e, MailerEvent as f, Attempt 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, RequestStatus as t, SkipReason as u };
@@ -58,6 +58,11 @@ declare function renderTemplate(source: string, data: Data, opts: {
58
58
  declare function templateVariableNames(...sources: string[]): string[];
59
59
  /** ค่าตัวอย่างจากรายการตัวแปร (path a.b → ซ้อน object) */
60
60
  declare function sampleData(variables: TemplateVariable[] | null | undefined): Data;
61
+ /**
62
+ * ข้อความล้วนจาก HTML ที่ render แล้ว — ตัด <head>/<style>/<script>/<title> ทั้งก้อนก่อน (เทมเพลต HTML เต็มฉบับ
63
+ * ไม่ให้ CSS หลุดเข้า text part) และขึ้นบรรทัดใหม่ตามบล็อก (p, div, li, h1–h6, tr) ให้อ่านได้
64
+ */
65
+ declare function htmlToText(html: string): string;
61
66
  /** layout มาตรฐานเมื่อ HTML ไม่มี <html> ของตัวเอง — กว้าง 600px อ่านง่ายทุกโปรแกรมอีเมล */
62
67
  declare function wrapEmailLayout(body: string, opts?: {
63
68
  appName?: string;
@@ -92,5 +97,5 @@ declare function builtinFallback(key: string, data: Record<string, unknown> | un
92
97
  html: string;
93
98
  } | null;
94
99
 
95
- export { BUILTIN_TEMPLATES, BUILTIN_TEMPLATE_KEYS, TemplateError, builtinFallback, isBuiltinTemplate, parseTemplate, renderEmailTemplate, renderTemplate, sampleData, templateVariableNames, wrapEmailLayout };
100
+ export { BUILTIN_TEMPLATES, BUILTIN_TEMPLATE_KEYS, TemplateError, builtinFallback, htmlToText, isBuiltinTemplate, parseTemplate, renderEmailTemplate, renderTemplate, sampleData, templateVariableNames, wrapEmailLayout };
96
101
  export type { BuiltinTemplateKey, EmailTemplateSource, TemplateVariable };
package/dist/template.js CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  BUILTIN_TEMPLATE_KEYS,
4
4
  TemplateError,
5
5
  builtinFallback,
6
+ htmlToText,
6
7
  isBuiltinTemplate,
7
8
  parseTemplate,
8
9
  renderEmailTemplate,
@@ -10,12 +11,13 @@ import {
10
11
  sampleData,
11
12
  templateVariableNames,
12
13
  wrapEmailLayout
13
- } from "./chunk-NNMV7FZ3.js";
14
+ } from "./chunk-WXINJXF4.js";
14
15
  export {
15
16
  BUILTIN_TEMPLATES,
16
17
  BUILTIN_TEMPLATE_KEYS,
17
18
  TemplateError,
18
19
  builtinFallback,
20
+ htmlToText,
19
21
  isBuiltinTemplate,
20
22
  parseTemplate,
21
23
  renderEmailTemplate,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suphark/notify",
3
- "version": "0.10.1",
3
+ "version": "0.11.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 อัตโนมัติ",