@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.
package/dist/index.cjs CHANGED
@@ -32,19 +32,26 @@ var src_exports = {};
32
32
  __export(src_exports, {
33
33
  SinasAuthError: () => SinasAuthError,
34
34
  SinasClient: () => SinasClient,
35
+ SinasClientAuth: () => SinasClientAuth,
35
36
  SinasError: () => SinasError,
36
37
  SinasNotFoundError: () => SinasNotFoundError,
37
38
  SinasPermissionError: () => SinasPermissionError,
38
39
  SinasProvider: () => SinasProvider,
39
40
  SinasTimeoutError: () => SinasTimeoutError,
40
41
  approveToolCall: () => approveToolCall,
42
+ checkPermissions: () => checkPermissions,
41
43
  createChat: () => createChat,
42
44
  executeFunction: () => executeFunction,
43
45
  executeQuery: () => executeQuery,
44
46
  generateFileUrl: () => generateFileUrl,
45
47
  getChat: () => getChat,
48
+ getInfo: () => getInfo,
49
+ getMe: () => getMe,
46
50
  listCollections: () => listCollections,
47
51
  listFiles: () => listFiles,
52
+ login: () => login,
53
+ logout: () => logout,
54
+ refresh: () => refresh,
48
55
  sendMessage: () => sendMessage,
49
56
  stateDelete: () => stateDelete,
50
57
  stateGet: () => stateGet,
@@ -53,12 +60,14 @@ __export(src_exports, {
53
60
  streamFromChannel: () => streamFromChannel,
54
61
  streamMessage: () => streamMessage,
55
62
  uploadFile: () => uploadFile,
63
+ useAuth: () => useAuth,
56
64
  useChat: () => useChat,
57
65
  useClient: () => useClient,
58
66
  useExecute: () => useExecute,
59
67
  useQuery: () => useQuery,
60
68
  useSinas: () => useSinas,
61
- useStateStore: () => useStateStore
69
+ useStateStore: () => useStateStore,
70
+ verifyOTP: () => verifyOTP
62
71
  });
63
72
  module.exports = __toCommonJS(src_exports);
64
73
 
@@ -96,16 +105,131 @@ var SinasTimeoutError = class extends SinasError {
96
105
  }
97
106
  };
98
107
 
108
+ // src/client/auth.ts
109
+ function trimBase(baseUrl) {
110
+ return baseUrl.replace(/\/+$/, "");
111
+ }
112
+ async function authFetch(baseUrl, path, init = {}, accessToken) {
113
+ const headers = new Headers(init.headers);
114
+ if (!headers.has("Content-Type") && init.body !== void 0) {
115
+ headers.set("Content-Type", "application/json");
116
+ }
117
+ if (accessToken) headers.set("Authorization", `Bearer ${accessToken}`);
118
+ const res = await fetch(`${trimBase(baseUrl)}${path}`, { ...init, headers });
119
+ if (res.ok || res.status === 204) return res;
120
+ let detail = res.statusText;
121
+ try {
122
+ const body = await res.clone().json();
123
+ detail = body.detail || body.message || detail;
124
+ } catch {
125
+ }
126
+ if (res.status === 401) throw new SinasAuthError(detail);
127
+ if (res.status === 403) throw new SinasPermissionError(detail);
128
+ if (res.status === 404) throw new SinasNotFoundError(detail);
129
+ throw new SinasError(detail, res.status, detail);
130
+ }
131
+ async function getInfo(baseUrl) {
132
+ const res = await authFetch(baseUrl, "/info");
133
+ return await res.json();
134
+ }
135
+ async function login(baseUrl, request) {
136
+ const res = await authFetch(baseUrl, "/auth/login", {
137
+ method: "POST",
138
+ body: JSON.stringify(request)
139
+ });
140
+ return await res.json();
141
+ }
142
+ async function verifyOTP(baseUrl, request) {
143
+ const res = await authFetch(baseUrl, "/auth/verify-otp", {
144
+ method: "POST",
145
+ body: JSON.stringify(request)
146
+ });
147
+ return await res.json();
148
+ }
149
+ async function refresh(baseUrl, refreshToken) {
150
+ const res = await authFetch(baseUrl, "/auth/refresh", {
151
+ method: "POST",
152
+ body: JSON.stringify({ refresh_token: refreshToken })
153
+ });
154
+ return await res.json();
155
+ }
156
+ async function logout(baseUrl, refreshToken) {
157
+ try {
158
+ await authFetch(baseUrl, "/auth/logout", {
159
+ method: "POST",
160
+ body: JSON.stringify({ refresh_token: refreshToken })
161
+ });
162
+ } catch (e) {
163
+ if (e instanceof SinasNotFoundError) return;
164
+ throw e;
165
+ }
166
+ }
167
+ async function getMe(baseUrl, accessToken) {
168
+ const res = await authFetch(baseUrl, "/auth/me", { method: "GET" }, accessToken);
169
+ return await res.json();
170
+ }
171
+ async function checkPermissions(baseUrl, accessToken, request) {
172
+ const res = await authFetch(
173
+ baseUrl,
174
+ "/auth/check-permissions",
175
+ {
176
+ method: "POST",
177
+ body: JSON.stringify({ logic: "AND", ...request })
178
+ },
179
+ accessToken
180
+ );
181
+ return await res.json();
182
+ }
183
+
99
184
  // src/client/SinasClient.ts
185
+ function hasWindow() {
186
+ return typeof window !== "undefined";
187
+ }
188
+ function trim(url) {
189
+ return url.replace(/\/+$/, "");
190
+ }
191
+ var SinasClientAuth = class {
192
+ constructor(client) {
193
+ this.client = client;
194
+ }
195
+ getInfo() {
196
+ return getInfo(this.client.requireBaseUrl());
197
+ }
198
+ login(request) {
199
+ return login(this.client.requireBaseUrl(), request);
200
+ }
201
+ verifyOTP(request) {
202
+ return verifyOTP(this.client.requireBaseUrl(), request);
203
+ }
204
+ refresh(refreshToken) {
205
+ return refresh(this.client.requireBaseUrl(), refreshToken);
206
+ }
207
+ logout(refreshToken) {
208
+ return logout(this.client.requireBaseUrl(), refreshToken);
209
+ }
210
+ getMe() {
211
+ const token = this.client.getAccessToken();
212
+ if (!token) throw new SinasAuthError("No access token available");
213
+ return getMe(this.client.requireBaseUrl(), token);
214
+ }
215
+ checkPermissions(request) {
216
+ const token = this.client.getAccessToken();
217
+ if (!token) throw new SinasAuthError("No access token available");
218
+ return checkPermissions(this.client.requireBaseUrl(), token, request);
219
+ }
220
+ };
100
221
  var SinasClient = class {
101
- constructor(config) {
102
- this.configOverride = config;
222
+ constructor(options = {}) {
223
+ this.refreshInFlight = null;
224
+ this.options = options;
225
+ this.auth = new SinasClientAuth(this);
103
226
  }
227
+ // ─── Config / base URL ───
104
228
  getConfig() {
105
- const config = this.configOverride || window.__SINAS_CONFIG__;
229
+ const config = this.options.config || (hasWindow() ? window.__SINAS_CONFIG__ : void 0);
106
230
  if (!config) {
107
231
  return {
108
- apiBase: "",
232
+ apiBase: this.options.baseUrl ? `${trim(this.options.baseUrl)}/api/v1` : "",
109
233
  component: { namespace: "", name: "", version: "" },
110
234
  resources: {
111
235
  enabledAgents: [],
@@ -122,12 +246,56 @@ var SinasClient = class {
122
246
  getInput() {
123
247
  return this.getConfig().input || {};
124
248
  }
249
+ /** Workspace base URL (root of the Sinas instance). Throws if unset. */
250
+ requireBaseUrl() {
251
+ const url = this.getBaseUrl();
252
+ if (!url) {
253
+ throw new SinasError(
254
+ "No baseUrl configured. Construct with `new SinasClient({ baseUrl })` for standalone use.",
255
+ 0
256
+ );
257
+ }
258
+ return url;
259
+ }
260
+ getBaseUrl() {
261
+ if (this.options.baseUrl) return trim(this.options.baseUrl);
262
+ const apiBase = this.getConfig().apiBase;
263
+ if (!apiBase) return null;
264
+ return trim(apiBase).replace(/\/api\/v1$/, "");
265
+ }
266
+ // ─── Tokens ───
267
+ getAccessToken() {
268
+ if (this.options.getAccessToken) return this.options.getAccessToken();
269
+ if (hasWindow()) return window.__SINAS_AUTH_TOKEN__ ?? null;
270
+ return null;
271
+ }
272
+ getRefreshToken() {
273
+ return this.options.getRefreshToken ? this.options.getRefreshToken() : null;
274
+ }
275
+ async tryRefresh() {
276
+ const refreshToken = this.getRefreshToken();
277
+ if (!refreshToken) return null;
278
+ if (this.refreshInFlight) return this.refreshInFlight;
279
+ const baseUrl = this.getBaseUrl();
280
+ if (!baseUrl) return null;
281
+ this.refreshInFlight = (async () => {
282
+ try {
283
+ const resp = await refresh(baseUrl, refreshToken);
284
+ this.options.onTokenRefresh?.(resp.access_token);
285
+ return resp.access_token;
286
+ } catch {
287
+ return null;
288
+ } finally {
289
+ this.refreshInFlight = null;
290
+ }
291
+ })();
292
+ return this.refreshInFlight;
293
+ }
294
+ // ─── Headers / proxy ───
125
295
  getAuthHeaders() {
126
- const token = window.__SINAS_AUTH_TOKEN__;
296
+ const token = this.getAccessToken();
127
297
  const headers = { "Content-Type": "application/json" };
128
- if (token) {
129
- headers["Authorization"] = `Bearer ${token}`;
130
- }
298
+ if (token) headers["Authorization"] = `Bearer ${token}`;
131
299
  return headers;
132
300
  }
133
301
  getProxyBase() {
@@ -135,7 +303,8 @@ var SinasClient = class {
135
303
  const comp = config.component;
136
304
  return `${config.apiBase}/components/${comp.namespace}/${comp.name}/proxy`;
137
305
  }
138
- async fetch(url, options = {}) {
306
+ // ─── Fetch with refresh-on-401 ───
307
+ async fetch(url, options = {}, _retried = false) {
139
308
  const res = await fetch(url, {
140
309
  ...options,
141
310
  headers: {
@@ -143,15 +312,18 @@ var SinasClient = class {
143
312
  ...options.headers || {}
144
313
  }
145
314
  });
146
- if (!res.ok) {
147
- const body = await res.json().catch(() => ({ detail: res.statusText }));
148
- const message = body.detail || body.message || res.statusText;
149
- if (res.status === 401) throw new SinasAuthError(message);
150
- if (res.status === 403) throw new SinasPermissionError(message);
151
- if (res.status === 404) throw new SinasNotFoundError(message);
152
- throw new SinasError(message, res.status);
315
+ if (res.ok || res.status === 204) return res;
316
+ if (res.status === 401 && !_retried) {
317
+ const newToken = await this.tryRefresh();
318
+ if (newToken) return this.fetch(url, options, true);
319
+ this.options.onUnauthenticated?.();
153
320
  }
154
- return res;
321
+ const body = await res.json().catch(() => ({ detail: res.statusText }));
322
+ const message = body.detail || body.message || res.statusText;
323
+ if (res.status === 401) throw new SinasAuthError(message);
324
+ if (res.status === 403) throw new SinasPermissionError(message);
325
+ if (res.status === 404) throw new SinasNotFoundError(message);
326
+ throw new SinasError(message, res.status);
155
327
  }
156
328
  };
157
329
 
@@ -475,8 +647,14 @@ var import_react2 = __toESM(require("react"), 1);
475
647
  // src/hooks/context.ts
476
648
  var import_react = __toESM(require("react"), 1);
477
649
  var SinasContext = import_react.default.createContext(null);
478
- function SinasProvider({ client, children }) {
479
- const value = import_react.default.useMemo(() => client || new SinasClient(), [client]);
650
+ function SinasProvider({ client, options, children }) {
651
+ const value = import_react.default.useMemo(
652
+ () => client || new SinasClient(options),
653
+ // We intentionally only re-create when `client` reference changes; `options`
654
+ // is expected to be a stable object (callbacks reading from localStorage).
655
+ // eslint-disable-next-line react-hooks/exhaustive-deps
656
+ [client]
657
+ );
480
658
  return import_react.default.createElement(SinasContext.Provider, { value }, children);
481
659
  }
482
660
  function useClient() {
@@ -951,23 +1129,105 @@ function useSinas() {
951
1129
  useChat
952
1130
  };
953
1131
  }
1132
+
1133
+ // src/hooks/useAuth.ts
1134
+ var import_react6 = __toESM(require("react"), 1);
1135
+ function useAuth(options = {}) {
1136
+ const { autoRefresh = true } = options;
1137
+ const client = useClient();
1138
+ const [status, setStatus] = import_react6.default.useState("loading");
1139
+ const [user, setUser] = import_react6.default.useState(null);
1140
+ const [info, setInfo] = import_react6.default.useState(null);
1141
+ const [error, setError] = import_react6.default.useState(null);
1142
+ const refresh2 = import_react6.default.useCallback(async () => {
1143
+ setError(null);
1144
+ try {
1145
+ const [infoResp, meResp] = await Promise.all([
1146
+ client.auth.getInfo().catch(() => null),
1147
+ client.getAccessToken() ? client.auth.getMe().catch(() => null) : Promise.resolve(null)
1148
+ ]);
1149
+ if (infoResp) setInfo(infoResp);
1150
+ if (meResp) {
1151
+ setUser(meResp);
1152
+ setStatus("authenticated");
1153
+ } else {
1154
+ setUser(null);
1155
+ setStatus("unauthenticated");
1156
+ }
1157
+ } catch (e) {
1158
+ setError(e instanceof Error ? e.message : String(e));
1159
+ setStatus("unauthenticated");
1160
+ }
1161
+ }, [client]);
1162
+ import_react6.default.useEffect(() => {
1163
+ if (autoRefresh) void refresh2();
1164
+ }, [autoRefresh, refresh2]);
1165
+ const login2 = import_react6.default.useCallback(
1166
+ async (email, password) => {
1167
+ setError(null);
1168
+ try {
1169
+ return await client.auth.login({ email, password });
1170
+ } catch (e) {
1171
+ const msg = e instanceof Error ? e.message : String(e);
1172
+ setError(msg);
1173
+ throw e;
1174
+ }
1175
+ },
1176
+ [client]
1177
+ );
1178
+ const verifyOTP2 = import_react6.default.useCallback(
1179
+ async (sessionId, otpCode) => {
1180
+ setError(null);
1181
+ try {
1182
+ return await client.auth.verifyOTP({ session_id: sessionId, otp_code: otpCode });
1183
+ } catch (e) {
1184
+ const msg = e instanceof Error ? e.message : String(e);
1185
+ setError(msg);
1186
+ throw e;
1187
+ }
1188
+ },
1189
+ [client]
1190
+ );
1191
+ const logout2 = import_react6.default.useCallback(
1192
+ async (refreshToken) => {
1193
+ if (refreshToken) {
1194
+ try {
1195
+ await client.auth.logout(refreshToken);
1196
+ } catch (e) {
1197
+ if (!(e instanceof SinasAuthError)) throw e;
1198
+ }
1199
+ }
1200
+ setUser(null);
1201
+ setStatus("unauthenticated");
1202
+ },
1203
+ [client]
1204
+ );
1205
+ return { status, user, info, error, refresh: refresh2, login: login2, verifyOTP: verifyOTP2, logout: logout2 };
1206
+ }
954
1207
  // Annotate the CommonJS export names for ESM import in node:
955
1208
  0 && (module.exports = {
956
1209
  SinasAuthError,
957
1210
  SinasClient,
1211
+ SinasClientAuth,
958
1212
  SinasError,
959
1213
  SinasNotFoundError,
960
1214
  SinasPermissionError,
961
1215
  SinasProvider,
962
1216
  SinasTimeoutError,
963
1217
  approveToolCall,
1218
+ checkPermissions,
964
1219
  createChat,
965
1220
  executeFunction,
966
1221
  executeQuery,
967
1222
  generateFileUrl,
968
1223
  getChat,
1224
+ getInfo,
1225
+ getMe,
969
1226
  listCollections,
970
1227
  listFiles,
1228
+ login,
1229
+ logout,
1230
+ refresh,
971
1231
  sendMessage,
972
1232
  stateDelete,
973
1233
  stateGet,
@@ -976,11 +1236,13 @@ function useSinas() {
976
1236
  streamFromChannel,
977
1237
  streamMessage,
978
1238
  uploadFile,
1239
+ useAuth,
979
1240
  useChat,
980
1241
  useClient,
981
1242
  useExecute,
982
1243
  useQuery,
983
1244
  useSinas,
984
- useStateStore
1245
+ useStateStore,
1246
+ verifyOTP
985
1247
  });
986
1248
  //# sourceMappingURL=index.cjs.map