@lark-apaas/aily-web-sdk 0.0.2-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +13 -0
- package/README.local-test.md +202 -0
- package/README.md +70 -0
- package/dist/index.cjs +419 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +175 -0
- package/dist/index.d.ts +175 -0
- package/dist/index.js +391 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/constants.ts
|
|
5
|
+
var MESSAGE_BIZ_ID = "cui-sdk-message";
|
|
6
|
+
var IFRAME_BASE_URL = {
|
|
7
|
+
production: "https://aily.feishu.cn/cui",
|
|
8
|
+
staging: "https://aily.feishu-pre.cn/cui",
|
|
9
|
+
boe: "https://aily.feishu-boe.cn/cui",
|
|
10
|
+
development: "http://localhost:8080/cui"
|
|
11
|
+
};
|
|
12
|
+
function getIframeBaseURL(env) {
|
|
13
|
+
if (env && IFRAME_BASE_URL[env]) {
|
|
14
|
+
return IFRAME_BASE_URL[env];
|
|
15
|
+
}
|
|
16
|
+
const hostname = typeof window !== "undefined" ? window.location.hostname : "";
|
|
17
|
+
if (hostname.includes("aiforce-boe")) {
|
|
18
|
+
return IFRAME_BASE_URL.boe;
|
|
19
|
+
}
|
|
20
|
+
if (hostname.includes("aiforce-pre")) {
|
|
21
|
+
return IFRAME_BASE_URL.staging;
|
|
22
|
+
}
|
|
23
|
+
if (hostname.includes("localhost") || hostname.includes("127.0.0.1")) {
|
|
24
|
+
return IFRAME_BASE_URL.development;
|
|
25
|
+
}
|
|
26
|
+
return IFRAME_BASE_URL.production;
|
|
27
|
+
}
|
|
28
|
+
__name(getIframeBaseURL, "getIframeBaseURL");
|
|
29
|
+
var MESSAGE_TIMEOUT = 30 * 1e3;
|
|
30
|
+
var CHANNEL_TYPE = "MIAODA_CUI_SDK";
|
|
31
|
+
var ANONYMOUS_CHANNEL_TYPE = "MIAODA_ANONYMOUS_CUI_SDK";
|
|
32
|
+
var ALLOWED_ORIGINS = [
|
|
33
|
+
"https://aily.feishu.cn",
|
|
34
|
+
"https://aily.feishu-pre.cn",
|
|
35
|
+
"https://aily.feishu-boe.cn",
|
|
36
|
+
"http://localhost:3000",
|
|
37
|
+
"http://localhost:5173",
|
|
38
|
+
"http://localhost:8080"
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
// src/device-detector.ts
|
|
42
|
+
var MOBILE_BREAKPOINT = 768;
|
|
43
|
+
var DeviceDetector = class {
|
|
44
|
+
static {
|
|
45
|
+
__name(this, "DeviceDetector");
|
|
46
|
+
}
|
|
47
|
+
mediaQuery;
|
|
48
|
+
currentDevice;
|
|
49
|
+
callback = null;
|
|
50
|
+
handleChange = null;
|
|
51
|
+
constructor() {
|
|
52
|
+
this.mediaQuery = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
|
53
|
+
this.currentDevice = window.innerWidth < MOBILE_BREAKPOINT ? "mobile" : "pc";
|
|
54
|
+
}
|
|
55
|
+
getDevice() {
|
|
56
|
+
return this.currentDevice;
|
|
57
|
+
}
|
|
58
|
+
observe(callback) {
|
|
59
|
+
this.callback = callback;
|
|
60
|
+
this.handleChange = () => {
|
|
61
|
+
const newDevice = window.innerWidth < MOBILE_BREAKPOINT ? "mobile" : "pc";
|
|
62
|
+
if (newDevice !== this.currentDevice) {
|
|
63
|
+
this.currentDevice = newDevice;
|
|
64
|
+
this.callback?.(newDevice);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
this.mediaQuery.addEventListener("change", this.handleChange);
|
|
68
|
+
}
|
|
69
|
+
destroy() {
|
|
70
|
+
if (this.handleChange) {
|
|
71
|
+
this.mediaQuery.removeEventListener("change", this.handleChange);
|
|
72
|
+
this.handleChange = null;
|
|
73
|
+
}
|
|
74
|
+
this.callback = null;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// src/message-bridge.ts
|
|
79
|
+
var MessageBridge = class {
|
|
80
|
+
static {
|
|
81
|
+
__name(this, "MessageBridge");
|
|
82
|
+
}
|
|
83
|
+
iframe = null;
|
|
84
|
+
requestId = 0;
|
|
85
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
86
|
+
eventHandlers = /* @__PURE__ */ new Map();
|
|
87
|
+
targetOrigin;
|
|
88
|
+
constructor(targetOrigin) {
|
|
89
|
+
this.targetOrigin = targetOrigin;
|
|
90
|
+
this.handleMessage = this.handleMessage.bind(this);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Attach to an iframe and start listening for messages
|
|
94
|
+
*/
|
|
95
|
+
attach(iframe) {
|
|
96
|
+
this.iframe = iframe;
|
|
97
|
+
window.addEventListener("message", this.handleMessage);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Detach from iframe and clean up
|
|
101
|
+
*/
|
|
102
|
+
detach() {
|
|
103
|
+
window.removeEventListener("message", this.handleMessage);
|
|
104
|
+
this.iframe = null;
|
|
105
|
+
this.pendingRequests.forEach(({ reject }) => {
|
|
106
|
+
reject(new Error("MessageBridge detached"));
|
|
107
|
+
});
|
|
108
|
+
this.pendingRequests.clear();
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Send a message to the iframe and wait for response
|
|
112
|
+
*/
|
|
113
|
+
send(type, data) {
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
if (!this.iframe?.contentWindow) {
|
|
116
|
+
reject(new Error("Iframe not attached"));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
this.requestId += 1;
|
|
120
|
+
const id = this.requestId;
|
|
121
|
+
const message = {
|
|
122
|
+
messageBizId: MESSAGE_BIZ_ID,
|
|
123
|
+
id,
|
|
124
|
+
type,
|
|
125
|
+
data,
|
|
126
|
+
timestamp: Date.now(),
|
|
127
|
+
isRequest: true
|
|
128
|
+
};
|
|
129
|
+
const timer = setTimeout(() => {
|
|
130
|
+
this.pendingRequests.delete(id);
|
|
131
|
+
reject(new Error(`Message timeout: ${type}`));
|
|
132
|
+
}, MESSAGE_TIMEOUT);
|
|
133
|
+
this.pendingRequests.set(id, {
|
|
134
|
+
resolve,
|
|
135
|
+
reject,
|
|
136
|
+
timer
|
|
137
|
+
});
|
|
138
|
+
this.iframe.contentWindow.postMessage(message, this.targetOrigin);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Register an event handler
|
|
143
|
+
*/
|
|
144
|
+
onEvent(eventName, handler) {
|
|
145
|
+
const handlers = this.eventHandlers.get(eventName) || [];
|
|
146
|
+
handlers.push(handler);
|
|
147
|
+
this.eventHandlers.set(eventName, handlers);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Remove an event handler
|
|
151
|
+
*/
|
|
152
|
+
offEvent(eventName, handler) {
|
|
153
|
+
const handlers = this.eventHandlers.get(eventName) || [];
|
|
154
|
+
const index = handlers.indexOf(handler);
|
|
155
|
+
if (index > -1) {
|
|
156
|
+
handlers.splice(index, 1);
|
|
157
|
+
this.eventHandlers.set(eventName, handlers);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
handleMessage(event) {
|
|
161
|
+
if (!ALLOWED_ORIGINS.includes(event.origin)) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const { data } = event;
|
|
165
|
+
if (data?.messageBizId !== MESSAGE_BIZ_ID) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (this.isResponseMessage(data)) {
|
|
169
|
+
const pending = this.pendingRequests.get(data.id);
|
|
170
|
+
if (pending) {
|
|
171
|
+
clearTimeout(pending.timer);
|
|
172
|
+
this.pendingRequests.delete(data.id);
|
|
173
|
+
if (data.error) {
|
|
174
|
+
pending.reject(new Error(data.error.message));
|
|
175
|
+
} else {
|
|
176
|
+
pending.resolve(data.data);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (this.isEventMessage(data)) {
|
|
181
|
+
const handlers = this.eventHandlers.get(data.eventName) || [];
|
|
182
|
+
handlers.forEach((handler) => {
|
|
183
|
+
try {
|
|
184
|
+
handler(data.data);
|
|
185
|
+
} catch {
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
isResponseMessage(data) {
|
|
191
|
+
return "isResponse" in data && data.isResponse === true;
|
|
192
|
+
}
|
|
193
|
+
isEventMessage(data) {
|
|
194
|
+
return data.type === "event";
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// src/iframe-manager.ts
|
|
199
|
+
var IframeManager = class {
|
|
200
|
+
static {
|
|
201
|
+
__name(this, "IframeManager");
|
|
202
|
+
}
|
|
203
|
+
iframe = null;
|
|
204
|
+
messageBridge;
|
|
205
|
+
config;
|
|
206
|
+
channelType;
|
|
207
|
+
root;
|
|
208
|
+
deviceDetector = null;
|
|
209
|
+
constructor(root, config) {
|
|
210
|
+
this.root = root;
|
|
211
|
+
this.config = config;
|
|
212
|
+
this.channelType = config.anonymous ? ANONYMOUS_CHANNEL_TYPE : CHANNEL_TYPE;
|
|
213
|
+
this.messageBridge = new MessageBridge(getIframeBaseURL());
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Initialize the iframe and establish communication
|
|
217
|
+
* 配置通过 URL hash 编码传入 iframe,iframe 加载后自行初始化
|
|
218
|
+
*/
|
|
219
|
+
async init() {
|
|
220
|
+
const initStart = performance.now();
|
|
221
|
+
if (!this.config.device) {
|
|
222
|
+
this.deviceDetector = new DeviceDetector();
|
|
223
|
+
this.config.device = this.deviceDetector.getDevice();
|
|
224
|
+
}
|
|
225
|
+
this.iframe = document.createElement("iframe");
|
|
226
|
+
this.iframe.src = this.buildIframeURL();
|
|
227
|
+
this.iframe.style.width = "100%";
|
|
228
|
+
this.iframe.style.height = "100%";
|
|
229
|
+
this.iframe.style.border = "none";
|
|
230
|
+
this.iframe.allow = "microphone; clipboard-write";
|
|
231
|
+
this.root.appendChild(this.iframe);
|
|
232
|
+
this.messageBridge.attach(this.iframe);
|
|
233
|
+
this.setupEventHandlers();
|
|
234
|
+
if (this.deviceDetector) {
|
|
235
|
+
this.deviceDetector.observe((newDevice) => {
|
|
236
|
+
this.messageBridge.send("updateConfig", {
|
|
237
|
+
device: newDevice
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
await this.waitForReady();
|
|
242
|
+
this.reportInitMetrics(initStart);
|
|
243
|
+
return this.createChatPanel();
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Destroy the iframe and cleanup
|
|
247
|
+
*/
|
|
248
|
+
async destroy() {
|
|
249
|
+
try {
|
|
250
|
+
await this.messageBridge.send("destroy");
|
|
251
|
+
} catch {
|
|
252
|
+
}
|
|
253
|
+
this.messageBridge.detach();
|
|
254
|
+
if (this.deviceDetector) {
|
|
255
|
+
this.deviceDetector.destroy();
|
|
256
|
+
this.deviceDetector = null;
|
|
257
|
+
}
|
|
258
|
+
if (this.iframe && this.iframe.parentNode) {
|
|
259
|
+
this.iframe.parentNode.removeChild(this.iframe);
|
|
260
|
+
}
|
|
261
|
+
this.iframe = null;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* 构建 iframe URL,将配置编码到 hash 中
|
|
265
|
+
* 格式: {baseURL}#{params} 其中 config 为 base64(JSON.stringify(configWithoutEvents))
|
|
266
|
+
*
|
|
267
|
+
* 鉴权说明:SDK 不参与 token 获取/传递。iframe 自行处理飞书登录流程,
|
|
268
|
+
* 允许用户在 iframe 中独立登录。匿名渠道跳过登录检查。
|
|
269
|
+
*/
|
|
270
|
+
buildIframeURL() {
|
|
271
|
+
const baseURL = getIframeBaseURL();
|
|
272
|
+
const { events, anonymous: _anonymous, ...configWithoutEvents } = this.config;
|
|
273
|
+
const iframeConfig = {
|
|
274
|
+
...configWithoutEvents,
|
|
275
|
+
common: {
|
|
276
|
+
...configWithoutEvents.common,
|
|
277
|
+
channelType: this.channelType,
|
|
278
|
+
resourceType: this.channelType
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
const configBase64 = btoa(JSON.stringify(iframeConfig));
|
|
282
|
+
const params = new URLSearchParams({
|
|
283
|
+
appKey: this.config.appKey,
|
|
284
|
+
config: encodeURIComponent(configBase64)
|
|
285
|
+
});
|
|
286
|
+
return `${baseURL}#${params.toString()}`;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* 等待 iframe 自行初始化完成后发送 ready 事件
|
|
290
|
+
*/
|
|
291
|
+
waitForReady() {
|
|
292
|
+
return new Promise((resolve, reject) => {
|
|
293
|
+
const timeout = setTimeout(() => {
|
|
294
|
+
reject(new Error("Iframe ready timeout"));
|
|
295
|
+
}, 3e4);
|
|
296
|
+
const checkMessage = /* @__PURE__ */ __name((event) => {
|
|
297
|
+
if (event.data?.messageBizId === MESSAGE_BIZ_ID && event.data?.type === "event" && event.data?.eventName === "ready") {
|
|
298
|
+
clearTimeout(timeout);
|
|
299
|
+
window.removeEventListener("message", checkMessage);
|
|
300
|
+
resolve();
|
|
301
|
+
}
|
|
302
|
+
}, "checkMessage");
|
|
303
|
+
window.addEventListener("message", checkMessage);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
setupEventHandlers() {
|
|
307
|
+
const { events } = this.config;
|
|
308
|
+
if (!events) return;
|
|
309
|
+
if (events.onReady) {
|
|
310
|
+
this.messageBridge.onEvent("ready", events.onReady);
|
|
311
|
+
}
|
|
312
|
+
if (events.onError) {
|
|
313
|
+
this.messageBridge.onEvent("error", (data) => {
|
|
314
|
+
const msg = data?.message || "Unknown error";
|
|
315
|
+
events.onError(new Error(msg));
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
if (events.onMessage) {
|
|
319
|
+
this.messageBridge.onEvent("onMessage", events.onMessage);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
reportInitMetrics(initStart) {
|
|
323
|
+
try {
|
|
324
|
+
const cost = performance.now() - initStart;
|
|
325
|
+
const Slardar = window.__Slardar;
|
|
326
|
+
if (Slardar?.sendEvent) {
|
|
327
|
+
Slardar.sendEvent({
|
|
328
|
+
name: "cui_init_total",
|
|
329
|
+
metrics: {
|
|
330
|
+
cost
|
|
331
|
+
},
|
|
332
|
+
categories: {
|
|
333
|
+
arch: "iframe-static",
|
|
334
|
+
appKey: this.config.appKey,
|
|
335
|
+
channelType: this.channelType
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
} catch {
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
createChatPanel() {
|
|
343
|
+
return {
|
|
344
|
+
sendMessage: /* @__PURE__ */ __name(async (data) => {
|
|
345
|
+
await this.messageBridge.send("sendMessage", data);
|
|
346
|
+
}, "sendMessage"),
|
|
347
|
+
clear: /* @__PURE__ */ __name(async () => {
|
|
348
|
+
await this.messageBridge.send("clear");
|
|
349
|
+
}, "clear"),
|
|
350
|
+
cancelMessage: /* @__PURE__ */ __name(async (messageItem) => {
|
|
351
|
+
await this.messageBridge.send("cancelMessage", messageItem);
|
|
352
|
+
}, "cancelMessage"),
|
|
353
|
+
clearAndStop: /* @__PURE__ */ __name(async () => {
|
|
354
|
+
await this.messageBridge.send("clearAndStop");
|
|
355
|
+
}, "clearAndStop"),
|
|
356
|
+
updateWelcomeMessage: /* @__PURE__ */ __name(async () => {
|
|
357
|
+
await this.messageBridge.send("updateWelcomeMessage");
|
|
358
|
+
}, "updateWelcomeMessage"),
|
|
359
|
+
updateConfig: /* @__PURE__ */ __name(async (config) => {
|
|
360
|
+
await this.messageBridge.send("updateConfig", config);
|
|
361
|
+
}, "updateConfig"),
|
|
362
|
+
observeSkill: /* @__PURE__ */ __name(async (skillId, name, skillType) => {
|
|
363
|
+
await this.messageBridge.send("observeSkill", {
|
|
364
|
+
skillId,
|
|
365
|
+
name,
|
|
366
|
+
skillType
|
|
367
|
+
});
|
|
368
|
+
}, "observeSkill"),
|
|
369
|
+
setCurrentSkill: /* @__PURE__ */ __name(async (skill) => {
|
|
370
|
+
await this.messageBridge.send("setCurrentSkill", skill);
|
|
371
|
+
}, "setCurrentSkill"),
|
|
372
|
+
destroy: /* @__PURE__ */ __name(async () => {
|
|
373
|
+
await this.destroy();
|
|
374
|
+
}, "destroy")
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// src/index.ts
|
|
380
|
+
async function initAilyChat(root, config) {
|
|
381
|
+
const manager = new IframeManager(root, config);
|
|
382
|
+
return manager.init();
|
|
383
|
+
}
|
|
384
|
+
__name(initAilyChat, "initAilyChat");
|
|
385
|
+
export {
|
|
386
|
+
IFRAME_BASE_URL,
|
|
387
|
+
MESSAGE_TIMEOUT,
|
|
388
|
+
getIframeBaseURL,
|
|
389
|
+
initAilyChat
|
|
390
|
+
};
|
|
391
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/device-detector.ts","../src/message-bridge.ts","../src/iframe-manager.ts","../src/index.ts"],"sourcesContent":["/**\n * Constants for Aily Web SDK\n */\n\n/** Message business ID for postMessage identification */\nexport const MESSAGE_BIZ_ID = 'cui-sdk-message' as const;\n\n/** Iframe base URLs by environment */\nexport const IFRAME_BASE_URL: Record<string, string> = {\n production: 'https://aily.feishu.cn/cui',\n staging: 'https://aily.feishu-pre.cn/cui',\n boe: 'https://aily.feishu-boe.cn/cui',\n development: 'http://localhost:8080/cui',\n};\n\n/**\n * Get iframe base URL based on environment\n */\nexport function getIframeBaseURL(env?: string): string {\n if (env && IFRAME_BASE_URL[env]) {\n return IFRAME_BASE_URL[env];\n }\n\n // Auto-detect based on hostname\n // MiaoDa 宿主域名: miaoda.feishu[-env].cn, *.aiforce[-env][-preview].bytedance.net, *.aiforce.run, *.aiforce.cloud\n const hostname = typeof window !== 'undefined' ? window.location.hostname : '';\n\n if (hostname.includes('aiforce-boe')) {\n return IFRAME_BASE_URL.boe;\n }\n if (hostname.includes('aiforce-pre')) {\n return IFRAME_BASE_URL.staging;\n }\n if (hostname.includes('localhost') || hostname.includes('127.0.0.1')) {\n return IFRAME_BASE_URL.development;\n }\n\n return IFRAME_BASE_URL.production;\n}\n\n/** Timeout for postMessage requests (30 seconds) */\nexport const MESSAGE_TIMEOUT = 30 * 1000;\n\n/** Miaoda 渠道标识(本 SDK 为 Miaoda 专用包,不允许外部自定义) */\nexport const CHANNEL_TYPE = 'MIAODA_CUI_SDK';\nexport const ANONYMOUS_CHANNEL_TYPE = 'MIAODA_ANONYMOUS_CUI_SDK';\n\n/** Allowed origins for postMessage (cui-iframe 的 origin) */\nexport const ALLOWED_ORIGINS = [\n 'https://aily.feishu.cn',\n 'https://aily.feishu-pre.cn',\n 'https://aily.feishu-boe.cn',\n 'http://localhost:3000',\n 'http://localhost:5173',\n 'http://localhost:8080',\n];\n","const MOBILE_BREAKPOINT = 768;\n\ntype DeviceType = 'pc' | 'mobile';\ntype DeviceChangeCallback = (device: DeviceType) => void;\n\n/**\n * 设备类型自动检测器\n *\n * 使用 matchMedia + 768px 断点检测设备类型,与 miaoda useIsMobile 逻辑一致。\n * 检测在宿主页面(而非 iframe)中执行,结果通过 postMessage 推送给 iframe。\n */\nexport class DeviceDetector {\n private mediaQuery: MediaQueryList;\n private currentDevice: DeviceType;\n private callback: DeviceChangeCallback | null = null;\n private handleChange: (() => void) | null = null;\n\n constructor() {\n this.mediaQuery = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n this.currentDevice = window.innerWidth < MOBILE_BREAKPOINT ? 'mobile' : 'pc';\n }\n\n getDevice(): DeviceType {\n return this.currentDevice;\n }\n\n observe(callback: DeviceChangeCallback): void {\n this.callback = callback;\n this.handleChange = () => {\n const newDevice: DeviceType = window.innerWidth < MOBILE_BREAKPOINT ? 'mobile' : 'pc';\n if (newDevice !== this.currentDevice) {\n this.currentDevice = newDevice;\n this.callback?.(newDevice);\n }\n };\n this.mediaQuery.addEventListener('change', this.handleChange);\n }\n\n destroy(): void {\n if (this.handleChange) {\n this.mediaQuery.removeEventListener('change', this.handleChange);\n this.handleChange = null;\n }\n this.callback = null;\n }\n}\n","import { MESSAGE_TIMEOUT, ALLOWED_ORIGINS } from './constants';\nimport {\n MESSAGE_BIZ_ID,\n type IframeMessage,\n type IframeRequestMessage,\n type IframeResponseMessage,\n type IframeEventMessage,\n type IframeMessageType,\n} from './types/message';\n\ninterface PendingRequest {\n resolve: (value: unknown) => void;\n reject: (reason: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\n/**\n * MessageBridge handles postMessage communication between parent and iframe\n */\nexport class MessageBridge {\n private iframe: HTMLIFrameElement | null = null;\n private requestId = 0;\n private pendingRequests = new Map<number, PendingRequest>();\n private eventHandlers = new Map<string, ((data: unknown) => void)[]>();\n private targetOrigin: string;\n\n constructor(targetOrigin: string) {\n this.targetOrigin = targetOrigin;\n this.handleMessage = this.handleMessage.bind(this);\n }\n\n /**\n * Attach to an iframe and start listening for messages\n */\n attach(iframe: HTMLIFrameElement): void {\n this.iframe = iframe;\n window.addEventListener('message', this.handleMessage);\n }\n\n /**\n * Detach from iframe and clean up\n */\n detach(): void {\n window.removeEventListener('message', this.handleMessage);\n this.iframe = null;\n // Clear all pending requests\n this.pendingRequests.forEach(({ reject }) => {\n reject(new Error('MessageBridge detached'));\n });\n this.pendingRequests.clear();\n }\n\n /**\n * Send a message to the iframe and wait for response\n */\n send<T = unknown>(type: IframeMessageType, data?: unknown): Promise<T> {\n return new Promise((resolve, reject) => {\n if (!this.iframe?.contentWindow) {\n reject(new Error('Iframe not attached'));\n return;\n }\n\n this.requestId += 1;\n const id = this.requestId;\n const message: IframeRequestMessage = {\n messageBizId: MESSAGE_BIZ_ID,\n id,\n type,\n data,\n timestamp: Date.now(),\n isRequest: true,\n };\n\n const timer = setTimeout(() => {\n this.pendingRequests.delete(id);\n reject(new Error(`Message timeout: ${type}`));\n }, MESSAGE_TIMEOUT);\n\n this.pendingRequests.set(id, { resolve: resolve as (value: unknown) => void, reject, timer });\n\n this.iframe.contentWindow.postMessage(message, this.targetOrigin);\n });\n }\n\n /**\n * Register an event handler\n */\n onEvent(eventName: string, handler: (data: unknown) => void): void {\n const handlers = this.eventHandlers.get(eventName) || [];\n handlers.push(handler);\n this.eventHandlers.set(eventName, handlers);\n }\n\n /**\n * Remove an event handler\n */\n offEvent(eventName: string, handler: (data: unknown) => void): void {\n const handlers = this.eventHandlers.get(eventName) || [];\n const index = handlers.indexOf(handler);\n if (index > -1) {\n handlers.splice(index, 1);\n this.eventHandlers.set(eventName, handlers);\n }\n }\n\n private handleMessage(event: MessageEvent<IframeMessage>): void {\n // Validate origin\n if (!ALLOWED_ORIGINS.includes(event.origin)) {\n return;\n }\n\n const { data } = event;\n\n // Validate message format\n if (data?.messageBizId !== MESSAGE_BIZ_ID) {\n return;\n }\n\n // Handle response messages\n if (this.isResponseMessage(data)) {\n const pending = this.pendingRequests.get(data.id);\n if (pending) {\n clearTimeout(pending.timer);\n this.pendingRequests.delete(data.id);\n\n if (data.error) {\n pending.reject(new Error(data.error.message));\n } else {\n pending.resolve(data.data);\n }\n }\n }\n\n // Handle event messages\n if (this.isEventMessage(data)) {\n const handlers = this.eventHandlers.get(data.eventName) || [];\n handlers.forEach((handler) => {\n try {\n handler(data.data);\n } catch {\n // Ignore handler errors\n }\n });\n }\n }\n\n private isResponseMessage(data: IframeMessage): data is IframeResponseMessage {\n return 'isResponse' in data && data.isResponse === true;\n }\n\n private isEventMessage(data: IframeMessage): data is IframeEventMessage {\n return data.type === 'event';\n }\n}\n","import { getIframeBaseURL, MESSAGE_BIZ_ID, CHANNEL_TYPE, ANONYMOUS_CHANNEL_TYPE } from './constants';\nimport { DeviceDetector } from './device-detector';\nimport { MessageBridge } from './message-bridge';\n\nimport type { WebSDKConfig, ChatPanel } from './types/config';\n\n/**\n * IframeManager handles iframe creation and lifecycle\n *\n * iframe 采用自初始化模式:配置通过 URL hash 传入,iframe 加载后自行解析并渲染,\n * 与旧版 copilot iframe 的行为保持一致。宿主通过 postMessage 进行后续操作。\n */\nexport class IframeManager {\n private iframe: HTMLIFrameElement | null = null;\n private messageBridge: MessageBridge;\n private config: WebSDKConfig;\n private channelType: string;\n private root: HTMLElement;\n private deviceDetector: DeviceDetector | null = null;\n\n constructor(root: HTMLElement, config: WebSDKConfig) {\n this.root = root;\n this.config = config;\n this.channelType = config.anonymous ? ANONYMOUS_CHANNEL_TYPE : CHANNEL_TYPE;\n this.messageBridge = new MessageBridge(getIframeBaseURL());\n }\n\n /**\n * Initialize the iframe and establish communication\n * 配置通过 URL hash 编码传入 iframe,iframe 加载后自行初始化\n */\n async init(): Promise<ChatPanel> {\n const initStart = performance.now();\n\n // Auto-detect device if not explicitly provided\n if (!this.config.device) {\n this.deviceDetector = new DeviceDetector();\n this.config.device = this.deviceDetector.getDevice();\n }\n\n // Create iframe element\n this.iframe = document.createElement('iframe');\n this.iframe.src = this.buildIframeURL();\n this.iframe.style.width = '100%';\n this.iframe.style.height = '100%';\n this.iframe.style.border = 'none';\n this.iframe.allow = 'microphone; clipboard-write';\n\n // Attach to DOM\n this.root.appendChild(this.iframe);\n\n // Attach message bridge (开始监听消息)\n this.messageBridge.attach(this.iframe);\n\n // Setup event handlers (在 iframe 初始化完成之前就开始监听,确保不丢失事件)\n this.setupEventHandlers();\n\n // Listen for device changes and push updates to iframe\n if (this.deviceDetector) {\n this.deviceDetector.observe((newDevice) => {\n this.messageBridge.send('updateConfig', { device: newDevice });\n });\n }\n\n // Wait for iframe to finish initialization (iframe 自行读取 URL 配置并渲染,完成后发送 ready 事件)\n await this.waitForReady();\n\n // 上报 cui_init_total 耗时(iframe 创建 → init 完成)\n this.reportInitMetrics(initStart);\n\n // Return ChatPanel interface\n return this.createChatPanel();\n }\n\n /**\n * Destroy the iframe and cleanup\n */\n async destroy(): Promise<void> {\n try {\n await this.messageBridge.send('destroy');\n } catch {\n // Ignore errors during destroy\n }\n\n this.messageBridge.detach();\n\n if (this.deviceDetector) {\n this.deviceDetector.destroy();\n this.deviceDetector = null;\n }\n\n if (this.iframe && this.iframe.parentNode) {\n this.iframe.parentNode.removeChild(this.iframe);\n }\n\n this.iframe = null;\n }\n\n /**\n * 构建 iframe URL,将配置编码到 hash 中\n * 格式: {baseURL}#{params} 其中 config 为 base64(JSON.stringify(configWithoutEvents))\n *\n * 鉴权说明:SDK 不参与 token 获取/传递。iframe 自行处理飞书登录流程,\n * 允许用户在 iframe 中独立登录。匿名渠道跳过登录检查。\n */\n private buildIframeURL(): string {\n const baseURL = getIframeBaseURL();\n\n // 移除 events(函数无法序列化)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { events, anonymous: _anonymous, ...configWithoutEvents } = this.config;\n // 注入 channelType/resourceType(SDK 内部控制,不暴露给消费者)\n const iframeConfig = {\n ...configWithoutEvents,\n common: {\n ...configWithoutEvents.common,\n channelType: this.channelType,\n resourceType: this.channelType,\n },\n };\n const configBase64 = btoa(JSON.stringify(iframeConfig));\n\n const params = new URLSearchParams({\n appKey: this.config.appKey,\n config: encodeURIComponent(configBase64),\n });\n\n return `${baseURL}#${params.toString()}`;\n }\n\n /**\n * 等待 iframe 自行初始化完成后发送 ready 事件\n */\n private waitForReady(): Promise<void> {\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(new Error('Iframe ready timeout'));\n }, 30000);\n\n const checkMessage = (event: MessageEvent) => {\n if (\n event.data?.messageBizId === MESSAGE_BIZ_ID &&\n event.data?.type === 'event' &&\n event.data?.eventName === 'ready'\n ) {\n clearTimeout(timeout);\n window.removeEventListener('message', checkMessage);\n resolve();\n }\n };\n\n window.addEventListener('message', checkMessage);\n });\n }\n\n private setupEventHandlers(): void {\n const { events } = this.config;\n if (!events) return;\n\n if (events.onReady) {\n this.messageBridge.onEvent('ready', events.onReady);\n }\n\n if (events.onError) {\n this.messageBridge.onEvent('error', (data: unknown) => {\n const msg = (data as { message?: string })?.message || 'Unknown error';\n events.onError!(new Error(msg));\n });\n }\n\n if (events.onMessage) {\n this.messageBridge.onEvent('onMessage', events.onMessage);\n }\n }\n\n private reportInitMetrics(initStart: number): void {\n try {\n const cost = performance.now() - initStart;\n const Slardar = (window as unknown as Record<string, unknown>).__Slardar as\n | { sendEvent?: (event: Record<string, unknown>) => void }\n | undefined;\n if (Slardar?.sendEvent) {\n Slardar.sendEvent({\n name: 'cui_init_total',\n metrics: { cost },\n categories: {\n arch: 'iframe-static',\n appKey: this.config.appKey,\n channelType: this.channelType,\n },\n });\n }\n } catch {\n // 性能上报失败不影响功能\n }\n }\n\n private createChatPanel(): ChatPanel {\n return {\n sendMessage: async (data) => {\n await this.messageBridge.send('sendMessage', data);\n },\n clear: async () => {\n await this.messageBridge.send('clear');\n },\n cancelMessage: async (messageItem) => {\n await this.messageBridge.send('cancelMessage', messageItem);\n },\n clearAndStop: async () => {\n await this.messageBridge.send('clearAndStop');\n },\n updateWelcomeMessage: async () => {\n await this.messageBridge.send('updateWelcomeMessage');\n },\n updateConfig: async (config) => {\n await this.messageBridge.send('updateConfig', config);\n },\n observeSkill: async (skillId, name, skillType) => {\n await this.messageBridge.send('observeSkill', { skillId, name, skillType });\n },\n setCurrentSkill: async (skill) => {\n await this.messageBridge.send('setCurrentSkill', skill);\n },\n destroy: async () => {\n await this.destroy();\n },\n };\n }\n}\n","/*\n * index.ts\n * Author: perterpon.wang<perterpon.wang@bytedance.com>\n * Create: Tue Feb 10 2026\n */\n\nimport { IframeManager } from './iframe-manager';\n\nimport type { ChatPanel, WebSDKConfig } from './types/config';\n\n/**\n * Initialize Aily Chat via iframe\n * This is the primary method for initializing the chat panel\n * @param root - Container element for the iframe\n * @param config - Configuration options\n * @returns ChatPanel instance for controlling the chat\n */\nexport async function initAilyChat(root: HTMLElement, config: WebSDKConfig): Promise<ChatPanel> {\n const manager = new IframeManager(root, config);\n return manager.init();\n}\n\n// Export types\nexport type {\n ChatPanel,\n WebSDKConfig,\n WebSDKCommonConfig,\n WebSDKConversionConfig,\n WebSDKEditorConfig,\n WebSDKEvents,\n} from './types/config';\n\n// Export constants\nexport { getIframeBaseURL, IFRAME_BASE_URL, MESSAGE_TIMEOUT } from './constants';\n\n// Export message types\nexport type {\n IframeMessageType,\n IframeRequestMessage,\n IframeResponseMessage,\n IframeEventMessage,\n IframeMessage,\n} from './types/message';\n"],"mappings":"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","names":["MESSAGE_BIZ_ID","IFRAME_BASE_URL","production","staging","boe","development","getIframeBaseURL","env","hostname","window","location","includes","MESSAGE_TIMEOUT","CHANNEL_TYPE","ANONYMOUS_CHANNEL_TYPE","ALLOWED_ORIGINS","MOBILE_BREAKPOINT","DeviceDetector","mediaQuery","currentDevice","callback","handleChange","window","matchMedia","innerWidth","getDevice","observe","newDevice","addEventListener","destroy","removeEventListener","MessageBridge","iframe","requestId","pendingRequests","Map","eventHandlers","targetOrigin","handleMessage","bind","attach","window","addEventListener","detach","removeEventListener","forEach","reject","Error","clear","send","type","data","Promise","resolve","contentWindow","id","message","messageBizId","MESSAGE_BIZ_ID","timestamp","Date","now","isRequest","timer","setTimeout","delete","MESSAGE_TIMEOUT","set","postMessage","onEvent","eventName","handler","handlers","get","push","offEvent","index","indexOf","splice","event","ALLOWED_ORIGINS","includes","origin","isResponseMessage","pending","clearTimeout","error","isEventMessage","isResponse","IframeManager","iframe","messageBridge","config","channelType","root","deviceDetector","anonymous","ANONYMOUS_CHANNEL_TYPE","CHANNEL_TYPE","MessageBridge","getIframeBaseURL","init","initStart","performance","now","device","DeviceDetector","getDevice","document","createElement","src","buildIframeURL","style","width","height","border","allow","appendChild","attach","setupEventHandlers","observe","newDevice","send","waitForReady","reportInitMetrics","createChatPanel","destroy","detach","parentNode","removeChild","baseURL","events","_anonymous","configWithoutEvents","iframeConfig","common","resourceType","configBase64","btoa","JSON","stringify","params","URLSearchParams","appKey","encodeURIComponent","toString","Promise","resolve","reject","timeout","setTimeout","Error","checkMessage","event","data","messageBizId","MESSAGE_BIZ_ID","type","eventName","clearTimeout","window","removeEventListener","addEventListener","onReady","onEvent","onError","msg","message","onMessage","cost","Slardar","__Slardar","sendEvent","name","metrics","categories","arch","sendMessage","clear","cancelMessage","messageItem","clearAndStop","updateWelcomeMessage","updateConfig","observeSkill","skillId","skillType","setCurrentSkill","skill","initAilyChat","root","config","manager","IframeManager","init"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lark-apaas/aily-web-sdk",
|
|
3
|
+
"version": "0.0.2-alpha.0",
|
|
4
|
+
"description": "Aily Web SDK - iframe-based chat panel integration",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"require": "./dist/index.cjs",
|
|
20
|
+
"types": "./dist/index.d.ts"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"dev": "tsup --watch",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"storybook": "storybook dev",
|
|
29
|
+
"build:storybook": "storybook build",
|
|
30
|
+
"prepublishOnly": "npm run build"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@rsbuild/core": "~1.4.13",
|
|
34
|
+
"@rsbuild/plugin-react": "^1.3.4",
|
|
35
|
+
"@storybook/addon-essentials": "^8.6.14",
|
|
36
|
+
"@storybook/addon-interactions": "^8.6.14",
|
|
37
|
+
"@storybook/addon-links": "^8.6.14",
|
|
38
|
+
"@storybook/react": "^8.6.14",
|
|
39
|
+
"@types/node": "^22.10.2",
|
|
40
|
+
"@types/react": "^19",
|
|
41
|
+
"@types/react-dom": "^19",
|
|
42
|
+
"react": "^19",
|
|
43
|
+
"react-dom": "^19",
|
|
44
|
+
"storybook": "^8.6.14",
|
|
45
|
+
"storybook-react-rsbuild": "^1.0.2",
|
|
46
|
+
"tsup": "^8.0.0",
|
|
47
|
+
"typescript": "^5.0.0",
|
|
48
|
+
"vitest": "^3.2.4"
|
|
49
|
+
},
|
|
50
|
+
"license": "MIT"
|
|
51
|
+
}
|