@sinas/sdk 0.4.0 → 0.5.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.
@@ -44,19 +44,26 @@ var SinasSDK = (() => {
44
44
  __export(src_exports, {
45
45
  SinasAuthError: () => SinasAuthError,
46
46
  SinasClient: () => SinasClient,
47
+ SinasClientAuth: () => SinasClientAuth,
47
48
  SinasError: () => SinasError,
48
49
  SinasNotFoundError: () => SinasNotFoundError,
49
50
  SinasPermissionError: () => SinasPermissionError,
50
51
  SinasProvider: () => SinasProvider,
51
52
  SinasTimeoutError: () => SinasTimeoutError,
52
53
  approveToolCall: () => approveToolCall,
54
+ checkPermissions: () => checkPermissions,
53
55
  createChat: () => createChat,
54
56
  executeFunction: () => executeFunction,
55
57
  executeQuery: () => executeQuery,
56
58
  generateFileUrl: () => generateFileUrl,
57
59
  getChat: () => getChat,
60
+ getInfo: () => getInfo,
61
+ getMe: () => getMe,
58
62
  listCollections: () => listCollections,
59
63
  listFiles: () => listFiles,
64
+ login: () => login,
65
+ logout: () => logout,
66
+ refresh: () => refresh,
60
67
  sendMessage: () => sendMessage,
61
68
  stateDelete: () => stateDelete,
62
69
  stateGet: () => stateGet,
@@ -65,12 +72,14 @@ var SinasSDK = (() => {
65
72
  streamFromChannel: () => streamFromChannel,
66
73
  streamMessage: () => streamMessage,
67
74
  uploadFile: () => uploadFile,
75
+ useAuth: () => useAuth,
68
76
  useChat: () => useChat,
69
77
  useClient: () => useClient,
70
78
  useExecute: () => useExecute,
71
79
  useQuery: () => useQuery,
72
80
  useSinas: () => useSinas,
73
- useStateStore: () => useStateStore
81
+ useStateStore: () => useStateStore,
82
+ verifyOTP: () => verifyOTP
74
83
  });
75
84
 
76
85
  // src/client/errors.ts
@@ -107,16 +116,131 @@ var SinasSDK = (() => {
107
116
  }
108
117
  };
109
118
 
119
+ // src/client/auth.ts
120
+ function trimBase(baseUrl) {
121
+ return baseUrl.replace(/\/+$/, "");
122
+ }
123
+ async function authFetch(baseUrl, path, init = {}, accessToken) {
124
+ const headers = new Headers(init.headers);
125
+ if (!headers.has("Content-Type") && init.body !== void 0) {
126
+ headers.set("Content-Type", "application/json");
127
+ }
128
+ if (accessToken) headers.set("Authorization", `Bearer ${accessToken}`);
129
+ const res = await fetch(`${trimBase(baseUrl)}${path}`, { ...init, headers });
130
+ if (res.ok || res.status === 204) return res;
131
+ let detail = res.statusText;
132
+ try {
133
+ const body = await res.clone().json();
134
+ detail = body.detail || body.message || detail;
135
+ } catch {
136
+ }
137
+ if (res.status === 401) throw new SinasAuthError(detail);
138
+ if (res.status === 403) throw new SinasPermissionError(detail);
139
+ if (res.status === 404) throw new SinasNotFoundError(detail);
140
+ throw new SinasError(detail, res.status, detail);
141
+ }
142
+ async function getInfo(baseUrl) {
143
+ const res = await authFetch(baseUrl, "/info");
144
+ return await res.json();
145
+ }
146
+ async function login(baseUrl, request) {
147
+ const res = await authFetch(baseUrl, "/auth/login", {
148
+ method: "POST",
149
+ body: JSON.stringify(request)
150
+ });
151
+ return await res.json();
152
+ }
153
+ async function verifyOTP(baseUrl, request) {
154
+ const res = await authFetch(baseUrl, "/auth/verify-otp", {
155
+ method: "POST",
156
+ body: JSON.stringify(request)
157
+ });
158
+ return await res.json();
159
+ }
160
+ async function refresh(baseUrl, refreshToken) {
161
+ const res = await authFetch(baseUrl, "/auth/refresh", {
162
+ method: "POST",
163
+ body: JSON.stringify({ refresh_token: refreshToken })
164
+ });
165
+ return await res.json();
166
+ }
167
+ async function logout(baseUrl, refreshToken) {
168
+ try {
169
+ await authFetch(baseUrl, "/auth/logout", {
170
+ method: "POST",
171
+ body: JSON.stringify({ refresh_token: refreshToken })
172
+ });
173
+ } catch (e) {
174
+ if (e instanceof SinasNotFoundError) return;
175
+ throw e;
176
+ }
177
+ }
178
+ async function getMe(baseUrl, accessToken) {
179
+ const res = await authFetch(baseUrl, "/auth/me", { method: "GET" }, accessToken);
180
+ return await res.json();
181
+ }
182
+ async function checkPermissions(baseUrl, accessToken, request) {
183
+ const res = await authFetch(
184
+ baseUrl,
185
+ "/auth/check-permissions",
186
+ {
187
+ method: "POST",
188
+ body: JSON.stringify({ logic: "AND", ...request })
189
+ },
190
+ accessToken
191
+ );
192
+ return await res.json();
193
+ }
194
+
110
195
  // src/client/SinasClient.ts
196
+ function hasWindow() {
197
+ return typeof window !== "undefined";
198
+ }
199
+ function trim(url) {
200
+ return url.replace(/\/+$/, "");
201
+ }
202
+ var SinasClientAuth = class {
203
+ constructor(client) {
204
+ this.client = client;
205
+ }
206
+ getInfo() {
207
+ return getInfo(this.client.requireBaseUrl());
208
+ }
209
+ login(request) {
210
+ return login(this.client.requireBaseUrl(), request);
211
+ }
212
+ verifyOTP(request) {
213
+ return verifyOTP(this.client.requireBaseUrl(), request);
214
+ }
215
+ refresh(refreshToken) {
216
+ return refresh(this.client.requireBaseUrl(), refreshToken);
217
+ }
218
+ logout(refreshToken) {
219
+ return logout(this.client.requireBaseUrl(), refreshToken);
220
+ }
221
+ getMe() {
222
+ const token = this.client.getAccessToken();
223
+ if (!token) throw new SinasAuthError("No access token available");
224
+ return getMe(this.client.requireBaseUrl(), token);
225
+ }
226
+ checkPermissions(request) {
227
+ const token = this.client.getAccessToken();
228
+ if (!token) throw new SinasAuthError("No access token available");
229
+ return checkPermissions(this.client.requireBaseUrl(), token, request);
230
+ }
231
+ };
111
232
  var SinasClient = class {
112
- constructor(config) {
113
- this.configOverride = config;
233
+ constructor(options = {}) {
234
+ this.refreshInFlight = null;
235
+ this.options = options;
236
+ this.auth = new SinasClientAuth(this);
114
237
  }
238
+ // ─── Config / base URL ───
115
239
  getConfig() {
116
- const config = this.configOverride || window.__SINAS_CONFIG__;
240
+ const config = this.options.config || (hasWindow() ? window.__SINAS_CONFIG__ : void 0);
117
241
  if (!config) {
118
242
  return {
119
- apiBase: "",
243
+ apiBase: this.options.baseUrl ? `${trim(this.options.baseUrl)}/api/v1` : "",
120
244
  component: { namespace: "", name: "", version: "" },
121
245
  resources: {
122
246
  enabledAgents: [],
@@ -133,12 +257,56 @@ var SinasSDK = (() => {
133
257
  getInput() {
134
258
  return this.getConfig().input || {};
135
259
  }
260
+ /** Workspace base URL (root of the Sinas instance). Throws if unset. */
261
+ requireBaseUrl() {
262
+ const url = this.getBaseUrl();
263
+ if (!url) {
264
+ throw new SinasError(
265
+ "No baseUrl configured. Construct with `new SinasClient({ baseUrl })` for standalone use.",
266
+ 0
267
+ );
268
+ }
269
+ return url;
270
+ }
271
+ getBaseUrl() {
272
+ if (this.options.baseUrl) return trim(this.options.baseUrl);
273
+ const apiBase = this.getConfig().apiBase;
274
+ if (!apiBase) return null;
275
+ return trim(apiBase).replace(/\/api\/v1$/, "");
276
+ }
277
+ // ─── Tokens ───
278
+ getAccessToken() {
279
+ if (this.options.getAccessToken) return this.options.getAccessToken();
280
+ if (hasWindow()) return window.__SINAS_AUTH_TOKEN__ ?? null;
281
+ return null;
282
+ }
283
+ getRefreshToken() {
284
+ return this.options.getRefreshToken ? this.options.getRefreshToken() : null;
285
+ }
286
+ async tryRefresh() {
287
+ const refreshToken = this.getRefreshToken();
288
+ if (!refreshToken) return null;
289
+ if (this.refreshInFlight) return this.refreshInFlight;
290
+ const baseUrl = this.getBaseUrl();
291
+ if (!baseUrl) return null;
292
+ this.refreshInFlight = (async () => {
293
+ try {
294
+ const resp = await refresh(baseUrl, refreshToken);
295
+ this.options.onTokenRefresh?.(resp.access_token);
296
+ return resp.access_token;
297
+ } catch {
298
+ return null;
299
+ } finally {
300
+ this.refreshInFlight = null;
301
+ }
302
+ })();
303
+ return this.refreshInFlight;
304
+ }
305
+ // ─── Headers / proxy ───
136
306
  getAuthHeaders() {
137
- const token = window.__SINAS_AUTH_TOKEN__;
307
+ const token = this.getAccessToken();
138
308
  const headers = { "Content-Type": "application/json" };
139
- if (token) {
140
- headers["Authorization"] = `Bearer ${token}`;
141
- }
309
+ if (token) headers["Authorization"] = `Bearer ${token}`;
142
310
  return headers;
143
311
  }
144
312
  getProxyBase() {
@@ -146,7 +314,8 @@ var SinasSDK = (() => {
146
314
  const comp = config.component;
147
315
  return `${config.apiBase}/components/${comp.namespace}/${comp.name}/proxy`;
148
316
  }
149
- async fetch(url, options = {}) {
317
+ // ─── Fetch with refresh-on-401 ───
318
+ async fetch(url, options = {}, _retried = false) {
150
319
  const res = await fetch(url, {
151
320
  ...options,
152
321
  headers: {
@@ -154,15 +323,18 @@ var SinasSDK = (() => {
154
323
  ...options.headers || {}
155
324
  }
156
325
  });
157
- if (!res.ok) {
158
- const body = await res.json().catch(() => ({ detail: res.statusText }));
159
- const message = body.detail || body.message || res.statusText;
160
- if (res.status === 401) throw new SinasAuthError(message);
161
- if (res.status === 403) throw new SinasPermissionError(message);
162
- if (res.status === 404) throw new SinasNotFoundError(message);
163
- throw new SinasError(message, res.status);
326
+ if (res.ok || res.status === 204) return res;
327
+ if (res.status === 401 && !_retried) {
328
+ const newToken = await this.tryRefresh();
329
+ if (newToken) return this.fetch(url, options, true);
330
+ this.options.onUnauthenticated?.();
164
331
  }
165
- return res;
332
+ const body = await res.json().catch(() => ({ detail: res.statusText }));
333
+ const message = body.detail || body.message || res.statusText;
334
+ if (res.status === 401) throw new SinasAuthError(message);
335
+ if (res.status === 403) throw new SinasPermissionError(message);
336
+ if (res.status === 404) throw new SinasNotFoundError(message);
337
+ throw new SinasError(message, res.status);
166
338
  }
167
339
  };
168
340
 
@@ -486,8 +658,14 @@ var SinasSDK = (() => {
486
658
  // src/hooks/context.ts
487
659
  var import_react = __toESM(require_react(), 1);
488
660
  var SinasContext = import_react.default.createContext(null);
489
- function SinasProvider({ client, children }) {
490
- const value = import_react.default.useMemo(() => client || new SinasClient(), [client]);
661
+ function SinasProvider({ client, options, children }) {
662
+ const value = import_react.default.useMemo(
663
+ () => client || new SinasClient(options),
664
+ // We intentionally only re-create when `client` reference changes; `options`
665
+ // is expected to be a stable object (callbacks reading from localStorage).
666
+ // eslint-disable-next-line react-hooks/exhaustive-deps
667
+ [client]
668
+ );
491
669
  return import_react.default.createElement(SinasContext.Provider, { value }, children);
492
670
  }
493
671
  function useClient() {
@@ -962,6 +1140,81 @@ var SinasSDK = (() => {
962
1140
  useChat
963
1141
  };
964
1142
  }
1143
+
1144
+ // src/hooks/useAuth.ts
1145
+ var import_react6 = __toESM(require_react(), 1);
1146
+ function useAuth(options = {}) {
1147
+ const { autoRefresh = true } = options;
1148
+ const client = useClient();
1149
+ const [status, setStatus] = import_react6.default.useState("loading");
1150
+ const [user, setUser] = import_react6.default.useState(null);
1151
+ const [info, setInfo] = import_react6.default.useState(null);
1152
+ const [error, setError] = import_react6.default.useState(null);
1153
+ const refresh2 = import_react6.default.useCallback(async () => {
1154
+ setError(null);
1155
+ try {
1156
+ const [infoResp, meResp] = await Promise.all([
1157
+ client.auth.getInfo().catch(() => null),
1158
+ client.getAccessToken() ? client.auth.getMe().catch(() => null) : Promise.resolve(null)
1159
+ ]);
1160
+ if (infoResp) setInfo(infoResp);
1161
+ if (meResp) {
1162
+ setUser(meResp);
1163
+ setStatus("authenticated");
1164
+ } else {
1165
+ setUser(null);
1166
+ setStatus("unauthenticated");
1167
+ }
1168
+ } catch (e) {
1169
+ setError(e instanceof Error ? e.message : String(e));
1170
+ setStatus("unauthenticated");
1171
+ }
1172
+ }, [client]);
1173
+ import_react6.default.useEffect(() => {
1174
+ if (autoRefresh) void refresh2();
1175
+ }, [autoRefresh, refresh2]);
1176
+ const login2 = import_react6.default.useCallback(
1177
+ async (email, password) => {
1178
+ setError(null);
1179
+ try {
1180
+ return await client.auth.login({ email, password });
1181
+ } catch (e) {
1182
+ const msg = e instanceof Error ? e.message : String(e);
1183
+ setError(msg);
1184
+ throw e;
1185
+ }
1186
+ },
1187
+ [client]
1188
+ );
1189
+ const verifyOTP2 = import_react6.default.useCallback(
1190
+ async (sessionId, otpCode) => {
1191
+ setError(null);
1192
+ try {
1193
+ return await client.auth.verifyOTP({ session_id: sessionId, otp_code: otpCode });
1194
+ } catch (e) {
1195
+ const msg = e instanceof Error ? e.message : String(e);
1196
+ setError(msg);
1197
+ throw e;
1198
+ }
1199
+ },
1200
+ [client]
1201
+ );
1202
+ const logout2 = import_react6.default.useCallback(
1203
+ async (refreshToken) => {
1204
+ if (refreshToken) {
1205
+ try {
1206
+ await client.auth.logout(refreshToken);
1207
+ } catch (e) {
1208
+ if (!(e instanceof SinasAuthError)) throw e;
1209
+ }
1210
+ }
1211
+ setUser(null);
1212
+ setStatus("unauthenticated");
1213
+ },
1214
+ [client]
1215
+ );
1216
+ return { status, user, info, error, refresh: refresh2, login: login2, verifyOTP: verifyOTP2, logout: logout2 };
1217
+ }
965
1218
  return __toCommonJS(src_exports);
966
1219
  })();
967
1220
  //# sourceMappingURL=sinas-sdk.umd.js.map