@krischoichoi/channel-weixin 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.
Files changed (138) hide show
  1. package/README.md +101 -0
  2. package/lib/adapter.d.ts +90 -0
  3. package/lib/adapter.d.ts.map +1 -0
  4. package/lib/adapter.js +247 -0
  5. package/lib/adapter.js.map +1 -0
  6. package/lib/auth/account-store.d.ts +60 -0
  7. package/lib/auth/account-store.d.ts.map +1 -0
  8. package/lib/auth/account-store.js +84 -0
  9. package/lib/auth/account-store.js.map +1 -0
  10. package/lib/auth/login.d.ts +83 -0
  11. package/lib/auth/login.d.ts.map +1 -0
  12. package/lib/auth/login.js +202 -0
  13. package/lib/auth/login.js.map +1 -0
  14. package/lib/config.d.ts +52 -0
  15. package/lib/config.d.ts.map +1 -0
  16. package/lib/config.js +36 -0
  17. package/lib/config.js.map +1 -0
  18. package/lib/definition.d.ts +59 -0
  19. package/lib/definition.d.ts.map +1 -0
  20. package/lib/definition.js +146 -0
  21. package/lib/definition.js.map +1 -0
  22. package/lib/ilink/base-info.d.ts +22 -0
  23. package/lib/ilink/base-info.d.ts.map +1 -0
  24. package/lib/ilink/base-info.js +11 -0
  25. package/lib/ilink/base-info.js.map +1 -0
  26. package/lib/ilink/client.d.ts +110 -0
  27. package/lib/ilink/client.d.ts.map +1 -0
  28. package/lib/ilink/client.js +308 -0
  29. package/lib/ilink/client.js.map +1 -0
  30. package/lib/ilink/constants.d.ts +59 -0
  31. package/lib/ilink/constants.d.ts.map +1 -0
  32. package/lib/ilink/constants.js +59 -0
  33. package/lib/ilink/constants.js.map +1 -0
  34. package/lib/ilink/errors.d.ts +79 -0
  35. package/lib/ilink/errors.d.ts.map +1 -0
  36. package/lib/ilink/errors.js +121 -0
  37. package/lib/ilink/errors.js.map +1 -0
  38. package/lib/ilink/headers.d.ts +40 -0
  39. package/lib/ilink/headers.d.ts.map +1 -0
  40. package/lib/ilink/headers.js +70 -0
  41. package/lib/ilink/headers.js.map +1 -0
  42. package/lib/ilink/schema.d.ts +154 -0
  43. package/lib/ilink/schema.d.ts.map +1 -0
  44. package/lib/ilink/schema.js +140 -0
  45. package/lib/ilink/schema.js.map +1 -0
  46. package/lib/ilink/types.d.ts +235 -0
  47. package/lib/ilink/types.d.ts.map +1 -0
  48. package/lib/ilink/types.js +21 -0
  49. package/lib/ilink/types.js.map +1 -0
  50. package/lib/index.d.ts +50 -0
  51. package/lib/index.d.ts.map +1 -0
  52. package/lib/index.js +91 -0
  53. package/lib/index.js.map +1 -0
  54. package/lib/manifest.d.ts +54 -0
  55. package/lib/manifest.d.ts.map +1 -0
  56. package/lib/manifest.js +32 -0
  57. package/lib/manifest.js.map +1 -0
  58. package/lib/media/decrypt.d.ts +3 -0
  59. package/lib/media/decrypt.d.ts.map +1 -0
  60. package/lib/media/decrypt.js +16 -0
  61. package/lib/media/decrypt.js.map +1 -0
  62. package/lib/media/download.d.ts +22 -0
  63. package/lib/media/download.d.ts.map +1 -0
  64. package/lib/media/download.js +62 -0
  65. package/lib/media/download.js.map +1 -0
  66. package/lib/media/encrypt.d.ts +3 -0
  67. package/lib/media/encrypt.d.ts.map +1 -0
  68. package/lib/media/encrypt.js +16 -0
  69. package/lib/media/encrypt.js.map +1 -0
  70. package/lib/media/send-media.d.ts +41 -0
  71. package/lib/media/send-media.d.ts.map +1 -0
  72. package/lib/media/send-media.js +52 -0
  73. package/lib/media/send-media.js.map +1 -0
  74. package/lib/media/silk-transcode.d.ts +38 -0
  75. package/lib/media/silk-transcode.d.ts.map +1 -0
  76. package/lib/media/silk-transcode.js +88 -0
  77. package/lib/media/silk-transcode.js.map +1 -0
  78. package/lib/media/upload.d.ts +38 -0
  79. package/lib/media/upload.d.ts.map +1 -0
  80. package/lib/media/upload.js +122 -0
  81. package/lib/media/upload.js.map +1 -0
  82. package/lib/messaging/dedup.d.ts +87 -0
  83. package/lib/messaging/dedup.d.ts.map +1 -0
  84. package/lib/messaging/dedup.js +127 -0
  85. package/lib/messaging/dedup.js.map +1 -0
  86. package/lib/messaging/mapper.d.ts +26 -0
  87. package/lib/messaging/mapper.d.ts.map +1 -0
  88. package/lib/messaging/mapper.js +107 -0
  89. package/lib/messaging/mapper.js.map +1 -0
  90. package/lib/messaging/monitor.d.ts +109 -0
  91. package/lib/messaging/monitor.d.ts.map +1 -0
  92. package/lib/messaging/monitor.js +234 -0
  93. package/lib/messaging/monitor.js.map +1 -0
  94. package/lib/messaging/quote.d.ts +9 -0
  95. package/lib/messaging/quote.d.ts.map +1 -0
  96. package/lib/messaging/quote.js +35 -0
  97. package/lib/messaging/quote.js.map +1 -0
  98. package/lib/messaging/send.d.ts +54 -0
  99. package/lib/messaging/send.d.ts.map +1 -0
  100. package/lib/messaging/send.js +173 -0
  101. package/lib/messaging/send.js.map +1 -0
  102. package/lib/messaging/typing.d.ts +34 -0
  103. package/lib/messaging/typing.d.ts.map +1 -0
  104. package/lib/messaging/typing.js +56 -0
  105. package/lib/messaging/typing.js.map +1 -0
  106. package/lib/storage/context-token.d.ts +26 -0
  107. package/lib/storage/context-token.d.ts.map +1 -0
  108. package/lib/storage/context-token.js +26 -0
  109. package/lib/storage/context-token.js.map +1 -0
  110. package/lib/storage/sync-cursor.d.ts +27 -0
  111. package/lib/storage/sync-cursor.d.ts.map +1 -0
  112. package/lib/storage/sync-cursor.js +25 -0
  113. package/lib/storage/sync-cursor.js.map +1 -0
  114. package/lib/transport.d.ts +38 -0
  115. package/lib/transport.d.ts.map +1 -0
  116. package/lib/transport.js +68 -0
  117. package/lib/transport.js.map +1 -0
  118. package/lib/upstream/compat.d.ts +54 -0
  119. package/lib/upstream/compat.d.ts.map +1 -0
  120. package/lib/upstream/compat.js +54 -0
  121. package/lib/upstream/compat.js.map +1 -0
  122. package/lib/upstream/config-manager.d.ts +35 -0
  123. package/lib/upstream/config-manager.d.ts.map +1 -0
  124. package/lib/upstream/config-manager.js +91 -0
  125. package/lib/upstream/config-manager.js.map +1 -0
  126. package/lib/upstream/index.d.ts +42 -0
  127. package/lib/upstream/index.d.ts.map +1 -0
  128. package/lib/upstream/index.js +13 -0
  129. package/lib/upstream/index.js.map +1 -0
  130. package/lib/upstream/port.d.ts +153 -0
  131. package/lib/upstream/port.d.ts.map +1 -0
  132. package/lib/upstream/port.js +15 -0
  133. package/lib/upstream/port.js.map +1 -0
  134. package/lib/upstream/tencent-upstream.d.ts +116 -0
  135. package/lib/upstream/tencent-upstream.d.ts.map +1 -0
  136. package/lib/upstream/tencent-upstream.js +518 -0
  137. package/lib/upstream/tencent-upstream.js.map +1 -0
  138. package/package.json +48 -0
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Preserve a Weixin reference as explicit model-visible context. iLink does
3
+ * not expose a stable referenced Channel message id, so this must not create a
4
+ * synthetic replyTo relationship.
5
+ */
6
+ export function formatQuotedContext(ref) {
7
+ if (!ref)
8
+ return [];
9
+ const item = ref.message_item;
10
+ const summary = quotedItemSummary(item);
11
+ const title = cleanText(ref.title);
12
+ if (!title && !summary)
13
+ return [];
14
+ const body = [title, summary].filter(Boolean).join(' | ');
15
+ return [{ type: 'text', text: `[quoted: ${body}]` }];
16
+ }
17
+ function quotedItemSummary(item) {
18
+ if (!item)
19
+ return undefined;
20
+ const text = cleanText(item.text_item?.text ?? item.voice_item?.text);
21
+ if (text)
22
+ return text;
23
+ switch (item.type) {
24
+ case 2: return '[image]';
25
+ case 3: return '[voice]';
26
+ case 4: return item.file_item?.file_name ? `[file: ${item.file_item.file_name}]` : '[file]';
27
+ case 5: return '[video]';
28
+ default: return undefined;
29
+ }
30
+ }
31
+ function cleanText(value) {
32
+ const trimmed = value?.trim();
33
+ return trimmed || undefined;
34
+ }
35
+ //# sourceMappingURL=quote.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quote.js","sourceRoot":"","sources":["../../src/messaging/quote.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAgC;IAClE,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC;IAC9B,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACnC,IAAI,CAAC,KAAK,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IAClC,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1D,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,IAAI,GAAG,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAkC;IAC3D,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IACtE,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IACtB,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,CAAC,CAAC,CAAC,OAAO,SAAS,CAAC;QACzB,KAAK,CAAC,CAAC,CAAC,OAAO,SAAS,CAAC;QACzB,KAAK,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,SAAS,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC5F,KAAK,CAAC,CAAC,CAAC,OAAO,SAAS,CAAC;QACzB,OAAO,CAAC,CAAC,OAAO,SAAS,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,KAAyB;IAC1C,MAAM,OAAO,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC;IAC9B,OAAO,OAAO,IAAI,SAAS,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,54 @@
1
+ import type { ChannelTarget, OutboundMessage, SendResult } from '@krischoichoi/channel-core';
2
+ import type { ILinkClient } from '../ilink/client.js';
3
+ import type { ContextTokenStore } from '../storage/context-token.js';
4
+ export interface OutboundSenderOptions {
5
+ client: ILinkClient;
6
+ contextTokens: ContextTokenStore;
7
+ /** Optional default run id scope; defaults to a fresh UUID per send. */
8
+ runId?: string;
9
+ /** CDN base URL for outbound image upload. */
10
+ cdnBaseUrl?: string;
11
+ /** API base URL for the getuploadurl CGI. */
12
+ apiBaseUrl?: string;
13
+ /** Injectable getuploadurl resolver (tests). */
14
+ getUploadUrl?: (request: Record<string, unknown>) => Promise<{
15
+ upload_full_url?: string;
16
+ upload_param?: string;
17
+ thumb_upload_param?: string;
18
+ }>;
19
+ /** Injectable raw upload POST (tests). */
20
+ upload?: (url: string, form: FormData) => Promise<Response>;
21
+ }
22
+ export interface SendTextParams {
23
+ to: string;
24
+ text: string;
25
+ contextToken?: string;
26
+ /** Per-send correlation; defaults to a fresh UUID. */
27
+ runId?: string;
28
+ }
29
+ /**
30
+ * Build one iLink sendmessage payload for a text message.
31
+ */
32
+ export declare function buildSendTextPayload(params: SendTextParams): Record<string, unknown>;
33
+ export declare class OutboundSender {
34
+ private readonly client;
35
+ private readonly contextTokens;
36
+ private readonly defaultRunId;
37
+ private readonly cdnBaseUrl?;
38
+ private readonly apiBaseUrl?;
39
+ private readonly getUploadUrl?;
40
+ private readonly uploadFn?;
41
+ constructor(options: OutboundSenderOptions);
42
+ /**
43
+ * Send an outbound Channel message to a target. Reads the context token for
44
+ * the target peer, then dispatches text (default) or an image upload+send
45
+ * when the message carries image bytes. Failures become ChannelError (token
46
+ * redacted).
47
+ */
48
+ send(target: ChannelTarget, message: OutboundMessage): Promise<SendResult>;
49
+ private sendImage;
50
+ private sendFile;
51
+ private sendVideo;
52
+ private uploadAndSend;
53
+ }
54
+ //# sourceMappingURL=send.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../src/messaging/send.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EACf,UAAU,EAIX,MAAM,4BAA4B,CAAC;AAGpC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAWrE,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,WAAW,CAAC;IACpB,aAAa,EAAE,iBAAiB,CAAC;IACjC,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/I,0CAA0C;IAC1C,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC7D;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAepF;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IAClD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAwC;IACtE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAkC;gBAEhD,OAAO,EAAE,qBAAqB;IAU1C;;;;;OAKG;IACG,IAAI,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC;YA2ClE,SAAS;YAST,QAAQ;YASR,SAAS;YAST,aAAa;CA2C5B"}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Classification: C — duplicate send payload + orchestration [@deprecated upstream-gap].
3
+ *
4
+ * Outbound text/image send payload building + CDN upload routing. The official
5
+ * messaging/send.js (sendMessageWeixin / sendImageMessageWeixin) is OpenClaw
6
+ * coupled (imports api/api + util/logger). The text payload builders here
7
+ * mirror the official shapes; kept behind the facade as an upstream-gap. The
8
+ * local type/shape enums mirror the Tencent source reference.
9
+ */
10
+ /**
11
+ * OutboundSender — builds the real iLink sendmessage payload and calls the
12
+ * client. The ReplyRouter never generates Weixin protocol fields; they live
13
+ * here.
14
+ *
15
+ * Text is the default path. When the outbound message carries an image part
16
+ * with local bytes (or a data URI), the sender uploads it to the CDN
17
+ * (uploadMedia) and sends an image sendmessage (sendMedia) through the same
18
+ * target + context token + run id, so a media turn correlates identically to a
19
+ * text turn.
20
+ */
21
+ import { randomUUID } from 'node:crypto';
22
+ import { collectText } from '@krischoichoi/channel-core';
23
+ import { ChannelError } from '@krischoichoi/channel-core';
24
+ import { MESSAGE_ITEM_TYPE_TEXT, MESSAGE_STATE_FINISH, MESSAGE_TYPE_BOT, } from '../ilink/types.js';
25
+ import { redactMessage } from '../ilink/errors.js';
26
+ import { uploadMedia } from '../media/upload.js';
27
+ import { WX5_MEDIA_TYPE_FILE, WX5_MEDIA_TYPE_IMAGE, WX5_MEDIA_TYPE_VIDEO } from '../media/upload.js';
28
+ import { sendMedia } from '../media/send-media.js';
29
+ /**
30
+ * Build one iLink sendmessage payload for a text message.
31
+ */
32
+ export function buildSendTextPayload(params) {
33
+ return {
34
+ msg: {
35
+ from_user_id: '',
36
+ to_user_id: params.to,
37
+ client_id: randomUUID(),
38
+ message_type: MESSAGE_TYPE_BOT,
39
+ message_state: MESSAGE_STATE_FINISH,
40
+ item_list: params.text
41
+ ? [{ type: MESSAGE_ITEM_TYPE_TEXT, text_item: { text: params.text } }]
42
+ : [],
43
+ context_token: params.contextToken ?? undefined,
44
+ run_id: params.runId ?? undefined,
45
+ },
46
+ };
47
+ }
48
+ export class OutboundSender {
49
+ client;
50
+ contextTokens;
51
+ defaultRunId;
52
+ cdnBaseUrl;
53
+ apiBaseUrl;
54
+ getUploadUrl;
55
+ uploadFn;
56
+ constructor(options) {
57
+ this.client = options.client;
58
+ this.contextTokens = options.contextTokens;
59
+ this.defaultRunId = options.runId;
60
+ this.cdnBaseUrl = options.cdnBaseUrl;
61
+ this.apiBaseUrl = options.apiBaseUrl;
62
+ this.getUploadUrl = options.getUploadUrl;
63
+ this.uploadFn = options.upload;
64
+ }
65
+ /**
66
+ * Send an outbound Channel message to a target. Reads the context token for
67
+ * the target peer, then dispatches text (default) or an image upload+send
68
+ * when the message carries image bytes. Failures become ChannelError (token
69
+ * redacted).
70
+ */
71
+ async send(target, message) {
72
+ const to = target.conversationId;
73
+ const runId = target.runId ?? this.defaultRunId ?? randomUUID();
74
+ const contextToken = await this.contextTokens.get(to);
75
+ const media = outboundMediaParts(message.parts);
76
+ const text = message.text ?? collectText(message.parts ?? []);
77
+ try {
78
+ // Validate the complete request before sending a caption, otherwise a
79
+ // rejected media part would leave a misleading partial delivery.
80
+ if (hasUnsupportedMediaPart(message.parts)) {
81
+ throw new ChannelError('CHANNEL_UNSUPPORTED', 'weixin outbound media requires local bytes; voice is not supported');
82
+ }
83
+ // Tencent 2.4.6 sends an optional caption as its own TEXT request before
84
+ // the media request; each item_list contains exactly one item.
85
+ let messageId;
86
+ if (media.length > 0 && text) {
87
+ const caption = buildSendTextPayload({ to, text, contextToken, runId });
88
+ await this.client.sendMessage(caption);
89
+ messageId = caption.msg.client_id;
90
+ }
91
+ for (const part of media) {
92
+ messageId = part.type === 'image'
93
+ ? await this.sendImage(to, part, contextToken, runId)
94
+ : part.type === 'file'
95
+ ? await this.sendFile(to, part, contextToken, runId)
96
+ : await this.sendVideo(to, part, contextToken, runId);
97
+ }
98
+ if (media.length > 0) {
99
+ return { delivered: true, ...(messageId ? { messageId } : {}) };
100
+ }
101
+ // Voice has no established Tencent 2.4.6 outbound path. File/video need
102
+ // local bytes for the encrypted CDN upload, so reject URL-only parts.
103
+ const payload = buildSendTextPayload({ to, text, contextToken, runId });
104
+ await this.client.sendMessage(payload);
105
+ return { delivered: true, messageId: payload.msg.client_id };
106
+ }
107
+ catch (error) {
108
+ const messageText = error instanceof Error ? error.message : String(error);
109
+ throw new ChannelError('CHANNEL_SEND_FAILED', redactMessage(messageText), { cause: error });
110
+ }
111
+ }
112
+ async sendImage(to, image, contextToken, runId) {
113
+ return this.uploadAndSend(to, image, contextToken, runId, 'image');
114
+ }
115
+ async sendFile(to, file, contextToken, runId) {
116
+ return this.uploadAndSend(to, file, contextToken, runId, 'file');
117
+ }
118
+ async sendVideo(to, video, contextToken, runId) {
119
+ return this.uploadAndSend(to, video, contextToken, runId, 'video');
120
+ }
121
+ async uploadAndSend(to, part, contextToken, runId, kind) {
122
+ if (!this.client.cdnUrl && !this.cdnBaseUrl) {
123
+ throw new ChannelError('CHANNEL_UNSUPPORTED', 'outbound media upload requires a CDN base URL');
124
+ }
125
+ const cdnBaseUrl = this.cdnBaseUrl ?? this.client.cdnUrl;
126
+ const apiBaseUrl = this.apiBaseUrl ?? this.client.baseUrl;
127
+ const mediaType = kind === 'image'
128
+ ? WX5_MEDIA_TYPE_IMAGE
129
+ : kind === 'file'
130
+ ? WX5_MEDIA_TYPE_FILE
131
+ : WX5_MEDIA_TYPE_VIDEO;
132
+ const uploaded = await uploadMedia(Buffer.from(part.localData), {
133
+ cdnBaseUrl,
134
+ apiBaseUrl,
135
+ toUserId: to,
136
+ token: this.client.token,
137
+ mediaType,
138
+ getUploadUrl: this.getUploadUrl ?? ((request) => this.client.getUploadUrl(request)),
139
+ upload: this.uploadFn,
140
+ });
141
+ // Tencent 2.4.6 base64-encodes the 32-character ASCII hex key, not the
142
+ // decoded 16 raw key bytes.
143
+ const sent = await sendMedia(this.client, {
144
+ to,
145
+ kind,
146
+ media: {
147
+ encrypt_query_param: uploaded.downloadEncryptedQueryParam,
148
+ aes_key: uploaded.aeskey ? Buffer.from(uploaded.aeskey, 'ascii').toString('base64') : undefined,
149
+ },
150
+ fileSize: uploaded.fileSize,
151
+ fileSizeCiphertext: uploaded.fileSizeCiphertext,
152
+ fileName: kind === 'file' ? part.name : undefined,
153
+ contextToken,
154
+ runId,
155
+ });
156
+ return sent.messageId;
157
+ }
158
+ }
159
+ function outboundMediaParts(parts) {
160
+ if (!parts)
161
+ return [];
162
+ return parts.filter((part) => (part.type === 'image' || part.type === 'file' || part.type === 'video') &&
163
+ part.localData !== undefined &&
164
+ part.localData.byteLength > 0);
165
+ }
166
+ /** True when the message carries an unsupported or non-uploadable media part. */
167
+ function hasUnsupportedMediaPart(parts) {
168
+ if (!parts)
169
+ return false;
170
+ return parts.some((p) => p.type === 'audio' ||
171
+ ((p.type === 'image' || p.type === 'file' || p.type === 'video') && (!p.localData || p.localData.byteLength === 0)));
172
+ }
173
+ //# sourceMappingURL=send.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/messaging/send.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Classification: A — DSH glue [keep].
3
+ *
4
+ * Best-effort typing indicator over client.sendTyping. Pure DSH wrapper; no
5
+ * duplicated platform algorithm. Keep; driven by upstream startTyping().
6
+ */
7
+ /**
8
+ * TypingController — best-effort typing indicator (WX6-grade).
9
+ *
10
+ * Calls `client.sendTyping` when enabled; failures NEVER fail the main
11
+ * message. Tracks active/stopped so a turn that ends cancels the indicator.
12
+ */
13
+ import type { ILinkClient } from '../ilink/client.js';
14
+ export interface TypingControllerOptions {
15
+ client: ILinkClient;
16
+ /** Whether typing is enabled (from config). */
17
+ enabled?: boolean;
18
+ /** Optional typing ticket for the conversation. */
19
+ typingTicket?: string | (() => Promise<string | undefined> | string | undefined);
20
+ }
21
+ export declare class TypingController {
22
+ private readonly client;
23
+ private readonly enabled;
24
+ private readonly typingTicket?;
25
+ private active;
26
+ constructor(options: TypingControllerOptions);
27
+ get isActive(): boolean;
28
+ private resolveTicket;
29
+ /** Start the typing indicator for a user. */
30
+ start(to: string): Promise<void>;
31
+ /** Cancel the typing indicator for a user. */
32
+ stop(to: string): Promise<void>;
33
+ }
34
+ //# sourceMappingURL=typing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typing.d.ts","sourceRoot":"","sources":["../../src/messaging/typing.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH;;;;;GAKG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGtD,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,WAAW,CAAC;IACpB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC;CAClF;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAA0C;IACxE,OAAO,CAAC,MAAM,CAAS;gBAEX,OAAO,EAAE,uBAAuB;IAM5C,IAAI,QAAQ,IAAI,OAAO,CAEtB;YAEa,aAAa;IAK3B,6CAA6C;IACvC,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBtC,8CAA8C;IACxC,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CActC"}
@@ -0,0 +1,56 @@
1
+ import { TYPING_STATUS_CANCEL, TYPING_STATUS_TYPING } from '../ilink/types.js';
2
+ export class TypingController {
3
+ client;
4
+ enabled;
5
+ typingTicket;
6
+ active = false;
7
+ constructor(options) {
8
+ this.client = options.client;
9
+ this.enabled = options.enabled ?? false;
10
+ this.typingTicket = options.typingTicket;
11
+ }
12
+ get isActive() {
13
+ return this.active;
14
+ }
15
+ async resolveTicket() {
16
+ if (this.typingTicket === undefined)
17
+ return undefined;
18
+ return typeof this.typingTicket === 'function' ? this.typingTicket() : this.typingTicket;
19
+ }
20
+ /** Start the typing indicator for a user. */
21
+ async start(to) {
22
+ if (!this.enabled)
23
+ return;
24
+ this.active = true;
25
+ try {
26
+ const ticket = await this.resolveTicket();
27
+ await this.client.sendTyping({
28
+ ilink_user_id: to,
29
+ typing_ticket: ticket,
30
+ status: TYPING_STATUS_TYPING,
31
+ });
32
+ }
33
+ catch {
34
+ // best effort — typing must never break the main flow.
35
+ this.active = false;
36
+ }
37
+ }
38
+ /** Cancel the typing indicator for a user. */
39
+ async stop(to) {
40
+ if (!this.enabled)
41
+ return;
42
+ this.active = false;
43
+ try {
44
+ const ticket = await this.resolveTicket();
45
+ await this.client.sendTyping({
46
+ ilink_user_id: to,
47
+ typing_ticket: ticket,
48
+ status: TYPING_STATUS_CANCEL,
49
+ });
50
+ }
51
+ catch {
52
+ // best effort
53
+ }
54
+ }
55
+ }
56
+ //# sourceMappingURL=typing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typing.js","sourceRoot":"","sources":["../../src/messaging/typing.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAU/E,MAAM,OAAO,gBAAgB;IACV,MAAM,CAAc;IACpB,OAAO,CAAU;IACjB,YAAY,CAA2C;IAChE,MAAM,GAAG,KAAK,CAAC;IAEvB,YAAY,OAAgC;QAC1C,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,KAAK,CAAC;QACxC,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IAC3C,CAAC;IAED,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAEO,KAAK,CAAC,aAAa;QACzB,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QACtD,OAAO,OAAO,IAAI,CAAC,YAAY,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC;IAC3F,CAAC;IAED,6CAA6C;IAC7C,KAAK,CAAC,KAAK,CAAC,EAAU;QACpB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;YAC1C,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC;gBAC3B,aAAa,EAAE,EAAE;gBACjB,aAAa,EAAE,MAAM;gBACrB,MAAM,EAAE,oBAAoB;aAC7B,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,uDAAuD;YACvD,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACtB,CAAC;IACH,CAAC;IAED,8CAA8C;IAC9C,KAAK,CAAC,IAAI,CAAC,EAAU;QACnB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;YAC1C,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC;gBAC3B,aAAa,EAAE,EAAE;gBACjB,aAAa,EAAE,MAAM;gBACrB,MAAM,EAAE,oBAAoB;aAC7B,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,cAAc;QAChB,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * ContextTokenStore — keyed by `accountId + peerId` -> `context_token`.
3
+ *
4
+ * Set on receive (from the inbound message's `from_user_id` + `context_token`),
5
+ * read on send so the reply carries the correct context. `channel-harness`
6
+ * never sees the context token — it is entirely internal to this adapter.
7
+ */
8
+ import type { ChannelStorage } from '@krischoichoi/channel-core';
9
+ export interface ContextTokenStoreOptions {
10
+ storage: ChannelStorage;
11
+ /** Local DSH account alias. */
12
+ accountId?: string;
13
+ }
14
+ export declare class ContextTokenStore {
15
+ private readonly storage;
16
+ private readonly accountId;
17
+ constructor(options: ContextTokenStoreOptions);
18
+ private key;
19
+ /** Record the context token for a peer. */
20
+ set(peerId: string, contextToken: string): Promise<void>;
21
+ /** Look up the context token for a peer (undefined when none). */
22
+ get(peerId: string): Promise<string | undefined>;
23
+ /** Clear token(s) for a peer. */
24
+ clear(peerId: string): Promise<void>;
25
+ }
26
+ //# sourceMappingURL=context-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-token.d.ts","sourceRoot":"","sources":["../../src/storage/context-token.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,cAAc,CAAC;IACxB,+BAA+B;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,OAAO,EAAE,wBAAwB;IAK7C,OAAO,CAAC,GAAG;IAIX,2CAA2C;IACrC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK9D,kEAAkE;IAC5D,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAItD,iCAAiC;IAC3B,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG3C"}
@@ -0,0 +1,26 @@
1
+ export class ContextTokenStore {
2
+ storage;
3
+ accountId;
4
+ constructor(options) {
5
+ this.storage = options.storage;
6
+ this.accountId = options.accountId ?? 'main';
7
+ }
8
+ key(peerId) {
9
+ return `weixin:context-token:${this.accountId}:${peerId}`;
10
+ }
11
+ /** Record the context token for a peer. */
12
+ async set(peerId, contextToken) {
13
+ if (!contextToken)
14
+ return;
15
+ await this.storage.set(this.key(peerId), contextToken);
16
+ }
17
+ /** Look up the context token for a peer (undefined when none). */
18
+ async get(peerId) {
19
+ return this.storage.get(this.key(peerId));
20
+ }
21
+ /** Clear token(s) for a peer. */
22
+ async clear(peerId) {
23
+ await this.storage.delete(this.key(peerId));
24
+ }
25
+ }
26
+ //# sourceMappingURL=context-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-token.js","sourceRoot":"","sources":["../../src/storage/context-token.ts"],"names":[],"mappings":"AAeA,MAAM,OAAO,iBAAiB;IACX,OAAO,CAAiB;IACxB,SAAS,CAAS;IAEnC,YAAY,OAAiC;QAC3C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC;IAC/C,CAAC;IAEO,GAAG,CAAC,MAAc;QACxB,OAAO,wBAAwB,IAAI,CAAC,SAAS,IAAI,MAAM,EAAE,CAAC;IAC5D,CAAC;IAED,2CAA2C;IAC3C,KAAK,CAAC,GAAG,CAAC,MAAc,EAAE,YAAoB;QAC5C,IAAI,CAAC,YAAY;YAAE,OAAO;QAC1B,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,CAAC;IACzD,CAAC;IAED,kEAAkE;IAClE,KAAK,CAAC,GAAG,CAAC,MAAc;QACtB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5C,CAAC;IAED,iCAAiC;IACjC,KAAK,CAAC,KAAK,CAAC,MAAc;QACxB,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,CAAC;CACF"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * SyncCursorStore — persists the `get_updates_buf` sync cursor.
3
+ *
4
+ * Per doc section 18: the cursor is persisted ONLY AFTER the messages of the
5
+ * round have entered the Channel inbound pipeline. If we crash before the
6
+ * commit, the same messages may be re-fetched — that is accepted and handled
7
+ * by the dedup layer.
8
+ */
9
+ import type { ChannelStorage } from '@krischoichoi/channel-core';
10
+ export interface SyncCursorStoreOptions {
11
+ storage: ChannelStorage;
12
+ /** Local DSH account alias. */
13
+ accountId?: string;
14
+ }
15
+ export declare class SyncCursorStore {
16
+ private readonly storage;
17
+ private readonly accountId;
18
+ constructor(options: SyncCursorStoreOptions);
19
+ private key;
20
+ /** Load the persisted cursor (empty string when none). */
21
+ load(): Promise<string>;
22
+ /** Atomically persist a new cursor. */
23
+ set(cursor: string): Promise<void>;
24
+ /** Clear the cursor (e.g. on re-auth). */
25
+ clear(): Promise<void>;
26
+ }
27
+ //# sourceMappingURL=sync-cursor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-cursor.d.ts","sourceRoot":"","sources":["../../src/storage/sync-cursor.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,cAAc,CAAC;IACxB,+BAA+B;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,OAAO,EAAE,sBAAsB;IAK3C,OAAO,CAAC,GAAG;IAIX,0DAA0D;IACpD,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IAK7B,uCAAuC;IACjC,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxC,0CAA0C;IACpC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
@@ -0,0 +1,25 @@
1
+ export class SyncCursorStore {
2
+ storage;
3
+ accountId;
4
+ constructor(options) {
5
+ this.storage = options.storage;
6
+ this.accountId = options.accountId ?? 'main';
7
+ }
8
+ key() {
9
+ return `weixin:sync-cursor:${this.accountId}`;
10
+ }
11
+ /** Load the persisted cursor (empty string when none). */
12
+ async load() {
13
+ const raw = await this.storage.get(this.key());
14
+ return raw ?? '';
15
+ }
16
+ /** Atomically persist a new cursor. */
17
+ async set(cursor) {
18
+ await this.storage.set(this.key(), cursor ?? '');
19
+ }
20
+ /** Clear the cursor (e.g. on re-auth). */
21
+ async clear() {
22
+ await this.storage.delete(this.key());
23
+ }
24
+ }
25
+ //# sourceMappingURL=sync-cursor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-cursor.js","sourceRoot":"","sources":["../../src/storage/sync-cursor.ts"],"names":[],"mappings":"AAgBA,MAAM,OAAO,eAAe;IACT,OAAO,CAAiB;IACxB,SAAS,CAAS;IAEnC,YAAY,OAA+B;QACzC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC;IAC/C,CAAC;IAEO,GAAG;QACT,OAAO,sBAAsB,IAAI,CAAC,SAAS,EAAE,CAAC;IAChD,CAAC;IAED,0DAA0D;IAC1D,KAAK,CAAC,IAAI;QACR,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAC/C,OAAO,GAAG,IAAI,EAAE,CAAC;IACnB,CAAC;IAED,uCAAuC;IACvC,KAAK,CAAC,GAAG,CAAC,MAAc;QACtB,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;IAED,0CAA0C;IAC1C,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACxC,CAAC;CACF"}
@@ -0,0 +1,38 @@
1
+ export interface HttpRequestInit {
2
+ method?: string;
3
+ /** Optional custom headers; merged over the transport default. */
4
+ headers?: Record<string, string>;
5
+ /** JSON body (serialized by the transport). */
6
+ body?: unknown;
7
+ /** Per-request timeout override. */
8
+ timeoutMs?: number;
9
+ }
10
+ export interface HttpTransport {
11
+ /**
12
+ * Perform one HTTP request against a full URL.
13
+ *
14
+ * @param url Absolute URL (origin + path + query) to request.
15
+ * @param init Request options.
16
+ * @param signal Optional external abort signal (combined with the timeout).
17
+ * @returns The parsed JSON response body.
18
+ */
19
+ request(url: string, init?: HttpRequestInit, signal?: AbortSignal): Promise<unknown>;
20
+ }
21
+ export interface FetchTransportOptions {
22
+ /** Default request timeout in ms; overridden by `init.timeoutMs`. */
23
+ timeoutMs: number;
24
+ /** Injectable fetch (tests). */
25
+ fetchImpl?: typeof fetch;
26
+ }
27
+ /**
28
+ * Default transport backed by the standard `fetch`. Timeout and external
29
+ * aborts are combined onto a single `AbortSignal`; HTTP non-2xx responses and
30
+ * network/timeout/abort failures are normalized to `ChannelError`.
31
+ */
32
+ export declare class FetchTransport implements HttpTransport {
33
+ private readonly timeoutMs;
34
+ private readonly fetchImpl;
35
+ constructor(options: FetchTransportOptions);
36
+ request(url: string, init?: HttpRequestInit, signal?: AbortSignal): Promise<unknown>;
37
+ }
38
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,+CAA+C;IAC/C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B;;;;;;;OAOG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtF;AAOD,MAAM,WAAW,qBAAqB;IACpC,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAC;IAClB,gCAAgC;IAChC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED;;;;GAIG;AACH,qBAAa,cAAe,YAAW,aAAa;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;gBAE7B,OAAO,EAAE,qBAAqB;IAKpC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,eAAoB,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;CA+B/F"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Base HTTP transport boundary — a clean `fetch` wrapper.
3
+ *
4
+ * This module knows NOTHING about Weixin endpoints or the iLink protocol. It
5
+ * only turns a URL + method + body into a `fetch` call with a timeout and an
6
+ * optional external abort signal, normalizing failures into `ChannelError`.
7
+ * The iLink client (`src/ilink/client.ts`) owns endpoint knowledge and builds
8
+ * the protocol headers on top of this transport.
9
+ */
10
+ import { ChannelError } from '@krischoichoi/channel-core';
11
+ /** Default headers attached to every request unless overridden. */
12
+ const DEFAULT_HEADERS = {
13
+ 'content-type': 'application/json',
14
+ };
15
+ /**
16
+ * Default transport backed by the standard `fetch`. Timeout and external
17
+ * aborts are combined onto a single `AbortSignal`; HTTP non-2xx responses and
18
+ * network/timeout/abort failures are normalized to `ChannelError`.
19
+ */
20
+ export class FetchTransport {
21
+ timeoutMs;
22
+ fetchImpl;
23
+ constructor(options) {
24
+ this.timeoutMs = options.timeoutMs;
25
+ this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
26
+ }
27
+ async request(url, init = {}, signal) {
28
+ const controller = new AbortController();
29
+ const timeoutMs = init.timeoutMs ?? this.timeoutMs;
30
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
31
+ const onOuterAbort = () => controller.abort();
32
+ signal?.addEventListener('abort', onOuterAbort, { once: true });
33
+ try {
34
+ const response = await this.fetchImpl(url, {
35
+ method: init.method ?? 'GET',
36
+ headers: { ...DEFAULT_HEADERS, ...init.headers },
37
+ body: init.body !== undefined ? JSON.stringify(init.body) : undefined,
38
+ signal: controller.signal,
39
+ });
40
+ if (!response.ok) {
41
+ throw new ChannelError('CHANNEL_ERROR', `http ${response.status} on ${safeUrl(url)}`);
42
+ }
43
+ const text = await response.text();
44
+ return text ? JSON.parse(text) : undefined;
45
+ }
46
+ catch (error) {
47
+ if (error instanceof ChannelError)
48
+ throw error;
49
+ const aborted = controller.signal.aborted || error?.name === 'AbortError';
50
+ throw new ChannelError('CHANNEL_ERROR', aborted ? `http request aborted on ${safeUrl(url)}` : `http request failed on ${safeUrl(url)}`, { cause: error });
51
+ }
52
+ finally {
53
+ clearTimeout(timer);
54
+ signal?.removeEventListener('abort', onOuterAbort);
55
+ }
56
+ }
57
+ }
58
+ /** Strip query strings (which may carry signatures) from log output. */
59
+ function safeUrl(url) {
60
+ try {
61
+ const parsed = new URL(url);
62
+ return parsed.search ? `${parsed.origin}${parsed.pathname}?<redacted>` : url;
63
+ }
64
+ catch {
65
+ return url.length > 80 ? `${url.slice(0, 80)}…` : url;
66
+ }
67
+ }
68
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAwB1D,mEAAmE;AACnE,MAAM,eAAe,GAA2B;IAC9C,cAAc,EAAE,kBAAkB;CACnC,CAAC;AASF;;;;GAIG;AACH,MAAM,OAAO,cAAc;IACR,SAAS,CAAS;IAClB,SAAS,CAAe;IAEzC,YAAY,OAA8B;QACxC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACnC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,UAAU,CAAC,KAAK,CAAC;IACzD,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,GAAW,EAAE,OAAwB,EAAE,EAAE,MAAoB;QACzE,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,SAAS,CAAC;QACnD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;QAC9D,MAAM,YAAY,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QAC9C,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAChE,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE;gBACzC,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,KAAK;gBAC5B,OAAO,EAAE,EAAE,GAAG,eAAe,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE;gBAChD,IAAI,EAAE,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;gBACrE,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,YAAY,CAAC,eAAe,EAAE,QAAQ,QAAQ,CAAC,MAAM,OAAO,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACxF,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACnC,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC7C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,YAAY;gBAAE,MAAM,KAAK,CAAC;YAC/C,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC,OAAO,IAAK,KAAe,EAAE,IAAI,KAAK,YAAY,CAAC;YACrF,MAAM,IAAI,YAAY,CACpB,eAAe,EACf,OAAO,CAAC,CAAC,CAAC,2BAA2B,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,0BAA0B,OAAO,CAAC,GAAG,CAAC,EAAE,EAC9F,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;CACF;AAED,wEAAwE;AACxE,SAAS,OAAO,CAAC,GAAW;IAC1B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,QAAQ,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC;IAC/E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IACxD,CAAC;AACH,CAAC"}