@nextclaw/channel-extension-weixin 0.1.1

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.
@@ -0,0 +1,192 @@
1
+ import { basename, extname } from "node:path";
2
+ import { readFile } from "node:fs/promises";
3
+ //#region src/services/weixin-media-part-reader.service.ts
4
+ const IMAGE_FILE_EXTENSIONS = new Set([
5
+ ".avif",
6
+ ".bmp",
7
+ ".gif",
8
+ ".heic",
9
+ ".jpeg",
10
+ ".jpg",
11
+ ".png",
12
+ ".svg",
13
+ ".webp"
14
+ ]);
15
+ function readString(value) {
16
+ if (typeof value !== "string") return;
17
+ return value.trim() || void 0;
18
+ }
19
+ function normalizeMimeType(value) {
20
+ if (!value) return;
21
+ const [mimeType] = value.split(";", 1);
22
+ return mimeType?.trim().toLowerCase() || void 0;
23
+ }
24
+ function mimeTypeToExtension(mimeType) {
25
+ switch (mimeType) {
26
+ case "image/png": return ".png";
27
+ case "image/jpeg": return ".jpg";
28
+ case "image/gif": return ".gif";
29
+ case "image/webp": return ".webp";
30
+ case "image/svg+xml": return ".svg";
31
+ case "application/pdf": return ".pdf";
32
+ default: return ".bin";
33
+ }
34
+ }
35
+ function isImageMimeType(mimeType) {
36
+ return Boolean(mimeType?.startsWith("image/"));
37
+ }
38
+ function readPngDimensions(bytes) {
39
+ if (bytes.byteLength < 24) return null;
40
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
41
+ const signature = view.getUint32(0);
42
+ const ihdr = view.getUint32(12);
43
+ if (signature !== 2303741511 || ihdr !== 1229472850) return null;
44
+ return {
45
+ width: view.getUint32(16),
46
+ height: view.getUint32(20)
47
+ };
48
+ }
49
+ function readGifDimensions(bytes) {
50
+ if (bytes.byteLength < 10) return null;
51
+ const header = Buffer.from(bytes.subarray(0, 6)).toString("ascii");
52
+ if (header !== "GIF87a" && header !== "GIF89a") return null;
53
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
54
+ return {
55
+ width: view.getUint16(6, true),
56
+ height: view.getUint16(8, true)
57
+ };
58
+ }
59
+ function readJpegDimensions(bytes) {
60
+ if (bytes.byteLength < 4 || bytes[0] !== 255 || bytes[1] !== 216) return null;
61
+ let offset = 2;
62
+ while (offset + 9 < bytes.byteLength) {
63
+ if (bytes[offset] !== 255) {
64
+ offset += 1;
65
+ continue;
66
+ }
67
+ const marker = bytes[offset + 1];
68
+ offset += 2;
69
+ if (marker === 216 || marker === 217) continue;
70
+ if (offset + 2 > bytes.byteLength) return null;
71
+ const length = bytes[offset] << 8 | bytes[offset + 1];
72
+ if (length < 2 || offset + length > bytes.byteLength) return null;
73
+ if (marker >= 192 && marker <= 195 || marker >= 197 && marker <= 199 || marker >= 201 && marker <= 203 || marker >= 205 && marker <= 207) return {
74
+ height: bytes[offset + 3] << 8 | bytes[offset + 4],
75
+ width: bytes[offset + 5] << 8 | bytes[offset + 6]
76
+ };
77
+ offset += length;
78
+ }
79
+ return null;
80
+ }
81
+ function readWebpDimensions(bytes) {
82
+ if (bytes.byteLength < 30) return null;
83
+ const header = Buffer.from(bytes.subarray(0, 4)).toString("ascii");
84
+ const webp = Buffer.from(bytes.subarray(8, 12)).toString("ascii");
85
+ if (header !== "RIFF" || webp !== "WEBP") return null;
86
+ const chunk = Buffer.from(bytes.subarray(12, 16)).toString("ascii");
87
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
88
+ if (chunk === "VP8X" && bytes.byteLength >= 30) return {
89
+ width: 1 + (bytes[24] | bytes[25] << 8 | bytes[26] << 16),
90
+ height: 1 + (bytes[27] | bytes[28] << 8 | bytes[29] << 16)
91
+ };
92
+ if (chunk === "VP8 " && bytes.byteLength >= 30) return {
93
+ width: view.getUint16(26, true) & 16383,
94
+ height: view.getUint16(28, true) & 16383
95
+ };
96
+ if (chunk === "VP8L" && bytes.byteLength >= 25) {
97
+ const bits = bytes[21] | bytes[22] << 8 | bytes[23] << 16 | bytes[24] << 24;
98
+ return {
99
+ width: (bits & 16383) + 1,
100
+ height: (bits >> 14 & 16383) + 1
101
+ };
102
+ }
103
+ return null;
104
+ }
105
+ function readImageDimensions(bytes) {
106
+ return readPngDimensions(bytes) ?? readGifDimensions(bytes) ?? readJpegDimensions(bytes) ?? readWebpDimensions(bytes);
107
+ }
108
+ function readFileNameFromUrl(url) {
109
+ try {
110
+ return basename(new URL(url).pathname).trim() || void 0;
111
+ } catch {
112
+ return basename(url).trim() || void 0;
113
+ }
114
+ }
115
+ function resolveFetchUrl(url) {
116
+ try {
117
+ return new URL(url).toString();
118
+ } catch {
119
+ const endpoint = process.env.NEXTCLAW_EXTENSION_ENDPOINT?.trim();
120
+ if (!endpoint) return url;
121
+ return new URL(url, endpoint.endsWith("/") ? endpoint : `${endpoint}/`).toString();
122
+ }
123
+ }
124
+ var WeixinMediaPartReader = class {
125
+ read = async (target, part) => {
126
+ if (part.contentBase64) {
127
+ const bytes = Buffer.from(part.contentBase64, "base64");
128
+ const mimeType = normalizeMimeType(part.mimeType);
129
+ const fileName = this.resolveFileName(part, mimeType);
130
+ const imageDimensions = this.resolveImageDimensions(bytes, fileName, mimeType);
131
+ return {
132
+ bytes,
133
+ fileName,
134
+ mimeType,
135
+ isImage: this.isImageFile(fileName, mimeType),
136
+ imageWidth: imageDimensions?.width,
137
+ imageHeight: imageDimensions?.height
138
+ };
139
+ }
140
+ if (part.assetUri) {
141
+ const contentPath = target.resolveAssetContentPath?.(part.assetUri)?.trim();
142
+ if (contentPath) {
143
+ const bytes = await readFile(contentPath);
144
+ const mimeType = normalizeMimeType(part.mimeType);
145
+ const fileName = this.resolveFileName(part, mimeType, basename(contentPath));
146
+ const imageDimensions = this.resolveImageDimensions(bytes, fileName, mimeType);
147
+ return {
148
+ bytes,
149
+ fileName,
150
+ mimeType,
151
+ isImage: this.isImageFile(fileName, mimeType),
152
+ imageWidth: imageDimensions?.width,
153
+ imageHeight: imageDimensions?.height
154
+ };
155
+ }
156
+ if (!part.url) throw new Error(`weixin send failed: asset "${part.assetUri}" is not readable`);
157
+ }
158
+ if (part.url) {
159
+ const response = await fetch(resolveFetchUrl(part.url));
160
+ if (!response.ok) throw new Error(`weixin send failed: unable to download ${part.url} (${response.status})`);
161
+ const bytes = new Uint8Array(await response.arrayBuffer());
162
+ const mimeType = normalizeMimeType(part.mimeType) ?? normalizeMimeType(response.headers.get("content-type") ?? void 0);
163
+ const fileName = this.resolveFileName(part, mimeType, readFileNameFromUrl(part.url));
164
+ const imageDimensions = this.resolveImageDimensions(bytes, fileName, mimeType);
165
+ return {
166
+ bytes,
167
+ fileName,
168
+ mimeType,
169
+ isImage: this.isImageFile(fileName, mimeType),
170
+ imageWidth: imageDimensions?.width,
171
+ imageHeight: imageDimensions?.height
172
+ };
173
+ }
174
+ throw new Error("weixin send failed: file part is missing content");
175
+ };
176
+ resolveFileName = (part, mimeType, fallbackName) => {
177
+ const explicitName = readString(part.name);
178
+ if (explicitName) return explicitName;
179
+ if (fallbackName) return fallbackName;
180
+ return `attachment${mimeTypeToExtension(mimeType)}`;
181
+ };
182
+ isImageFile = (fileName, mimeType) => {
183
+ if (isImageMimeType(mimeType)) return true;
184
+ return IMAGE_FILE_EXTENSIONS.has(extname(fileName).toLowerCase());
185
+ };
186
+ resolveImageDimensions = (bytes, fileName, mimeType) => {
187
+ if (!this.isImageFile(fileName, mimeType)) return null;
188
+ return readImageDimensions(bytes);
189
+ };
190
+ };
191
+ //#endregion
192
+ export { WeixinMediaPartReader };
@@ -0,0 +1,141 @@
1
+ import { sendWeixinFileMessage, sendWeixinImageMessage } from "../utils/weixin-media.utils.js";
2
+ import { WeixinMediaPartReader } from "./weixin-media-part-reader.service.js";
3
+ import "@nextclaw/ncp-toolkit";
4
+ import { NcpEventType } from "@nextclaw/ncp";
5
+ //#region src/services/weixin-reply-chat.service.ts
6
+ const TERMINAL_NCP_EVENT_TYPES = new Set([
7
+ NcpEventType.MessageCompleted,
8
+ NcpEventType.MessageFailed,
9
+ NcpEventType.RunFinished,
10
+ NcpEventType.RunError
11
+ ]);
12
+ var NcpEventQueue = class {
13
+ events = [];
14
+ waiting = null;
15
+ closed = false;
16
+ push = (event) => {
17
+ if (this.closed) return;
18
+ const waiting = this.waiting;
19
+ if (waiting) {
20
+ this.waiting = null;
21
+ waiting({
22
+ value: event,
23
+ done: false
24
+ });
25
+ return;
26
+ }
27
+ this.events.push(event);
28
+ };
29
+ close = () => {
30
+ this.closed = true;
31
+ const waiting = this.waiting;
32
+ if (waiting) {
33
+ this.waiting = null;
34
+ waiting({
35
+ value: void 0,
36
+ done: true
37
+ });
38
+ }
39
+ };
40
+ [Symbol.asyncIterator] = () => ({ next: async () => {
41
+ const event = this.events.shift();
42
+ if (event) return {
43
+ value: event,
44
+ done: false
45
+ };
46
+ if (this.closed) return {
47
+ value: void 0,
48
+ done: true
49
+ };
50
+ return await new Promise((resolve) => {
51
+ this.waiting = resolve;
52
+ });
53
+ } });
54
+ };
55
+ function renderPartText(part) {
56
+ switch (part.type) {
57
+ case "text":
58
+ case "rich-text": return part.text;
59
+ case "source": return [
60
+ part.title ?? "",
61
+ part.url ?? "",
62
+ part.snippet ?? ""
63
+ ].filter(Boolean).join("\n");
64
+ case "card": return (typeof part.payload.title === "string" ? part.payload.title.trim() : "") || JSON.stringify(part.payload);
65
+ case "action": return part.label.trim();
66
+ case "step-start": return part.title?.trim() ?? "";
67
+ default: return "";
68
+ }
69
+ }
70
+ var WeixinReplyChat = class {
71
+ mediaPartReader = new WeixinMediaPartReader();
72
+ constructor(deps) {
73
+ this.deps = deps;
74
+ }
75
+ startTyping = async (target) => {
76
+ const account = this.deps.resolveAccount(target);
77
+ const contextToken = this.deps.resolveContextToken(target, account.accountId);
78
+ if (!contextToken) return;
79
+ await this.deps.typingController.start({
80
+ accountId: account.accountId,
81
+ userId: target.conversationId,
82
+ contextToken,
83
+ baseUrl: account.baseUrl,
84
+ token: account.token
85
+ });
86
+ };
87
+ stopTyping = async (target) => {
88
+ const account = this.deps.resolveAccount(target);
89
+ await this.deps.typingController.stop({
90
+ accountId: account.accountId,
91
+ userId: target.conversationId
92
+ });
93
+ };
94
+ sendError = async (target, message) => {
95
+ if (message.trim()) await this.sendText(target, message);
96
+ };
97
+ sendPart = async (target, part) => {
98
+ if (part.type === "file") {
99
+ await this.sendFilePart(target, part);
100
+ return;
101
+ }
102
+ const text = renderPartText(part);
103
+ if (text.trim()) await this.sendText(target, text);
104
+ };
105
+ sendText = async (target, text) => {
106
+ const account = this.deps.resolveAccount(target);
107
+ await this.deps.sendText({
108
+ account,
109
+ conversationId: target.conversationId,
110
+ text,
111
+ contextToken: this.deps.resolveContextToken(target, account.accountId)
112
+ });
113
+ };
114
+ sendFilePart = async (target, part) => {
115
+ const media = await this.mediaPartReader.read(target, part);
116
+ const account = this.deps.resolveAccount(target);
117
+ const contextToken = this.deps.resolveContextToken(target, account.accountId);
118
+ if (media.isImage) {
119
+ await sendWeixinImageMessage({
120
+ baseUrl: account.baseUrl,
121
+ token: account.token,
122
+ toUserId: target.conversationId,
123
+ bytes: media.bytes,
124
+ width: media.imageWidth,
125
+ height: media.imageHeight,
126
+ contextToken
127
+ });
128
+ return;
129
+ }
130
+ await sendWeixinFileMessage({
131
+ baseUrl: account.baseUrl,
132
+ token: account.token,
133
+ toUserId: target.conversationId,
134
+ fileName: media.fileName,
135
+ bytes: media.bytes,
136
+ contextToken
137
+ });
138
+ };
139
+ };
140
+ //#endregion
141
+ export { NcpEventQueue, TERMINAL_NCP_EVENT_TYPES, WeixinReplyChat };
@@ -0,0 +1,109 @@
1
+ //#region src/services/weixin-typing-controller.service.ts
2
+ function buildTypingKey(accountId, userId) {
3
+ return `${accountId}:${userId}`;
4
+ }
5
+ var WeixinTypingController = class {
6
+ heartbeatMs;
7
+ ticketTtlMs;
8
+ ticketCache = /* @__PURE__ */ new Map();
9
+ activeSessions = /* @__PURE__ */ new Map();
10
+ sessionSequences = /* @__PURE__ */ new Map();
11
+ fetchTicket;
12
+ sendTyping;
13
+ constructor(options) {
14
+ const { fetchTicket, heartbeatMs, sendTyping, ticketTtlMs } = options;
15
+ this.heartbeatMs = Math.max(1e3, Math.trunc(heartbeatMs ?? 5e3));
16
+ this.ticketTtlMs = Math.max(this.heartbeatMs, Math.trunc(ticketTtlMs ?? 1440 * 60 * 1e3));
17
+ this.fetchTicket = fetchTicket;
18
+ this.sendTyping = sendTyping;
19
+ }
20
+ start = async (runtime) => {
21
+ const key = buildTypingKey(runtime.accountId, runtime.userId);
22
+ const sequence = this.bumpSequence(key);
23
+ await this.clearActiveSession({
24
+ accountId: runtime.accountId,
25
+ userId: runtime.userId,
26
+ sendCancel: false
27
+ });
28
+ const ticket = await this.getTicket(runtime);
29
+ if (!ticket || this.sessionSequences.get(key) !== sequence) return;
30
+ await this.sendTypingSafe({
31
+ ...runtime,
32
+ ticket,
33
+ status: 1
34
+ });
35
+ if (this.sessionSequences.get(key) !== sequence) return;
36
+ const heartbeat = setInterval(() => {
37
+ const active = this.activeSessions.get(key);
38
+ if (!active || active.sequence !== sequence) return;
39
+ this.sendTypingSafe({
40
+ ...active.runtime,
41
+ ticket: active.ticket,
42
+ status: 1
43
+ });
44
+ }, this.heartbeatMs);
45
+ this.activeSessions.set(key, {
46
+ heartbeat,
47
+ ticket,
48
+ runtime,
49
+ sequence
50
+ });
51
+ };
52
+ stop = async (params) => {
53
+ const key = buildTypingKey(params.accountId, params.userId);
54
+ this.bumpSequence(key);
55
+ await this.clearActiveSession(params);
56
+ };
57
+ stopAll = async () => {
58
+ const sessions = Array.from(this.activeSessions.values());
59
+ this.activeSessions.clear();
60
+ for (const session of sessions) {
61
+ this.bumpSequence(buildTypingKey(session.runtime.accountId, session.runtime.userId));
62
+ clearInterval(session.heartbeat);
63
+ }
64
+ await Promise.allSettled(sessions.map(async (session) => {
65
+ await this.sendTypingSafe({
66
+ ...session.runtime,
67
+ ticket: session.ticket,
68
+ status: 2
69
+ });
70
+ }));
71
+ };
72
+ bumpSequence = (key) => {
73
+ const next = (this.sessionSequences.get(key) ?? 0) + 1;
74
+ this.sessionSequences.set(key, next);
75
+ return next;
76
+ };
77
+ clearActiveSession = async (params) => {
78
+ const key = buildTypingKey(params.accountId, params.userId);
79
+ const active = this.activeSessions.get(key);
80
+ if (!active) return;
81
+ clearInterval(active.heartbeat);
82
+ this.activeSessions.delete(key);
83
+ if (params.sendCancel === false) return;
84
+ await this.sendTypingSafe({
85
+ ...active.runtime,
86
+ ticket: active.ticket,
87
+ status: 2
88
+ });
89
+ };
90
+ getTicket = async (runtime) => {
91
+ const key = buildTypingKey(runtime.accountId, runtime.userId);
92
+ const cached = this.ticketCache.get(key);
93
+ if (cached && cached.expiresAtMs > Date.now()) return cached.ticket;
94
+ const ticket = (await this.fetchTicket(runtime))?.trim();
95
+ if (!ticket) return;
96
+ this.ticketCache.set(key, {
97
+ ticket,
98
+ expiresAtMs: Date.now() + this.ticketTtlMs
99
+ });
100
+ return ticket;
101
+ };
102
+ sendTypingSafe = async (params) => {
103
+ try {
104
+ await this.sendTyping(params);
105
+ } catch {}
106
+ };
107
+ };
108
+ //#endregion
109
+ export { WeixinTypingController };
@@ -0,0 +1,19 @@
1
+ //#region src/stores/weixin-account.store.d.ts
2
+ type StoredWeixinAccount = {
3
+ accountId: string;
4
+ token: string;
5
+ baseUrl?: string;
6
+ userId?: string;
7
+ savedAt?: string;
8
+ };
9
+ type WeixinAccountStore = {
10
+ listAccountIds: () => string[];
11
+ loadAccount: (accountId: string) => StoredWeixinAccount | null;
12
+ saveAccount: (account: StoredWeixinAccount) => void;
13
+ deleteAccount: (accountId: string) => void;
14
+ loadCursor: (accountId: string) => string | undefined;
15
+ saveCursor: (accountId: string, cursor: string | undefined) => void;
16
+ deleteCursor: (accountId: string) => void;
17
+ };
18
+ //#endregion
19
+ export { WeixinAccountStore };
@@ -0,0 +1,56 @@
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join, resolve } from "node:path";
4
+ //#region src/stores/weixin-account.store.ts
5
+ function resolveNextclawHome() {
6
+ const override = process.env.NEXTCLAW_HOME?.trim();
7
+ return resolve(override || join(homedir(), ".nextclaw"));
8
+ }
9
+ function resolveWeixinDataDir() {
10
+ return join(resolveNextclawHome(), "channels", "weixin");
11
+ }
12
+ function resolveAccountsDir() {
13
+ return join(resolveWeixinDataDir(), "accounts");
14
+ }
15
+ function resolveCursorsDir() {
16
+ return join(resolveWeixinDataDir(), "cursors");
17
+ }
18
+ function toFileName(accountId) {
19
+ return `${encodeURIComponent(accountId)}.json`;
20
+ }
21
+ function readJsonFile(filePath) {
22
+ if (!existsSync(filePath)) return null;
23
+ try {
24
+ return JSON.parse(readFileSync(filePath, "utf-8"));
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+ var FileWeixinAccountStore = class {
30
+ listAccountIds = () => {
31
+ if (!existsSync(resolveAccountsDir())) return [];
32
+ return readdirSync(resolveAccountsDir()).filter((entry) => entry.endsWith(".json")).map((entry) => decodeURIComponent(entry.slice(0, -5))).filter(Boolean);
33
+ };
34
+ loadAccount = (accountId) => {
35
+ return readJsonFile(join(resolveAccountsDir(), toFileName(accountId)));
36
+ };
37
+ saveAccount = (account) => {
38
+ mkdirSync(resolveAccountsDir(), { recursive: true });
39
+ writeFileSync(join(resolveAccountsDir(), toFileName(account.accountId)), JSON.stringify(account, null, 2));
40
+ };
41
+ deleteAccount = (accountId) => {
42
+ rmSync(join(resolveAccountsDir(), toFileName(accountId)), { force: true });
43
+ };
44
+ loadCursor = (accountId) => {
45
+ return readJsonFile(join(resolveCursorsDir(), toFileName(accountId)))?.cursor?.trim() || void 0;
46
+ };
47
+ saveCursor = (accountId, cursor) => {
48
+ mkdirSync(resolveCursorsDir(), { recursive: true });
49
+ writeFileSync(join(resolveCursorsDir(), toFileName(accountId)), JSON.stringify({ cursor: cursor ?? "" }, null, 2));
50
+ };
51
+ deleteCursor = (accountId) => {
52
+ rmSync(join(resolveCursorsDir(), toFileName(accountId)), { force: true });
53
+ };
54
+ };
55
+ //#endregion
56
+ export { FileWeixinAccountStore };
@@ -0,0 +1,43 @@
1
+ import { NcpEndpointEvent } from "@nextclaw/ncp";
2
+ import { ChannelSubmittedAttachment } from "@nextclaw/extension-sdk";
3
+
4
+ //#region src/types/weixin-extension.types.d.ts
5
+ type WeixinAccountConfig = {
6
+ enabled?: boolean;
7
+ baseUrl?: string;
8
+ allowFrom?: string[];
9
+ };
10
+ type WeixinChannelConfig = {
11
+ enabled?: boolean;
12
+ defaultAccountId?: string;
13
+ baseUrl?: string;
14
+ pollTimeoutMs?: number;
15
+ allowFrom?: string[];
16
+ accounts?: Record<string, WeixinAccountConfig>;
17
+ };
18
+ type WeixinInboundMessage = {
19
+ conversationId: string;
20
+ senderId: string;
21
+ text: string;
22
+ attachments?: ChannelSubmittedAttachment[];
23
+ accountId?: string;
24
+ contextToken?: string;
25
+ raw?: unknown;
26
+ };
27
+ type WeixinRuntimeAccount = {
28
+ accountId: string;
29
+ token: string;
30
+ enabled: boolean;
31
+ baseUrl: string;
32
+ pollTimeoutMs: number;
33
+ allowFrom: string[];
34
+ };
35
+ type WeixinChannelAdapter = {
36
+ configure: (config: WeixinChannelConfig) => Promise<void>;
37
+ start: () => Promise<void>;
38
+ stop: () => Promise<void>;
39
+ onMessage: (handler: (message: WeixinInboundMessage) => void | Promise<void>) => () => void;
40
+ sendNcpEvent: (event: NcpEndpointEvent) => Promise<void>;
41
+ };
42
+ //#endregion
43
+ export { WeixinAccountConfig, WeixinChannelAdapter, WeixinChannelConfig, WeixinInboundMessage, WeixinRuntimeAccount };
@@ -0,0 +1,69 @@
1
+ //#region src/utils/weixin-config.utils.d.ts
2
+ declare const WEIXIN_EXTENSION_ID = "nextclaw-channel-extension-weixin";
3
+ declare const WEIXIN_CHANNEL_ID = "weixin";
4
+ declare const DEFAULT_WEIXIN_BASE_URL = "https://ilinkai.weixin.qq.com";
5
+ declare const WEIXIN_CHANNEL_CONFIG_SCHEMA: {
6
+ readonly type: "object";
7
+ readonly additionalProperties: false;
8
+ readonly properties: {
9
+ readonly enabled: {
10
+ readonly type: "boolean";
11
+ };
12
+ readonly defaultAccountId: {
13
+ readonly type: "string";
14
+ };
15
+ readonly baseUrl: {
16
+ readonly type: "string";
17
+ };
18
+ readonly pollTimeoutMs: {
19
+ readonly type: "number";
20
+ };
21
+ readonly allowFrom: {
22
+ readonly type: "array";
23
+ readonly items: {
24
+ readonly type: "string";
25
+ };
26
+ };
27
+ readonly accounts: {
28
+ readonly type: "object";
29
+ readonly additionalProperties: {
30
+ readonly type: "object";
31
+ readonly additionalProperties: false;
32
+ readonly properties: {
33
+ readonly enabled: {
34
+ readonly type: "boolean";
35
+ };
36
+ readonly baseUrl: {
37
+ readonly type: "string";
38
+ };
39
+ readonly allowFrom: {
40
+ readonly type: "array";
41
+ readonly items: {
42
+ readonly type: "string";
43
+ };
44
+ };
45
+ };
46
+ };
47
+ };
48
+ };
49
+ };
50
+ declare const WEIXIN_CHANNEL_CONFIG_UI_HINTS: {
51
+ readonly enabled: {
52
+ readonly label: "Enabled";
53
+ };
54
+ readonly defaultAccountId: {
55
+ readonly label: "Default Account ID";
56
+ };
57
+ readonly baseUrl: {
58
+ readonly label: "API Base URL";
59
+ };
60
+ readonly pollTimeoutMs: {
61
+ readonly label: "Long Poll Timeout (ms)";
62
+ readonly advanced: true;
63
+ };
64
+ readonly allowFrom: {
65
+ readonly label: "Allow From";
66
+ };
67
+ };
68
+ //#endregion
69
+ export { DEFAULT_WEIXIN_BASE_URL, WEIXIN_CHANNEL_CONFIG_SCHEMA, WEIXIN_CHANNEL_CONFIG_UI_HINTS, WEIXIN_CHANNEL_ID, WEIXIN_EXTENSION_ID };