chatablex-web-sdk 1.0.0 → 1.0.31
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 +754 -109
- package/README.zh-CN.md +751 -107
- package/dist/index.d.mts +44 -33
- package/dist/index.d.ts +44 -33
- package/dist/index.js +131 -10
- package/dist/index.mjs +131 -10
- package/package.json +13 -4
- package/src/bridge.ts +1 -1
- package/src/index.ts +6 -3
- package/src/modules/auth.ts +102 -0
- package/src/modules/platform.ts +14 -0
- package/src/modules/ui.ts +2 -2
- package/src/types.ts +48 -38
- package/src/modules/skills.ts +0 -14
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { Bridge } from '../bridge';
|
|
2
|
+
import type { AuthTokenData, ChatableXAuth } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Strategy interface behind `sdk.auth`. Lets us swap how a token is obtained
|
|
6
|
+
* (hosted WebView today, browser/unified-login later) without touching any
|
|
7
|
+
* consumer code or the public `ChatableXAuth` surface.
|
|
8
|
+
*/
|
|
9
|
+
export interface AuthProvider extends ChatableXAuth {}
|
|
10
|
+
|
|
11
|
+
/** Treat a token as expired this many ms before its real `expires_at`. */
|
|
12
|
+
const EXPIRY_SKEW_MS = 5_000;
|
|
13
|
+
|
|
14
|
+
/** Shape of the host reply to `host.getAuthToken`. */
|
|
15
|
+
type HostAuthResponse =
|
|
16
|
+
| { access_token: string; expires_at: number; user_id: string; error?: undefined }
|
|
17
|
+
| { error: string; access_token?: undefined };
|
|
18
|
+
|
|
19
|
+
function isValid(token: AuthTokenData | null, now: number): token is AuthTokenData {
|
|
20
|
+
return !!token && typeof token.access_token === 'string' && token.access_token.length > 0
|
|
21
|
+
&& token.expires_at - EXPIRY_SKEW_MS > now;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Auth provider that reuses the desktop host's login session over the bridge.
|
|
26
|
+
* Token lives in memory only; the refresh_token never crosses the bridge —
|
|
27
|
+
* the host refreshes before sending (see FR-05).
|
|
28
|
+
*/
|
|
29
|
+
export class HostAuthProvider implements AuthProvider {
|
|
30
|
+
private _bridge: Bridge;
|
|
31
|
+
private _token: AuthTokenData | null = null;
|
|
32
|
+
/** In-flight refresh, so concurrent callers share one host round-trip. */
|
|
33
|
+
private _refreshing: Promise<boolean> | null = null;
|
|
34
|
+
|
|
35
|
+
constructor(bridge: Bridge) {
|
|
36
|
+
this._bridge = bridge;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async getToken(): Promise<AuthTokenData | null> {
|
|
40
|
+
if (isValid(this._token, Date.now())) return this._token;
|
|
41
|
+
const ok = await this.refresh();
|
|
42
|
+
return ok ? this._token : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async getAuthHeaders(): Promise<Record<string, string>> {
|
|
46
|
+
const token = await this.getToken();
|
|
47
|
+
if (!token) return {};
|
|
48
|
+
return { Authorization: `Bearer ${token.access_token}` };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getUserId(): string | null {
|
|
52
|
+
return this._token?.user_id ?? null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
isAuthenticated(): boolean {
|
|
56
|
+
return isValid(this._token, Date.now());
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
refresh(): Promise<boolean> {
|
|
60
|
+
// single-flight: merge concurrent refreshes into one host round-trip
|
|
61
|
+
if (this._refreshing) return this._refreshing;
|
|
62
|
+
this._refreshing = this._doRefresh().finally(() => {
|
|
63
|
+
this._refreshing = null;
|
|
64
|
+
});
|
|
65
|
+
return this._refreshing;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private async _doRefresh(): Promise<boolean> {
|
|
69
|
+
try {
|
|
70
|
+
const raw = (await this._bridge.sendMessage('host.getAuthToken')) as HostAuthResponse | null;
|
|
71
|
+
if (
|
|
72
|
+
raw &&
|
|
73
|
+
typeof raw === 'object' &&
|
|
74
|
+
typeof raw.access_token === 'string' &&
|
|
75
|
+
raw.access_token.length > 0
|
|
76
|
+
) {
|
|
77
|
+
this._token = {
|
|
78
|
+
access_token: raw.access_token,
|
|
79
|
+
expires_at: Number(raw.expires_at) || 0,
|
|
80
|
+
user_id: String(raw.user_id ?? ''),
|
|
81
|
+
};
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
// not authenticated / not hosted / host returned { error }
|
|
85
|
+
this._token = null;
|
|
86
|
+
return false;
|
|
87
|
+
} catch {
|
|
88
|
+
// bridge unavailable (non-WebView) or host error — degrade safely
|
|
89
|
+
this._token = null;
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Build the `auth` module. Selects the provider for the current environment;
|
|
97
|
+
* today there is only `HostAuthProvider`. A future `WebAuthProvider`
|
|
98
|
+
* (browser / unified login) can be slotted in here without changing callers.
|
|
99
|
+
*/
|
|
100
|
+
export function createAuthModule(bridge: Bridge): ChatableXAuth {
|
|
101
|
+
return new HostAuthProvider(bridge);
|
|
102
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Bridge } from '../bridge';
|
|
2
|
+
import type { ChatableXPlatform } from '../types';
|
|
3
|
+
|
|
4
|
+
export function createPlatformModule(bridge: Bridge): ChatableXPlatform {
|
|
5
|
+
return {
|
|
6
|
+
async openInBrowser(targetUrl: string): Promise<void> {
|
|
7
|
+
const url = typeof targetUrl === 'string' ? targetUrl.trim() : '';
|
|
8
|
+
if (!url) {
|
|
9
|
+
throw new Error('openInBrowser: targetUrl is required');
|
|
10
|
+
}
|
|
11
|
+
await bridge.sendMessage('host.openInBrowser', { url });
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}
|
package/src/modules/ui.ts
CHANGED
|
@@ -16,11 +16,11 @@ export function createUIModule(bridge: Bridge): ChatableXUI {
|
|
|
16
16
|
},
|
|
17
17
|
|
|
18
18
|
openTab(config: TabConfig): Promise<void> {
|
|
19
|
-
return bridge.sendMessage('ui.openTab', config
|
|
19
|
+
return bridge.sendMessage('ui.openTab', config) as Promise<void>;
|
|
20
20
|
},
|
|
21
21
|
|
|
22
22
|
updateState(state: StateUpdate): Promise<void> {
|
|
23
|
-
return bridge.sendMessage('ui.updateState', state
|
|
23
|
+
return bridge.sendMessage('ui.updateState', state) as Promise<void>;
|
|
24
24
|
},
|
|
25
25
|
};
|
|
26
26
|
}
|
package/src/types.ts
CHANGED
|
@@ -80,38 +80,6 @@ export interface ToolResult {
|
|
|
80
80
|
|
|
81
81
|
export type ToolExecuteHandler = (params: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
82
82
|
|
|
83
|
-
// ---------------------------------------------------------------------------
|
|
84
|
-
// Skill
|
|
85
|
-
// ---------------------------------------------------------------------------
|
|
86
|
-
|
|
87
|
-
export interface Skill {
|
|
88
|
-
id: string;
|
|
89
|
-
name: string;
|
|
90
|
-
description: string;
|
|
91
|
-
version: string;
|
|
92
|
-
author?: string;
|
|
93
|
-
category?: string;
|
|
94
|
-
toolIds: string[];
|
|
95
|
-
variables: SkillVariable[];
|
|
96
|
-
installed: boolean;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface SkillVariable {
|
|
100
|
-
name: string;
|
|
101
|
-
type: string;
|
|
102
|
-
description: string;
|
|
103
|
-
required: boolean;
|
|
104
|
-
default?: unknown;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export interface SkillResult {
|
|
108
|
-
success: boolean;
|
|
109
|
-
data?: unknown;
|
|
110
|
-
error?: string;
|
|
111
|
-
skillId: string;
|
|
112
|
-
toolResults?: ToolResult[];
|
|
113
|
-
}
|
|
114
|
-
|
|
115
83
|
// ---------------------------------------------------------------------------
|
|
116
84
|
// UI
|
|
117
85
|
// ---------------------------------------------------------------------------
|
|
@@ -211,11 +179,6 @@ export interface ChatableXTools {
|
|
|
211
179
|
executeWithConfirm(toolId: string, params: Record<string, unknown>): Promise<ToolResult>;
|
|
212
180
|
}
|
|
213
181
|
|
|
214
|
-
export interface ChatableXSkills {
|
|
215
|
-
list(): Promise<Skill[]>;
|
|
216
|
-
execute(skillId: string, variables: Record<string, unknown>): Promise<SkillResult>;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
182
|
export interface ChatableXUI {
|
|
220
183
|
showNotification(message: string, type?: NotificationType): Promise<void>;
|
|
221
184
|
showConfirm(title: string, message: string): Promise<boolean>;
|
|
@@ -242,14 +205,61 @@ export interface ChatableXToolModule {
|
|
|
242
205
|
onExecute(handler: ToolExecuteHandler): void;
|
|
243
206
|
}
|
|
244
207
|
|
|
208
|
+
export interface ChatableXPlatform {
|
|
209
|
+
/** Open URL in system browser with auth handoff (WebView only; implemented by Flutter host). */
|
|
210
|
+
openInBrowser(targetUrl: string): Promise<void>;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
// Auth
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
/** Token payload returned by the host (never includes the refresh_token). */
|
|
218
|
+
export interface AuthTokenData {
|
|
219
|
+
/** Bearer access token to put in the Authorization header. */
|
|
220
|
+
access_token: string;
|
|
221
|
+
/** Access token expiry, epoch milliseconds. */
|
|
222
|
+
expires_at: number;
|
|
223
|
+
/** Authenticated user id. */
|
|
224
|
+
user_id: string;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Unified auth entry point for all WebUI apps.
|
|
229
|
+
*
|
|
230
|
+
* In a hosted (Flutter WebView) environment this reuses the desktop login
|
|
231
|
+
* session via the `host.getAuthToken` bridge call — apps never implement
|
|
232
|
+
* login or token handling themselves.
|
|
233
|
+
*/
|
|
234
|
+
export interface ChatableXAuth {
|
|
235
|
+
/**
|
|
236
|
+
* Get a valid access token. Returns the in-memory cached token when still
|
|
237
|
+
* valid, otherwise fetches a fresh one from the host. Returns `null` when
|
|
238
|
+
* not authenticated / not hosted.
|
|
239
|
+
*/
|
|
240
|
+
getToken(): Promise<AuthTokenData | null>;
|
|
241
|
+
/**
|
|
242
|
+
* Build auth headers ready to spread into a `fetch`. Returns
|
|
243
|
+
* `{ Authorization: "Bearer <token>" }` when authenticated, otherwise `{}`.
|
|
244
|
+
*/
|
|
245
|
+
getAuthHeaders(): Promise<Record<string, string>>;
|
|
246
|
+
/** Currently authenticated user id, or `null`. Synchronous (cache only). */
|
|
247
|
+
getUserId(): string | null;
|
|
248
|
+
/** Whether a valid token is currently cached. Synchronous (cache only). */
|
|
249
|
+
isAuthenticated(): boolean;
|
|
250
|
+
/** Force a token refresh via the host. Resolves `true` on success. */
|
|
251
|
+
refresh(): Promise<boolean>;
|
|
252
|
+
}
|
|
253
|
+
|
|
245
254
|
export interface ChatableXSDK {
|
|
246
255
|
ai: ChatableXAI;
|
|
247
256
|
tools: ChatableXTools;
|
|
248
|
-
skills: ChatableXSkills;
|
|
249
257
|
ui: ChatableXUI;
|
|
250
258
|
events: ChatableXEvents;
|
|
251
259
|
storage: ChatableXStorage;
|
|
252
260
|
tool: ChatableXToolModule;
|
|
261
|
+
platform: ChatableXPlatform;
|
|
262
|
+
auth: ChatableXAuth;
|
|
253
263
|
}
|
|
254
264
|
|
|
255
265
|
// ---------------------------------------------------------------------------
|
package/src/modules/skills.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { Bridge } from '../bridge';
|
|
2
|
-
import type { Skill, SkillResult, ChatableXSkills } from '../types';
|
|
3
|
-
|
|
4
|
-
export function createSkillsModule(bridge: Bridge): ChatableXSkills {
|
|
5
|
-
return {
|
|
6
|
-
list(): Promise<Skill[]> {
|
|
7
|
-
return bridge.sendMessage('skills.list', {}) as Promise<Skill[]>;
|
|
8
|
-
},
|
|
9
|
-
|
|
10
|
-
execute(skillId: string, variables: Record<string, unknown>): Promise<SkillResult> {
|
|
11
|
-
return bridge.sendMessage('skills.execute', { skillId, variables }) as Promise<SkillResult>;
|
|
12
|
-
},
|
|
13
|
-
};
|
|
14
|
-
}
|