chatablex-web-sdk 1.0.3 → 1.0.32
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/README.md +115 -0
- package/README.zh-CN.md +121 -0
- package/dist/index.d.mts +135 -1
- package/dist/index.d.ts +135 -1
- package/dist/index.js +253 -2
- package/dist/index.mjs +249 -2
- package/package.json +1 -1
- package/src/index.ts +16 -0
- package/src/modules/auth.ts +102 -0
- package/src/modules/cloud.ts +297 -0
- package/src/types.ts +124 -0
package/dist/index.js
CHANGED
|
@@ -22,6 +22,10 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
Bridge: () => Bridge,
|
|
24
24
|
ChatableX: () => ChatableX,
|
|
25
|
+
CloudAuthRequiredError: () => CloudAuthRequiredError,
|
|
26
|
+
CloudError: () => CloudError,
|
|
27
|
+
CloudQuotaExceededError: () => CloudQuotaExceededError,
|
|
28
|
+
CloudSubscriptionRequiredError: () => CloudSubscriptionRequiredError,
|
|
25
29
|
SDK_VERSION: () => SDK_VERSION
|
|
26
30
|
});
|
|
27
31
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -299,10 +303,246 @@ function createPlatformModule(bridge) {
|
|
|
299
303
|
};
|
|
300
304
|
}
|
|
301
305
|
|
|
306
|
+
// src/modules/auth.ts
|
|
307
|
+
var EXPIRY_SKEW_MS = 5e3;
|
|
308
|
+
function isValid(token, now) {
|
|
309
|
+
return !!token && typeof token.access_token === "string" && token.access_token.length > 0 && token.expires_at - EXPIRY_SKEW_MS > now;
|
|
310
|
+
}
|
|
311
|
+
var HostAuthProvider = class {
|
|
312
|
+
constructor(bridge) {
|
|
313
|
+
this._token = null;
|
|
314
|
+
/** In-flight refresh, so concurrent callers share one host round-trip. */
|
|
315
|
+
this._refreshing = null;
|
|
316
|
+
this._bridge = bridge;
|
|
317
|
+
}
|
|
318
|
+
async getToken() {
|
|
319
|
+
if (isValid(this._token, Date.now())) return this._token;
|
|
320
|
+
const ok = await this.refresh();
|
|
321
|
+
return ok ? this._token : null;
|
|
322
|
+
}
|
|
323
|
+
async getAuthHeaders() {
|
|
324
|
+
const token = await this.getToken();
|
|
325
|
+
if (!token) return {};
|
|
326
|
+
return { Authorization: `Bearer ${token.access_token}` };
|
|
327
|
+
}
|
|
328
|
+
getUserId() {
|
|
329
|
+
return this._token?.user_id ?? null;
|
|
330
|
+
}
|
|
331
|
+
isAuthenticated() {
|
|
332
|
+
return isValid(this._token, Date.now());
|
|
333
|
+
}
|
|
334
|
+
refresh() {
|
|
335
|
+
if (this._refreshing) return this._refreshing;
|
|
336
|
+
this._refreshing = this._doRefresh().finally(() => {
|
|
337
|
+
this._refreshing = null;
|
|
338
|
+
});
|
|
339
|
+
return this._refreshing;
|
|
340
|
+
}
|
|
341
|
+
async _doRefresh() {
|
|
342
|
+
try {
|
|
343
|
+
const raw = await this._bridge.sendMessage("host.getAuthToken");
|
|
344
|
+
if (raw && typeof raw === "object" && typeof raw.access_token === "string" && raw.access_token.length > 0) {
|
|
345
|
+
this._token = {
|
|
346
|
+
access_token: raw.access_token,
|
|
347
|
+
expires_at: Number(raw.expires_at) || 0,
|
|
348
|
+
user_id: String(raw.user_id ?? "")
|
|
349
|
+
};
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
this._token = null;
|
|
353
|
+
return false;
|
|
354
|
+
} catch {
|
|
355
|
+
this._token = null;
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
function createAuthModule(bridge) {
|
|
361
|
+
return new HostAuthProvider(bridge);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// src/modules/cloud.ts
|
|
365
|
+
var CloudError = class extends Error {
|
|
366
|
+
constructor(message, code) {
|
|
367
|
+
super(message);
|
|
368
|
+
this.name = "CloudError";
|
|
369
|
+
this.code = code;
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
var CloudAuthRequiredError = class extends CloudError {
|
|
373
|
+
constructor(message = "cloud storage requires an authenticated session") {
|
|
374
|
+
super(message, 401);
|
|
375
|
+
this.name = "CloudAuthRequiredError";
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
var CloudSubscriptionRequiredError = class extends CloudError {
|
|
379
|
+
constructor(message = "cloud storage requires an active subscription") {
|
|
380
|
+
super(message, 40302);
|
|
381
|
+
this.name = "CloudSubscriptionRequiredError";
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
var CloudQuotaExceededError = class extends CloudError {
|
|
385
|
+
constructor(usedBytes, quotaBytes, message = "storage quota exceeded") {
|
|
386
|
+
super(message, 40301);
|
|
387
|
+
this.name = "CloudQuotaExceededError";
|
|
388
|
+
this.usedBytes = usedBytes;
|
|
389
|
+
this.quotaBytes = quotaBytes;
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
var DEFAULT_CONTENT_TYPE = "application/octet-stream";
|
|
393
|
+
function normalizeData(data) {
|
|
394
|
+
if (typeof Blob !== "undefined" && data instanceof Blob) {
|
|
395
|
+
return { body: data, size: data.size, type: data.type || "" };
|
|
396
|
+
}
|
|
397
|
+
if (typeof data === "string") {
|
|
398
|
+
const blob = new Blob([data]);
|
|
399
|
+
return { body: blob, size: blob.size, type: "" };
|
|
400
|
+
}
|
|
401
|
+
if (data instanceof ArrayBuffer) {
|
|
402
|
+
return { body: data, size: data.byteLength, type: "" };
|
|
403
|
+
}
|
|
404
|
+
if (ArrayBuffer.isView(data)) {
|
|
405
|
+
return { body: data, size: data.byteLength, type: "" };
|
|
406
|
+
}
|
|
407
|
+
throw new CloudError("unsupported upload data type", 400);
|
|
408
|
+
}
|
|
409
|
+
function createCloudModule(bridge, deps) {
|
|
410
|
+
const { appId, auth } = deps;
|
|
411
|
+
let resolvedBase = deps.apiBaseUrl ? stripTrailingSlash(deps.apiBaseUrl) : null;
|
|
412
|
+
function stripTrailingSlash(url) {
|
|
413
|
+
return url.replace(/\/+$/, "");
|
|
414
|
+
}
|
|
415
|
+
async function baseUrl() {
|
|
416
|
+
if (resolvedBase) return resolvedBase;
|
|
417
|
+
try {
|
|
418
|
+
const r = await bridge.sendMessage("host.getApiBaseUrl", {}, 5e3);
|
|
419
|
+
const url = typeof r === "string" ? r : r && typeof r === "object" && typeof r.base_url === "string" ? r.base_url : "";
|
|
420
|
+
if (url) {
|
|
421
|
+
resolvedBase = stripTrailingSlash(url);
|
|
422
|
+
return resolvedBase;
|
|
423
|
+
}
|
|
424
|
+
} catch {
|
|
425
|
+
}
|
|
426
|
+
throw new CloudError(
|
|
427
|
+
"cloud API base URL is not configured; pass apiBaseUrl to ChatableX.init()",
|
|
428
|
+
0
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
async function authedFetch(path, init) {
|
|
432
|
+
const token = await auth.getToken();
|
|
433
|
+
if (!token) throw new CloudAuthRequiredError();
|
|
434
|
+
const base = await baseUrl();
|
|
435
|
+
const url = `${base}${path}`;
|
|
436
|
+
const build = async () => ({
|
|
437
|
+
...init,
|
|
438
|
+
headers: { ...init.headers ?? {}, ...await auth.getAuthHeaders() }
|
|
439
|
+
});
|
|
440
|
+
let res = await fetch(url, await build());
|
|
441
|
+
if (res.status === 401 && await auth.refresh()) {
|
|
442
|
+
res = await fetch(url, await build());
|
|
443
|
+
}
|
|
444
|
+
return res;
|
|
445
|
+
}
|
|
446
|
+
async function callApi(path, init) {
|
|
447
|
+
const res = await authedFetch(path, init);
|
|
448
|
+
let body = null;
|
|
449
|
+
try {
|
|
450
|
+
body = await res.json();
|
|
451
|
+
} catch {
|
|
452
|
+
}
|
|
453
|
+
const code = body?.code;
|
|
454
|
+
const message = body?.message || `HTTP ${res.status}`;
|
|
455
|
+
if (!res.ok || body?.success === false) {
|
|
456
|
+
if (code === 40301) {
|
|
457
|
+
const q = body?.data ?? {};
|
|
458
|
+
throw new CloudQuotaExceededError(q.used_bytes ?? 0, q.quota_bytes ?? 0, message);
|
|
459
|
+
}
|
|
460
|
+
if (code === 40302) throw new CloudSubscriptionRequiredError(message);
|
|
461
|
+
if (res.status === 401) throw new CloudAuthRequiredError(message);
|
|
462
|
+
throw new CloudError(message, code ?? res.status);
|
|
463
|
+
}
|
|
464
|
+
return body?.data ?? null;
|
|
465
|
+
}
|
|
466
|
+
function validateFileKey(fileKey) {
|
|
467
|
+
if (!fileKey || typeof fileKey !== "string") {
|
|
468
|
+
throw new CloudError("fileKey is required", 400);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return {
|
|
472
|
+
async upload(fileKey, data, options = {}) {
|
|
473
|
+
validateFileKey(fileKey);
|
|
474
|
+
const { body, size, type } = normalizeData(data);
|
|
475
|
+
const contentType = options.contentType || type || DEFAULT_CONTENT_TYPE;
|
|
476
|
+
const signed = await callApi("/api/storage/upload-url", {
|
|
477
|
+
method: "POST",
|
|
478
|
+
headers: { "Content-Type": "application/json" },
|
|
479
|
+
body: JSON.stringify({
|
|
480
|
+
app_id: appId,
|
|
481
|
+
file_key: fileKey,
|
|
482
|
+
content_type: contentType,
|
|
483
|
+
size_bytes: size
|
|
484
|
+
})
|
|
485
|
+
});
|
|
486
|
+
const put = await fetch(signed.upload_url, {
|
|
487
|
+
method: "PUT",
|
|
488
|
+
headers: { "Content-Type": contentType },
|
|
489
|
+
body
|
|
490
|
+
});
|
|
491
|
+
if (!put.ok) {
|
|
492
|
+
throw new CloudError(`OSS upload failed: HTTP ${put.status}`, put.status);
|
|
493
|
+
}
|
|
494
|
+
return { fileKey, objectKey: signed.object_key, size, contentType };
|
|
495
|
+
},
|
|
496
|
+
async getDownloadUrl(fileKey) {
|
|
497
|
+
validateFileKey(fileKey);
|
|
498
|
+
const signed = await callApi("/api/storage/download-url", {
|
|
499
|
+
method: "POST",
|
|
500
|
+
headers: { "Content-Type": "application/json" },
|
|
501
|
+
body: JSON.stringify({ app_id: appId, file_key: fileKey })
|
|
502
|
+
});
|
|
503
|
+
return signed.download_url;
|
|
504
|
+
},
|
|
505
|
+
async download(fileKey) {
|
|
506
|
+
const url = await this.getDownloadUrl(fileKey);
|
|
507
|
+
const res = await fetch(url, { method: "GET" });
|
|
508
|
+
if (!res.ok) {
|
|
509
|
+
throw new CloudError(`OSS download failed: HTTP ${res.status}`, res.status);
|
|
510
|
+
}
|
|
511
|
+
return res.blob();
|
|
512
|
+
},
|
|
513
|
+
async list(options = {}) {
|
|
514
|
+
const qs = new URLSearchParams({ app_id: appId });
|
|
515
|
+
if (options.prefix) qs.set("prefix", options.prefix);
|
|
516
|
+
const data = await callApi(`/api/storage/files?${qs.toString()}`, {
|
|
517
|
+
method: "GET"
|
|
518
|
+
});
|
|
519
|
+
return (data?.files ?? []).map((f) => ({
|
|
520
|
+
fileKey: f.file_key,
|
|
521
|
+
size: f.size,
|
|
522
|
+
lastModified: f.last_modified
|
|
523
|
+
}));
|
|
524
|
+
},
|
|
525
|
+
async delete(fileKey) {
|
|
526
|
+
validateFileKey(fileKey);
|
|
527
|
+
const qs = new URLSearchParams({ app_id: appId, file_key: fileKey });
|
|
528
|
+
await callApi(`/api/storage/files?${qs.toString()}`, { method: "DELETE" });
|
|
529
|
+
},
|
|
530
|
+
async usage() {
|
|
531
|
+
const data = await callApi("/api/storage/usage", { method: "GET" });
|
|
532
|
+
return {
|
|
533
|
+
usedBytes: data.used_bytes,
|
|
534
|
+
quotaBytes: data.quota_bytes,
|
|
535
|
+
fileCount: data.file_count,
|
|
536
|
+
reconciledAt: data.reconciled_at
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
302
542
|
// package.json
|
|
303
543
|
var package_default = {
|
|
304
544
|
name: "chatablex-web-sdk",
|
|
305
|
-
version: "1.0.
|
|
545
|
+
version: "1.0.32",
|
|
306
546
|
description: "ChatableX Web SDK for AI App WebUI development. Provides bridge communication with the ChatableX Flutter client.",
|
|
307
547
|
main: "dist/index.js",
|
|
308
548
|
module: "dist/index.mjs",
|
|
@@ -394,6 +634,7 @@ var ChatableX = {
|
|
|
394
634
|
}
|
|
395
635
|
const toolModule = createToolModule(bridge, config.appId);
|
|
396
636
|
if (toolConfig) toolModule._setInfo(toolConfig);
|
|
637
|
+
const authModule = createAuthModule(bridge);
|
|
397
638
|
const sdk = {
|
|
398
639
|
ai: createAIModule(bridge),
|
|
399
640
|
tools: createToolsModule(bridge),
|
|
@@ -401,7 +642,13 @@ var ChatableX = {
|
|
|
401
642
|
events: createEventsModule(bridge),
|
|
402
643
|
storage: createStorageModule(bridge),
|
|
403
644
|
tool: toolModule,
|
|
404
|
-
platform: createPlatformModule(bridge)
|
|
645
|
+
platform: createPlatformModule(bridge),
|
|
646
|
+
auth: authModule,
|
|
647
|
+
cloud: createCloudModule(bridge, {
|
|
648
|
+
appId: config.appId,
|
|
649
|
+
auth: authModule,
|
|
650
|
+
apiBaseUrl: config.apiBaseUrl
|
|
651
|
+
})
|
|
405
652
|
};
|
|
406
653
|
window.ChatableX = sdk;
|
|
407
654
|
_instance = sdk;
|
|
@@ -424,5 +671,9 @@ var ChatableX = {
|
|
|
424
671
|
0 && (module.exports = {
|
|
425
672
|
Bridge,
|
|
426
673
|
ChatableX,
|
|
674
|
+
CloudAuthRequiredError,
|
|
675
|
+
CloudError,
|
|
676
|
+
CloudQuotaExceededError,
|
|
677
|
+
CloudSubscriptionRequiredError,
|
|
427
678
|
SDK_VERSION
|
|
428
679
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -271,10 +271,246 @@ function createPlatformModule(bridge) {
|
|
|
271
271
|
};
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
+
// src/modules/auth.ts
|
|
275
|
+
var EXPIRY_SKEW_MS = 5e3;
|
|
276
|
+
function isValid(token, now) {
|
|
277
|
+
return !!token && typeof token.access_token === "string" && token.access_token.length > 0 && token.expires_at - EXPIRY_SKEW_MS > now;
|
|
278
|
+
}
|
|
279
|
+
var HostAuthProvider = class {
|
|
280
|
+
constructor(bridge) {
|
|
281
|
+
this._token = null;
|
|
282
|
+
/** In-flight refresh, so concurrent callers share one host round-trip. */
|
|
283
|
+
this._refreshing = null;
|
|
284
|
+
this._bridge = bridge;
|
|
285
|
+
}
|
|
286
|
+
async getToken() {
|
|
287
|
+
if (isValid(this._token, Date.now())) return this._token;
|
|
288
|
+
const ok = await this.refresh();
|
|
289
|
+
return ok ? this._token : null;
|
|
290
|
+
}
|
|
291
|
+
async getAuthHeaders() {
|
|
292
|
+
const token = await this.getToken();
|
|
293
|
+
if (!token) return {};
|
|
294
|
+
return { Authorization: `Bearer ${token.access_token}` };
|
|
295
|
+
}
|
|
296
|
+
getUserId() {
|
|
297
|
+
return this._token?.user_id ?? null;
|
|
298
|
+
}
|
|
299
|
+
isAuthenticated() {
|
|
300
|
+
return isValid(this._token, Date.now());
|
|
301
|
+
}
|
|
302
|
+
refresh() {
|
|
303
|
+
if (this._refreshing) return this._refreshing;
|
|
304
|
+
this._refreshing = this._doRefresh().finally(() => {
|
|
305
|
+
this._refreshing = null;
|
|
306
|
+
});
|
|
307
|
+
return this._refreshing;
|
|
308
|
+
}
|
|
309
|
+
async _doRefresh() {
|
|
310
|
+
try {
|
|
311
|
+
const raw = await this._bridge.sendMessage("host.getAuthToken");
|
|
312
|
+
if (raw && typeof raw === "object" && typeof raw.access_token === "string" && raw.access_token.length > 0) {
|
|
313
|
+
this._token = {
|
|
314
|
+
access_token: raw.access_token,
|
|
315
|
+
expires_at: Number(raw.expires_at) || 0,
|
|
316
|
+
user_id: String(raw.user_id ?? "")
|
|
317
|
+
};
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
this._token = null;
|
|
321
|
+
return false;
|
|
322
|
+
} catch {
|
|
323
|
+
this._token = null;
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
function createAuthModule(bridge) {
|
|
329
|
+
return new HostAuthProvider(bridge);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/modules/cloud.ts
|
|
333
|
+
var CloudError = class extends Error {
|
|
334
|
+
constructor(message, code) {
|
|
335
|
+
super(message);
|
|
336
|
+
this.name = "CloudError";
|
|
337
|
+
this.code = code;
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
var CloudAuthRequiredError = class extends CloudError {
|
|
341
|
+
constructor(message = "cloud storage requires an authenticated session") {
|
|
342
|
+
super(message, 401);
|
|
343
|
+
this.name = "CloudAuthRequiredError";
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
var CloudSubscriptionRequiredError = class extends CloudError {
|
|
347
|
+
constructor(message = "cloud storage requires an active subscription") {
|
|
348
|
+
super(message, 40302);
|
|
349
|
+
this.name = "CloudSubscriptionRequiredError";
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
var CloudQuotaExceededError = class extends CloudError {
|
|
353
|
+
constructor(usedBytes, quotaBytes, message = "storage quota exceeded") {
|
|
354
|
+
super(message, 40301);
|
|
355
|
+
this.name = "CloudQuotaExceededError";
|
|
356
|
+
this.usedBytes = usedBytes;
|
|
357
|
+
this.quotaBytes = quotaBytes;
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
var DEFAULT_CONTENT_TYPE = "application/octet-stream";
|
|
361
|
+
function normalizeData(data) {
|
|
362
|
+
if (typeof Blob !== "undefined" && data instanceof Blob) {
|
|
363
|
+
return { body: data, size: data.size, type: data.type || "" };
|
|
364
|
+
}
|
|
365
|
+
if (typeof data === "string") {
|
|
366
|
+
const blob = new Blob([data]);
|
|
367
|
+
return { body: blob, size: blob.size, type: "" };
|
|
368
|
+
}
|
|
369
|
+
if (data instanceof ArrayBuffer) {
|
|
370
|
+
return { body: data, size: data.byteLength, type: "" };
|
|
371
|
+
}
|
|
372
|
+
if (ArrayBuffer.isView(data)) {
|
|
373
|
+
return { body: data, size: data.byteLength, type: "" };
|
|
374
|
+
}
|
|
375
|
+
throw new CloudError("unsupported upload data type", 400);
|
|
376
|
+
}
|
|
377
|
+
function createCloudModule(bridge, deps) {
|
|
378
|
+
const { appId, auth } = deps;
|
|
379
|
+
let resolvedBase = deps.apiBaseUrl ? stripTrailingSlash(deps.apiBaseUrl) : null;
|
|
380
|
+
function stripTrailingSlash(url) {
|
|
381
|
+
return url.replace(/\/+$/, "");
|
|
382
|
+
}
|
|
383
|
+
async function baseUrl() {
|
|
384
|
+
if (resolvedBase) return resolvedBase;
|
|
385
|
+
try {
|
|
386
|
+
const r = await bridge.sendMessage("host.getApiBaseUrl", {}, 5e3);
|
|
387
|
+
const url = typeof r === "string" ? r : r && typeof r === "object" && typeof r.base_url === "string" ? r.base_url : "";
|
|
388
|
+
if (url) {
|
|
389
|
+
resolvedBase = stripTrailingSlash(url);
|
|
390
|
+
return resolvedBase;
|
|
391
|
+
}
|
|
392
|
+
} catch {
|
|
393
|
+
}
|
|
394
|
+
throw new CloudError(
|
|
395
|
+
"cloud API base URL is not configured; pass apiBaseUrl to ChatableX.init()",
|
|
396
|
+
0
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
async function authedFetch(path, init) {
|
|
400
|
+
const token = await auth.getToken();
|
|
401
|
+
if (!token) throw new CloudAuthRequiredError();
|
|
402
|
+
const base = await baseUrl();
|
|
403
|
+
const url = `${base}${path}`;
|
|
404
|
+
const build = async () => ({
|
|
405
|
+
...init,
|
|
406
|
+
headers: { ...init.headers ?? {}, ...await auth.getAuthHeaders() }
|
|
407
|
+
});
|
|
408
|
+
let res = await fetch(url, await build());
|
|
409
|
+
if (res.status === 401 && await auth.refresh()) {
|
|
410
|
+
res = await fetch(url, await build());
|
|
411
|
+
}
|
|
412
|
+
return res;
|
|
413
|
+
}
|
|
414
|
+
async function callApi(path, init) {
|
|
415
|
+
const res = await authedFetch(path, init);
|
|
416
|
+
let body = null;
|
|
417
|
+
try {
|
|
418
|
+
body = await res.json();
|
|
419
|
+
} catch {
|
|
420
|
+
}
|
|
421
|
+
const code = body?.code;
|
|
422
|
+
const message = body?.message || `HTTP ${res.status}`;
|
|
423
|
+
if (!res.ok || body?.success === false) {
|
|
424
|
+
if (code === 40301) {
|
|
425
|
+
const q = body?.data ?? {};
|
|
426
|
+
throw new CloudQuotaExceededError(q.used_bytes ?? 0, q.quota_bytes ?? 0, message);
|
|
427
|
+
}
|
|
428
|
+
if (code === 40302) throw new CloudSubscriptionRequiredError(message);
|
|
429
|
+
if (res.status === 401) throw new CloudAuthRequiredError(message);
|
|
430
|
+
throw new CloudError(message, code ?? res.status);
|
|
431
|
+
}
|
|
432
|
+
return body?.data ?? null;
|
|
433
|
+
}
|
|
434
|
+
function validateFileKey(fileKey) {
|
|
435
|
+
if (!fileKey || typeof fileKey !== "string") {
|
|
436
|
+
throw new CloudError("fileKey is required", 400);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
async upload(fileKey, data, options = {}) {
|
|
441
|
+
validateFileKey(fileKey);
|
|
442
|
+
const { body, size, type } = normalizeData(data);
|
|
443
|
+
const contentType = options.contentType || type || DEFAULT_CONTENT_TYPE;
|
|
444
|
+
const signed = await callApi("/api/storage/upload-url", {
|
|
445
|
+
method: "POST",
|
|
446
|
+
headers: { "Content-Type": "application/json" },
|
|
447
|
+
body: JSON.stringify({
|
|
448
|
+
app_id: appId,
|
|
449
|
+
file_key: fileKey,
|
|
450
|
+
content_type: contentType,
|
|
451
|
+
size_bytes: size
|
|
452
|
+
})
|
|
453
|
+
});
|
|
454
|
+
const put = await fetch(signed.upload_url, {
|
|
455
|
+
method: "PUT",
|
|
456
|
+
headers: { "Content-Type": contentType },
|
|
457
|
+
body
|
|
458
|
+
});
|
|
459
|
+
if (!put.ok) {
|
|
460
|
+
throw new CloudError(`OSS upload failed: HTTP ${put.status}`, put.status);
|
|
461
|
+
}
|
|
462
|
+
return { fileKey, objectKey: signed.object_key, size, contentType };
|
|
463
|
+
},
|
|
464
|
+
async getDownloadUrl(fileKey) {
|
|
465
|
+
validateFileKey(fileKey);
|
|
466
|
+
const signed = await callApi("/api/storage/download-url", {
|
|
467
|
+
method: "POST",
|
|
468
|
+
headers: { "Content-Type": "application/json" },
|
|
469
|
+
body: JSON.stringify({ app_id: appId, file_key: fileKey })
|
|
470
|
+
});
|
|
471
|
+
return signed.download_url;
|
|
472
|
+
},
|
|
473
|
+
async download(fileKey) {
|
|
474
|
+
const url = await this.getDownloadUrl(fileKey);
|
|
475
|
+
const res = await fetch(url, { method: "GET" });
|
|
476
|
+
if (!res.ok) {
|
|
477
|
+
throw new CloudError(`OSS download failed: HTTP ${res.status}`, res.status);
|
|
478
|
+
}
|
|
479
|
+
return res.blob();
|
|
480
|
+
},
|
|
481
|
+
async list(options = {}) {
|
|
482
|
+
const qs = new URLSearchParams({ app_id: appId });
|
|
483
|
+
if (options.prefix) qs.set("prefix", options.prefix);
|
|
484
|
+
const data = await callApi(`/api/storage/files?${qs.toString()}`, {
|
|
485
|
+
method: "GET"
|
|
486
|
+
});
|
|
487
|
+
return (data?.files ?? []).map((f) => ({
|
|
488
|
+
fileKey: f.file_key,
|
|
489
|
+
size: f.size,
|
|
490
|
+
lastModified: f.last_modified
|
|
491
|
+
}));
|
|
492
|
+
},
|
|
493
|
+
async delete(fileKey) {
|
|
494
|
+
validateFileKey(fileKey);
|
|
495
|
+
const qs = new URLSearchParams({ app_id: appId, file_key: fileKey });
|
|
496
|
+
await callApi(`/api/storage/files?${qs.toString()}`, { method: "DELETE" });
|
|
497
|
+
},
|
|
498
|
+
async usage() {
|
|
499
|
+
const data = await callApi("/api/storage/usage", { method: "GET" });
|
|
500
|
+
return {
|
|
501
|
+
usedBytes: data.used_bytes,
|
|
502
|
+
quotaBytes: data.quota_bytes,
|
|
503
|
+
fileCount: data.file_count,
|
|
504
|
+
reconciledAt: data.reconciled_at
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
274
510
|
// package.json
|
|
275
511
|
var package_default = {
|
|
276
512
|
name: "chatablex-web-sdk",
|
|
277
|
-
version: "1.0.
|
|
513
|
+
version: "1.0.32",
|
|
278
514
|
description: "ChatableX Web SDK for AI App WebUI development. Provides bridge communication with the ChatableX Flutter client.",
|
|
279
515
|
main: "dist/index.js",
|
|
280
516
|
module: "dist/index.mjs",
|
|
@@ -366,6 +602,7 @@ var ChatableX = {
|
|
|
366
602
|
}
|
|
367
603
|
const toolModule = createToolModule(bridge, config.appId);
|
|
368
604
|
if (toolConfig) toolModule._setInfo(toolConfig);
|
|
605
|
+
const authModule = createAuthModule(bridge);
|
|
369
606
|
const sdk = {
|
|
370
607
|
ai: createAIModule(bridge),
|
|
371
608
|
tools: createToolsModule(bridge),
|
|
@@ -373,7 +610,13 @@ var ChatableX = {
|
|
|
373
610
|
events: createEventsModule(bridge),
|
|
374
611
|
storage: createStorageModule(bridge),
|
|
375
612
|
tool: toolModule,
|
|
376
|
-
platform: createPlatformModule(bridge)
|
|
613
|
+
platform: createPlatformModule(bridge),
|
|
614
|
+
auth: authModule,
|
|
615
|
+
cloud: createCloudModule(bridge, {
|
|
616
|
+
appId: config.appId,
|
|
617
|
+
auth: authModule,
|
|
618
|
+
apiBaseUrl: config.apiBaseUrl
|
|
619
|
+
})
|
|
377
620
|
};
|
|
378
621
|
window.ChatableX = sdk;
|
|
379
622
|
_instance = sdk;
|
|
@@ -395,5 +638,9 @@ var ChatableX = {
|
|
|
395
638
|
export {
|
|
396
639
|
Bridge,
|
|
397
640
|
ChatableX,
|
|
641
|
+
CloudAuthRequiredError,
|
|
642
|
+
CloudError,
|
|
643
|
+
CloudQuotaExceededError,
|
|
644
|
+
CloudSubscriptionRequiredError,
|
|
398
645
|
SDK_VERSION
|
|
399
646
|
};
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -26,6 +26,8 @@ import { createUIModule } from './modules/ui';
|
|
|
26
26
|
import { createStorageModule } from './modules/storage';
|
|
27
27
|
import { createToolsModule } from './modules/tools';
|
|
28
28
|
import { createPlatformModule } from './modules/platform';
|
|
29
|
+
import { createAuthModule } from './modules/auth';
|
|
30
|
+
import { createCloudModule } from './modules/cloud';
|
|
29
31
|
import type { ChatableXSDK, ChatableXInitConfig, ToolInfo } from './types';
|
|
30
32
|
import pkg from '../package.json';
|
|
31
33
|
|
|
@@ -78,6 +80,8 @@ export const ChatableX = {
|
|
|
78
80
|
const toolModule = createToolModule(bridge, config.appId);
|
|
79
81
|
if (toolConfig) toolModule._setInfo(toolConfig);
|
|
80
82
|
|
|
83
|
+
const authModule = createAuthModule(bridge);
|
|
84
|
+
|
|
81
85
|
const sdk: ChatableXSDK = {
|
|
82
86
|
ai: createAIModule(bridge),
|
|
83
87
|
tools: createToolsModule(bridge),
|
|
@@ -86,6 +90,12 @@ export const ChatableX = {
|
|
|
86
90
|
storage: createStorageModule(bridge),
|
|
87
91
|
tool: toolModule,
|
|
88
92
|
platform: createPlatformModule(bridge),
|
|
93
|
+
auth: authModule,
|
|
94
|
+
cloud: createCloudModule(bridge, {
|
|
95
|
+
appId: config.appId,
|
|
96
|
+
auth: authModule,
|
|
97
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
98
|
+
}),
|
|
89
99
|
};
|
|
90
100
|
|
|
91
101
|
// Expose on window for debugging / Flutter interop
|
|
@@ -114,3 +124,9 @@ export const ChatableX = {
|
|
|
114
124
|
// Re-export all types
|
|
115
125
|
export * from './types';
|
|
116
126
|
export { Bridge } from './bridge';
|
|
127
|
+
export {
|
|
128
|
+
CloudError,
|
|
129
|
+
CloudAuthRequiredError,
|
|
130
|
+
CloudSubscriptionRequiredError,
|
|
131
|
+
CloudQuotaExceededError,
|
|
132
|
+
} from './modules/cloud';
|