@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
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # @krischoichoi/channel-weixin
2
+
3
+ Weixin / 微信 channel adapter for DeepSeek Harness.
4
+
5
+ Direct Tencent Weixin iLink integration (QR login, long-poll receive, text/image
6
+ send). This adapter is an upstream-gap source-port: it speaks the iLink
7
+ protocol directly rather than consuming an official host-neutral npm package.
8
+
9
+ > **Status:** `experimental` — real-Weixin live verification is still pending.
10
+ > The adapter is fully implemented and offline-tested, but it must not claim
11
+ > `tested` until the live platform gate passes.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pnpm add @krischoichoi/channel-weixin
17
+ ```
18
+
19
+ Or install the whole bundle:
20
+
21
+ ```bash
22
+ npx @deepseek-ai/dsh plugin --profile web add -w @krischoichoi/dsh-channels@latest
23
+ ```
24
+
25
+ ## Configuration
26
+
27
+ Weixin has no credential or setup-form fields; the only configurable values are
28
+ network/reconnect tuning (all with defaults). Login happens on the mounted
29
+ adapter, not through YAML.
30
+
31
+ ```yaml
32
+ - id: channels-weixin
33
+ name: '@krischoichoi/dsh-channels/weixin'
34
+ inject: [channels, channelControl]
35
+ config:
36
+ enabled: true
37
+ accountId: main
38
+ ilink:
39
+ baseUrl: https://ilinkai.weixin.qq.com # default iLink API base
40
+ cdnBaseUrl: https://novac2c.cdn.weixin.qq.com/c2c # default CDN base
41
+ network:
42
+ timeoutMs: 15000
43
+ longPollTimeoutMs: 35000
44
+ reconnect:
45
+ enabled: true
46
+ baseDelayMs: 2000
47
+ maxDelayMs: 30000
48
+ ```
49
+
50
+ The row above is the one shipped by the bundle patch
51
+ (`packages/channels/cordis.patch.yml`). Two details are easy to get wrong:
52
+
53
+ - `name` addresses the **bundle** subpath (`@krischoichoi/dsh-channels/weixin`), because
54
+ the bundle re-exports every plugin from one direct profile dependency. The
55
+ adapter package `@krischoichoi/channel-weixin` has no `./weixin` subpath of its own.
56
+ - The plugin module itself declares `inject = ['channels']`; the row adds
57
+ `channelControl`. The loader merges the row's `inject` into the plugin's own,
58
+ so the two are additive rather than required to be identical.
59
+
60
+ Login is QR-based: the control plane exposes the QR in Harness Web
61
+ 「设置 → 渠道」, or the adapter can be driven headlessly through
62
+ `beginAuth` / `pollAuth` / `submitAuthInput`.
63
+
64
+ ## Capabilities
65
+
66
+ | Capability | Value |
67
+ | --- | --- |
68
+ | text | ✅ |
69
+ | image | ✅ (Harness-native `saveImage` / `ImageBlock`) |
70
+ | file | ✅ (inbound download + AES decrypt; outbound `uploadMedia` + `sendMedia`) |
71
+ | video | ✅ (inbound download; outbound `sendVideo`) |
72
+ | audio | ❌ (inbound `localData` only; no Tencent 2.4.6 outbound voice path) |
73
+ | markdown / cards / reactions / threads | ❌ |
74
+ | streaming | `buffered` |
75
+
76
+ ## Upstream
77
+
78
+ | Field | Value |
79
+ | --- | --- |
80
+ | Reference | `Tencent/openclaw-weixin` |
81
+ | Strategy | `source-port` (iLink protocol) |
82
+ | Protocol | `weixin-ilink` |
83
+ | Official package | `@tencent-weixin/openclaw-weixin` |
84
+
85
+ ## Development
86
+
87
+ ```bash
88
+ pnpm --filter @krischoichoi/channel-weixin build
89
+ pnpm --filter @krischoichoi/channel-weixin typecheck
90
+ pnpm --filter @krischoichoi/channel-weixin test
91
+ pnpm --filter @krischoichoi/channel-weixin test:live # live verification (requires real Weixin)
92
+ ```
93
+
94
+ ## Related
95
+
96
+ - [Repository root](../../README.md)
97
+ - [Weixin live verification runbook](../../docs/weixin-live-verification-runbook.md)
98
+
99
+ ## License
100
+
101
+ [MIT](../../LICENSE)
@@ -0,0 +1,90 @@
1
+ /**
2
+ * WeixinChannelAdapter — maps the Weixin upstream facade to the stable Channel
3
+ * Contract.
4
+ *
5
+ * This adapter holds ONLY the Channel-contract lifecycle + credential wiring:
6
+ * - start(): wired the upstream host env (secrets/storage/ctx) -> load
7
+ * credential -> unauthenticated if missing; else start the monitor.
8
+ * - send(): map an OutboundMessage to upstream.sendText / sendImage.
9
+ * - beginAuth/pollAuth/submitVerifyCode: QR login via the upstream facade.
10
+ * - stop(): stop the monitor.
11
+ *
12
+ * Endpoint knowledge (iLink endpoints, AES, getuploadurl, CDN query assembly)
13
+ * lives inside the upstream facade layer — this adapter NEVER touches AES or
14
+ * getuploadurl details (plan DoD #1 / §78). The upstream is the
15
+ * `WeixinUpstream` port from `./upstream`.
16
+ */
17
+ import type { AuthChallenge, AuthInput, AuthStatePoll, ChannelAdapter, ChannelAdapterContext, ChannelCapabilities, ChannelHealth, ChannelTarget, OutboundMessage, SendResult } from '@krischoichoi/channel-core';
18
+ import type { WeixinConfig } from './config.js';
19
+ import type { HttpTransport } from './transport.js';
20
+ import type { WeixinUpstream } from './upstream/port.js';
21
+ import { type WeixinManifest } from './manifest.js';
22
+ export interface WeixinAdapterDeps {
23
+ /** Injectable transport (tests); defaults to FetchTransport. */
24
+ transport?: HttpTransport;
25
+ /** Injectable clock (tests). */
26
+ now?: () => number;
27
+ /** Injectable random source for X-WECHAT-UIN (tests). */
28
+ rand?: () => number;
29
+ /**
30
+ * Injectable upstream (tests). Defaults to the real
31
+ * [createWeixinUpstream]. When supplied, the adapter drives this fake as its
32
+ * protocol boundary.
33
+ */
34
+ upstream?: AdapterUpstreamLike;
35
+ }
36
+ /**
37
+ * The upstream surface the adapter actually consumes. Narrower than the full
38
+ * port to keep the fake easy to construct in tests.
39
+ */
40
+ export type AdapterUpstreamLike = (Pick<WeixinUpstream, 'id' | 'beginQrAuth' | 'pollQrAuth' | 'submitVerifyCode' | 'sendText' | 'sendImage' | 'sendFile' | 'sendVideo' | 'downloadImage' | 'downloadFile' | 'downloadAudio' | 'downloadVideo' | 'hasCredential'> & {
41
+ bind(env: import('./upstream/port.js').WeixinUpstreamHostEnv): void;
42
+ loadCredential(): Promise<boolean>;
43
+ startMonitor(): Promise<void>;
44
+ stopMonitor(): Promise<void>;
45
+ startTyping(peer: string): Promise<void>;
46
+ stopTyping(peer: string): Promise<void>;
47
+ getHealthInfo(): Promise<{
48
+ authenticated: boolean;
49
+ connection: string;
50
+ }>;
51
+ get_iLinkClient(): unknown;
52
+ });
53
+ export declare class WeixinAdapter implements ChannelAdapter {
54
+ readonly id = "weixin";
55
+ /** Upstream compatibility manifest (read structurally by `channels doctor`). */
56
+ readonly manifest: WeixinManifest;
57
+ readonly capabilities: ChannelCapabilities;
58
+ private readonly config;
59
+ private readonly upstream;
60
+ private ctx?;
61
+ private started;
62
+ private connected;
63
+ private readonly startedAt;
64
+ constructor(config: WeixinConfig, deps?: WeixinAdapterDeps);
65
+ private requireStarted;
66
+ start(ctx: ChannelAdapterContext): Promise<void>;
67
+ /** Start the getUpdates monitor with the currently-wired credential. */
68
+ private startMonitor;
69
+ stop(): Promise<void>;
70
+ send(target: ChannelTarget, message: OutboundMessage): Promise<SendResult>;
71
+ /** Begin QR login and return the Channel Contract AuthChallenge. */
72
+ beginAuth(): Promise<AuthChallenge>;
73
+ /** Poll the active QR challenge. */
74
+ pollAuth(challenge: AuthChallenge): Promise<AuthStatePoll>;
75
+ /** Submit a phone-verify code for the active QR challenge (adapter-specific). */
76
+ submitVerifyCode(code: string): void;
77
+ /** Generic auth-input hook: maps verification-code inputs to submitVerifyCode. */
78
+ submitAuthInput(_challenge: AuthChallenge, input: AuthInput): void;
79
+ getHealth(): Promise<ChannelHealth>;
80
+ /** Expose the underlying upstream (tests introspection). */
81
+ get_iLinkClient(): unknown;
82
+ /** Best-effort typing start (WX6). Failures never break the main flow. */
83
+ startTyping(conversationId: string): Promise<void>;
84
+ /** Best-effort typing stop (WX6). Failures never break the main flow. */
85
+ stopTyping(conversationId: string): Promise<void>;
86
+ private requireStartedForAuth;
87
+ private emitConnection;
88
+ private onConnectionChange;
89
+ }
90
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
package/lib/adapter.js ADDED
@@ -0,0 +1,247 @@
1
+ import { ChannelError, collectText } from '@krischoichoi/channel-core';
2
+ import { redactMessage } from './ilink/errors.js';
3
+ import { createWeixinUpstream } from './upstream/index.js';
4
+ import { manifest as weixinManifest } from './manifest.js';
5
+ export class WeixinAdapter {
6
+ id = 'weixin';
7
+ /** Upstream compatibility manifest (read structurally by `channels doctor`). */
8
+ manifest = weixinManifest;
9
+ capabilities = {
10
+ text: true,
11
+ image: true,
12
+ file: true,
13
+ audio: false,
14
+ video: true,
15
+ markdown: false,
16
+ cards: false,
17
+ reactions: false,
18
+ threads: false,
19
+ streaming: 'buffered',
20
+ // Directional per-kind media precision.
21
+ // CODE-CONFIRMED:
22
+ // inbound — TencentWeixinUpstream.enrichInboundMedia downloads + AES
23
+ // decrypts image/file/video and download+decrypt+transcodeSilkVoice for
24
+ // audio, setting localData before the monitor emits; failures keep the
25
+ // part with `ingressFailure`.
26
+ // outbound — concrete sendImage/sendFile/sendVideo (uploadMedia +
27
+ // sendMedia over iLink 2.4.6) upload real bytes; audio has no Tencent
28
+ // 2.4.6 outbound path (adapter.send() rejects voice parts with
29
+ // CHANNEL_UNSUPPORTED), so it is `unsupported`.
30
+ // Legacy booleans above stay unchanged (coarse flags).
31
+ media: {
32
+ inbound: {
33
+ image: 'bytes',
34
+ file: 'bytes',
35
+ audio: 'bytes',
36
+ video: 'bytes',
37
+ },
38
+ outbound: {
39
+ image: 'bytes',
40
+ file: 'bytes',
41
+ video: 'bytes',
42
+ audio: 'unsupported',
43
+ },
44
+ },
45
+ };
46
+ config;
47
+ upstream;
48
+ ctx;
49
+ started = false;
50
+ connected = false;
51
+ startedAt;
52
+ constructor(config, deps = {}) {
53
+ this.config = config;
54
+ this.startedAt = deps.now ? deps.now() : Date.now();
55
+ this.upstream = deps.upstream ?? createWeixinUpstream({
56
+ config,
57
+ transport: deps.transport,
58
+ now: deps.now,
59
+ rand: deps.rand,
60
+ });
61
+ }
62
+ requireStarted() {
63
+ if (!this.started || !this.ctx) {
64
+ throw new ChannelError('CHANNEL_NOT_STARTED', 'weixin adapter is not started');
65
+ }
66
+ }
67
+ async start(ctx) {
68
+ if (this.started)
69
+ return;
70
+ this.ctx = ctx;
71
+ this.upstream.bind({
72
+ ctx,
73
+ secrets: ctx.secrets,
74
+ storage: ctx.storage,
75
+ onConnectionChange: (state) => this.onConnectionChange(state),
76
+ });
77
+ // Unauthenticated: allow beginAuth/pollAuth flow to configure later.
78
+ const hasCredential = await this.upstream.loadCredential();
79
+ if (!hasCredential) {
80
+ this.started = true;
81
+ this.emitConnection('disconnected');
82
+ return;
83
+ }
84
+ await this.startMonitor(ctx);
85
+ this.started = true;
86
+ }
87
+ /** Start the getUpdates monitor with the currently-wired credential. */
88
+ async startMonitor(ctx) {
89
+ await this.upstream.startMonitor();
90
+ this.connected = true;
91
+ }
92
+ async stop() {
93
+ if (!this.started)
94
+ return;
95
+ await this.upstream.stopMonitor();
96
+ this.started = false;
97
+ this.connected = false;
98
+ }
99
+ async send(target, message) {
100
+ this.requireStarted();
101
+ const to = target.conversationId;
102
+ const runId = target.runId;
103
+ const media = outboundMediaParts(message.parts);
104
+ try {
105
+ if (hasUnsupportedMediaPart(message.parts)) {
106
+ throw new ChannelError('CHANNEL_UNSUPPORTED', 'weixin outbound media requires local bytes; voice is not supported');
107
+ }
108
+ const text = message.text ?? collectText(message.parts ?? []);
109
+ let result;
110
+ if (media.length > 0 && text) {
111
+ result = await this.upstream.sendText({ to, runId, text });
112
+ }
113
+ for (const part of media) {
114
+ if (part.type === 'image') {
115
+ result = await this.upstream.sendImage({
116
+ to,
117
+ runId,
118
+ data: part.localData,
119
+ mimeType: part.mimeType,
120
+ });
121
+ }
122
+ else if (part.type === 'file') {
123
+ result = await this.upstream.sendFile({
124
+ to,
125
+ runId,
126
+ data: part.localData,
127
+ fileName: part.name ?? 'attachment',
128
+ mimeType: part.mimeType,
129
+ });
130
+ }
131
+ else {
132
+ result = await this.upstream.sendVideo({
133
+ to,
134
+ runId,
135
+ data: part.localData,
136
+ mimeType: part.mimeType,
137
+ });
138
+ }
139
+ }
140
+ return result ?? this.upstream.sendText({ to, runId, text });
141
+ }
142
+ catch (error) {
143
+ const messageText = error instanceof Error ? error.message : String(error);
144
+ throw new ChannelError('CHANNEL_SEND_FAILED', redactMessage(messageText), { cause: error });
145
+ }
146
+ }
147
+ /** Begin QR login and return the Channel Contract AuthChallenge. */
148
+ async beginAuth() {
149
+ this.requireStartedForAuth();
150
+ const ticket = await this.upstream.beginQrAuth();
151
+ return {
152
+ id: ticket.id,
153
+ instruction: ticket.instruction ?? '请使用微信扫描二维码',
154
+ qrUrl: ticket.qrUrl,
155
+ expiresAt: ticket.expiresAt,
156
+ };
157
+ }
158
+ /** Poll the active QR challenge. */
159
+ async pollAuth(challenge) {
160
+ this.requireStartedForAuth();
161
+ const poll = await this.upstream.pollQrAuth(challenge.id);
162
+ if (poll.state === 'authenticated') {
163
+ // Persist the fresh credential on the upstream + (re)wire the sender.
164
+ return { state: 'authenticated', detail: poll.detail };
165
+ }
166
+ return { state: poll.state, detail: poll.detail };
167
+ }
168
+ /** Submit a phone-verify code for the active QR challenge (adapter-specific). */
169
+ submitVerifyCode(code) {
170
+ this.requireStartedForAuth();
171
+ this.upstream.submitVerifyCode(code);
172
+ }
173
+ /** Generic auth-input hook: maps verification-code inputs to submitVerifyCode. */
174
+ submitAuthInput(_challenge, input) {
175
+ if (input.kind === 'verification-code') {
176
+ this.submitVerifyCode(input.value);
177
+ }
178
+ }
179
+ async getHealth() {
180
+ if (!this.started) {
181
+ return { status: 'down', detail: 'weixin adapter is not started', authenticated: false };
182
+ }
183
+ const info = await this.upstream.getHealthInfo();
184
+ const { authenticated } = info;
185
+ if (!authenticated) {
186
+ return { status: 'down', detail: 'weixin not authenticated', connection: 'disconnected', authenticated: false };
187
+ }
188
+ return {
189
+ status: this.connected ? 'ok' : 'degraded',
190
+ detail: this.connected ? 'connected' : 'authenticated but receive loop down',
191
+ connection: this.connected ? 'connected' : 'disconnected',
192
+ authenticated: true,
193
+ };
194
+ }
195
+ /** Expose the underlying upstream (tests introspection). */
196
+ get_iLinkClient() {
197
+ return this.upstream.get_iLinkClient();
198
+ }
199
+ /** Best-effort typing start (WX6). Failures never break the main flow. */
200
+ async startTyping(conversationId) {
201
+ await this.upstream.startTyping(conversationId);
202
+ }
203
+ /** Best-effort typing stop (WX6). Failures never break the main flow. */
204
+ async stopTyping(conversationId) {
205
+ await this.upstream.stopTyping(conversationId);
206
+ }
207
+ requireStartedForAuth() {
208
+ if (!this.ctx) {
209
+ throw new ChannelError('CHANNEL_NOT_STARTED', 'weixin adapter is not started');
210
+ }
211
+ }
212
+ emitConnection(state) {
213
+ if (!this.ctx)
214
+ return;
215
+ void this.ctx
216
+ .emit({
217
+ type: 'connection.changed',
218
+ channel: this.id,
219
+ accountId: this.config.accountId,
220
+ state,
221
+ })
222
+ .catch(() => undefined);
223
+ }
224
+ onConnectionChange(state) {
225
+ this.connected = state === 'connected';
226
+ this.emitConnection(state);
227
+ }
228
+ }
229
+ function outboundMediaParts(parts) {
230
+ if (!parts)
231
+ return [];
232
+ return parts.filter((part) => (part.type === 'image' || part.type === 'file' || part.type === 'video') &&
233
+ part.localData !== undefined &&
234
+ part.localData.byteLength > 0);
235
+ }
236
+ /** True when the message carries unsupported or non-uploadable media. */
237
+ function hasUnsupportedMediaPart(parts) {
238
+ if (!parts)
239
+ return false;
240
+ return parts.some((p) => p.type === 'audio' ||
241
+ ((p.type === 'image' || p.type === 'file' || p.type === 'video') && (!p.localData || p.localData.byteLength === 0)));
242
+ }
243
+ /** Minimal token redaction for send-failure messages (no pattern dependency here). */
244
+ function redactSafe(message) {
245
+ return message.replace(/(token|authorization|aes_key|context_token)/gi, '<redacted>');
246
+ }
247
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Credential store for Weixin accounts.
3
+ *
4
+ * Splits the credential across the two storage boundaries the channel runtime
5
+ * provides:
6
+ * - the **token** (secret) lives in the {@link SecretStore};
7
+ * - the non-secret identity/base-URL metadata lives as JSON in the
8
+ * {@link ChannelStorage}.
9
+ *
10
+ * An account is "configured" only when both halves exist. Tokens are never
11
+ * logged, never embedded in emitted events or exception messages, and never
12
+ * rounded-trip through config dumps.
13
+ */
14
+ import type { SecretStore } from '@krischoichoi/channel-core';
15
+ export interface WeixinAccountCredential {
16
+ /** Bot token — the secret. Never logged. */
17
+ token: string;
18
+ /** Remote Weixin bot identity (ilink_bot_id). */
19
+ ilinkBotId: string;
20
+ /** Weixin user id of the scanning user (when reported). */
21
+ userId?: string;
22
+ /** Effective API base URL (redirect-adjusted). */
23
+ baseUrl: string;
24
+ /** ISO timestamp when the credential was saved. */
25
+ savedAt: string;
26
+ }
27
+ export interface AccountCredentialStoreOptions {
28
+ secrets: SecretStore;
29
+ storage: import('@krischoichoi/channel-core').ChannelStorage;
30
+ /** Local DSH account alias; defaults to `main`. */
31
+ accountId?: string;
32
+ /** Injectable clock (tests). */
33
+ now?: () => number;
34
+ }
35
+ /** Helper: one JSON field of the credential metadata. */
36
+ export declare class AccountCredentialStore {
37
+ private readonly secrets;
38
+ private readonly storage;
39
+ private readonly accountId;
40
+ private readonly now;
41
+ constructor(options: AccountCredentialStoreOptions);
42
+ private metaKey;
43
+ private tokenKey;
44
+ /** Save (or replace) a credential atomically. */
45
+ save(credential: WeixinAccountCredential & {
46
+ token?: string;
47
+ }): Promise<void>;
48
+ /** Load the credential; resolves `undefined` when absent or partial. */
49
+ load(): Promise<WeixinAccountCredential | undefined>;
50
+ /** Delete the credential (both halves). */
51
+ delete(): Promise<void>;
52
+ }
53
+ /** Redacted summary of a credential for diagnostics (never includes the token). */
54
+ export declare function redactCredential(c: WeixinAccountCredential): {
55
+ ilinkBotId: string;
56
+ userId?: string;
57
+ baseUrl: string;
58
+ savedAt: string;
59
+ };
60
+ //# sourceMappingURL=account-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-store.d.ts","sourceRoot":"","sources":["../../src/auth/account-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAG9D,MAAM,WAAW,uBAAuB;IACtC,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,WAAW,CAAC;IACrB,OAAO,EAAE,OAAO,4BAA4B,EAAE,cAAc,CAAC;IAC7D,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAcD,yDAAyD;AACzD,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsD;IAC9E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;gBAEvB,OAAO,EAAE,6BAA6B;IAOlD,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,QAAQ;IAIhB,iDAAiD;IAC3C,IAAI,CAAC,UAAU,EAAE,uBAAuB,GAAG;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAenF,wEAAwE;IAClE,IAAI,IAAI,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAAC;IAsB1D,2CAA2C;IACrC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAG9B;AAED,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,uBAAuB,GAAG;IAC5D,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,CAOA"}
@@ -0,0 +1,84 @@
1
+ import { z } from 'zod';
2
+ /** Storage key prefix for the non-secret credential metadata. */
3
+ const META_KEY_PREFIX = 'weixin:credential:';
4
+ const credentialMetadataSchema = z.object({
5
+ ilinkBotId: z.string().trim().min(1),
6
+ userId: z.string().trim().min(1).optional(),
7
+ baseUrl: z.string().url().refine((value) => new URL(value).protocol === 'https:', {
8
+ message: 'baseUrl must use https',
9
+ }),
10
+ savedAt: z.string().trim().min(1),
11
+ });
12
+ /** Helper: one JSON field of the credential metadata. */
13
+ export class AccountCredentialStore {
14
+ secrets;
15
+ storage;
16
+ accountId;
17
+ now;
18
+ constructor(options) {
19
+ this.secrets = options.secrets;
20
+ this.storage = options.storage;
21
+ this.accountId = options.accountId ?? 'main';
22
+ this.now = options.now ?? Date.now;
23
+ }
24
+ metaKey() {
25
+ return `${META_KEY_PREFIX}${this.accountId}`;
26
+ }
27
+ tokenKey() {
28
+ return `weixin:token:${this.accountId}`;
29
+ }
30
+ /** Save (or replace) a credential atomically. */
31
+ async save(credential) {
32
+ const meta = {
33
+ ilinkBotId: credential.ilinkBotId,
34
+ userId: credential.userId,
35
+ baseUrl: credential.baseUrl,
36
+ savedAt: credential.savedAt ?? new Date(this.now()).toISOString(),
37
+ };
38
+ const parsed = credentialMetadataSchema.safeParse(meta);
39
+ if (!parsed.success) {
40
+ throw new TypeError('invalid Weixin credential metadata');
41
+ }
42
+ await this.secrets.set(this.tokenKey(), credential.token);
43
+ await this.storage.set(this.metaKey(), JSON.stringify(parsed.data));
44
+ }
45
+ /** Load the credential; resolves `undefined` when absent or partial. */
46
+ async load() {
47
+ const token = await this.secrets.get(this.tokenKey());
48
+ const rawMeta = await this.storage.get(this.metaKey());
49
+ if (!token || !rawMeta)
50
+ return undefined;
51
+ try {
52
+ const parsedJson = JSON.parse(rawMeta);
53
+ const parsed = credentialMetadataSchema.safeParse(parsedJson);
54
+ if (!parsed.success)
55
+ return undefined;
56
+ const meta = parsed.data;
57
+ return {
58
+ token,
59
+ ilinkBotId: meta.ilinkBotId,
60
+ userId: meta.userId,
61
+ baseUrl: meta.baseUrl,
62
+ savedAt: meta.savedAt,
63
+ };
64
+ }
65
+ catch {
66
+ // Corrupt metadata — treat as unauthenticated.
67
+ return undefined;
68
+ }
69
+ }
70
+ /** Delete the credential (both halves). */
71
+ async delete() {
72
+ await Promise.all([this.secrets.delete(this.tokenKey()), this.storage.delete(this.metaKey())]);
73
+ }
74
+ }
75
+ /** Redacted summary of a credential for diagnostics (never includes the token). */
76
+ export function redactCredential(c) {
77
+ return {
78
+ ilinkBotId: c.ilinkBotId,
79
+ userId: c.userId,
80
+ baseUrl: c.baseUrl,
81
+ savedAt: c.savedAt,
82
+ };
83
+ }
84
+ //# sourceMappingURL=account-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-store.js","sourceRoot":"","sources":["../../src/auth/account-store.ts"],"names":[],"mappings":"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"}