@calimero-network/mero-js 1.0.2 → 1.2.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.
Files changed (57) hide show
  1. package/dist/admin-api/admin-client.d.ts +29 -27
  2. package/dist/admin-api/admin-client.d.ts.map +1 -1
  3. package/dist/admin-api/admin-client.js +70 -58
  4. package/dist/admin-api/admin-client.js.map +1 -1
  5. package/dist/admin-api/admin-types.d.ts +57 -159
  6. package/dist/admin-api/admin-types.d.ts.map +1 -1
  7. package/dist/admin-api/admin-types.js +1 -1
  8. package/dist/admin-api/admin-types.js.map +1 -1
  9. package/dist/auth/index.d.ts +26 -0
  10. package/dist/auth/index.d.ts.map +1 -0
  11. package/dist/auth/index.js +51 -0
  12. package/dist/auth/index.js.map +1 -0
  13. package/dist/events/index.d.ts +5 -0
  14. package/dist/events/index.d.ts.map +1 -0
  15. package/dist/events/index.js +3 -0
  16. package/dist/events/index.js.map +1 -0
  17. package/dist/events/sse.d.ts +41 -0
  18. package/dist/events/sse.d.ts.map +1 -0
  19. package/dist/events/sse.js +237 -0
  20. package/dist/events/sse.js.map +1 -0
  21. package/dist/events/ws.d.ts +42 -0
  22. package/dist/events/ws.d.ts.map +1 -0
  23. package/dist/events/ws.js +178 -0
  24. package/dist/events/ws.js.map +1 -0
  25. package/dist/http-client/http-factory.d.ts +18 -0
  26. package/dist/http-client/http-factory.d.ts.map +1 -1
  27. package/dist/http-client/http-factory.js +2 -0
  28. package/dist/http-client/http-factory.js.map +1 -1
  29. package/dist/http-client/http-types.d.ts +6 -0
  30. package/dist/http-client/http-types.d.ts.map +1 -1
  31. package/dist/http-client/web-client.d.ts +2 -0
  32. package/dist/http-client/web-client.d.ts.map +1 -1
  33. package/dist/http-client/web-client.js +129 -58
  34. package/dist/http-client/web-client.js.map +1 -1
  35. package/dist/index.browser.mjs +2 -1
  36. package/dist/index.browser.mjs.map +4 -4
  37. package/dist/index.cjs +784 -148
  38. package/dist/index.cjs.map +4 -4
  39. package/dist/index.d.ts +8 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +8 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/index.mjs +784 -148
  44. package/dist/index.mjs.map +4 -4
  45. package/dist/mero-js.d.ts +47 -1
  46. package/dist/mero-js.d.ts.map +1 -1
  47. package/dist/mero-js.js +132 -16
  48. package/dist/mero-js.js.map +1 -1
  49. package/dist/rpc/index.d.ts +21 -0
  50. package/dist/rpc/index.d.ts.map +1 -0
  51. package/dist/rpc/index.js +39 -0
  52. package/dist/rpc/index.js.map +1 -0
  53. package/dist/token-store/index.d.ts +20 -0
  54. package/dist/token-store/index.d.ts.map +1 -0
  55. package/dist/token-store/index.js +62 -0
  56. package/dist/token-store/index.js.map +1 -0
  57. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -22,8 +22,15 @@ __export(index_exports, {
22
22
  AdminApiClient: () => AdminApiClient,
23
23
  AuthApiClient: () => AuthApiClient,
24
24
  HTTPError: () => HTTPError,
25
+ LocalStorageTokenStore: () => LocalStorageTokenStore,
26
+ MemoryTokenStore: () => MemoryTokenStore,
25
27
  MeroJs: () => MeroJs,
28
+ RpcClient: () => RpcClient,
29
+ RpcError: () => RpcError,
30
+ SseClient: () => SseClient,
26
31
  WebHttpClient: () => WebHttpClient,
32
+ WsClient: () => WsClient,
33
+ buildAuthLoginUrl: () => buildAuthLoginUrl,
27
34
  combineSignals: () => combineSignals,
28
35
  createAdminApiClient: () => createAdminApiClient,
29
36
  createAdminApiClientFromHttpClient: () => createAdminApiClientFromHttpClient,
@@ -40,6 +47,7 @@ __export(index_exports, {
40
47
  createRetryableMethod: () => createRetryableMethod,
41
48
  createTimeoutSignal: () => createTimeoutSignal,
42
49
  createUniversalHttpClient: () => createUniversalHttpClient,
50
+ parseAuthCallback: () => parseAuthCallback,
43
51
  withRetry: () => withRetry
44
52
  });
45
53
  module.exports = __toCommonJS(index_exports);
@@ -108,6 +116,10 @@ function headersToRecord(headers) {
108
116
  var WebHttpClient = class {
109
117
  constructor(transport) {
110
118
  this.transport = transport;
119
+ // Cache for concurrent refresh token calls to prevent race conditions
120
+ this.refreshTokenPromise = null;
121
+ // Cache for concurrent onTokenRefresh calls to prevent duplicate callbacks
122
+ this.onTokenRefreshPromise = null;
111
123
  }
112
124
  async get(path, init) {
113
125
  return this.request(path, { ...init, method: "GET" });
@@ -162,61 +174,10 @@ var WebHttpClient = class {
162
174
  async request(path, init) {
163
175
  return this.makeRequest(path, init);
164
176
  }
165
- async makeRequest(path, init) {
177
+ async makeRequest(path, init, retryCount = 0, requestStartTime) {
178
+ const MAX_RETRY_ATTEMPTS = 1;
166
179
  const url = this.buildUrl(path);
167
- const hasTauriInternals = typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
168
- const hasTauri = typeof window !== "undefined" && "__TAURI__" in window;
169
- const isTauri = hasTauriInternals || hasTauri || this.transport.credentials === "omit";
170
- if (isTauri) {
171
- const headers2 = await this.buildHeaders(init?.headers);
172
- let headersObj2;
173
- if (headers2 instanceof Headers) {
174
- headersObj2 = {};
175
- headers2.forEach((value, key) => {
176
- headersObj2[key] = value;
177
- });
178
- } else {
179
- headersObj2 = headers2;
180
- }
181
- const requestInit2 = {};
182
- if (init?.method && init.method !== "GET") {
183
- requestInit2.method = init.method;
184
- }
185
- if (headersObj2 && Object.keys(headersObj2).length > 0) {
186
- requestInit2.headers = headersObj2;
187
- }
188
- if (init?.body !== void 0 && init.body !== null) {
189
- requestInit2.body = init.body;
190
- }
191
- if (this.transport.credentials !== void 0) {
192
- requestInit2.credentials = this.transport.credentials;
193
- }
194
- try {
195
- const response = await globalThis.fetch(url, requestInit2);
196
- if (!response.ok) {
197
- const bodyText = await this.getBodyText(response);
198
- throw new HTTPError(
199
- response.status,
200
- response.statusText,
201
- url,
202
- response.headers,
203
- bodyText
204
- );
205
- }
206
- return this.parseResponse(response, init?.parse);
207
- } catch (error) {
208
- if (error instanceof HTTPError) {
209
- throw error;
210
- }
211
- throw new HTTPError(
212
- 0,
213
- "Network Error",
214
- url,
215
- new Headers(),
216
- error instanceof Error ? error.message : "Unknown error"
217
- );
218
- }
219
- }
180
+ const startTime = requestStartTime ?? Date.now();
220
181
  const signal = this.createAbortSignal(init);
221
182
  const headers = await this.buildHeaders(init?.headers);
222
183
  let headersObj;
@@ -232,11 +193,27 @@ var WebHttpClient = class {
232
193
  method: init?.method || "GET",
233
194
  headers: headersObj
234
195
  };
235
- if (init?.body !== void 0) {
196
+ const isStreamBody = init?.body instanceof ReadableStream || typeof init?.body === "object" && init?.body !== null && "getReader" in init.body && !(init.body instanceof Blob);
197
+ if (init?.body !== void 0 && !isStreamBody) {
198
+ requestInit.body = init.body;
199
+ } else if (init?.body !== void 0 && isStreamBody && retryCount === 0) {
236
200
  requestInit.body = init.body;
237
201
  }
238
- if (signal) {
239
- requestInit.signal = signal;
202
+ let retrySignal;
203
+ if (retryCount > 0 && requestStartTime !== void 0) {
204
+ const timeoutMs = init?.timeoutMs || this.transport.timeoutMs;
205
+ if (timeoutMs) {
206
+ const elapsed = Date.now() - startTime;
207
+ const remaining = Math.max(0, timeoutMs - elapsed);
208
+ retrySignal = this.createAbortSignal({ ...init, timeoutMs: remaining });
209
+ } else {
210
+ retrySignal = this.createAbortSignal(init);
211
+ }
212
+ } else {
213
+ retrySignal = signal;
214
+ }
215
+ if (retrySignal) {
216
+ requestInit.signal = retrySignal;
240
217
  }
241
218
  if (this.transport.credentials !== void 0) {
242
219
  requestInit.credentials = this.transport.credentials;
@@ -266,19 +243,70 @@ var WebHttpClient = class {
266
243
  const response = await this.transport.fetch(url, requestInit);
267
244
  if (!response.ok) {
268
245
  const bodyText = await this.getBodyText(response);
269
- throw new HTTPError(
246
+ const httpError = new HTTPError(
270
247
  response.status,
271
248
  response.statusText,
272
249
  url,
273
250
  response.headers,
274
251
  bodyText
275
252
  );
253
+ const userAborted = init?.signal?.aborted === true;
254
+ if (response.status === 401 && this.transport.refreshToken && response.headers.get("x-auth-error") === "token_expired" && retryCount < MAX_RETRY_ATTEMPTS && !isStreamBody && !userAborted) {
255
+ try {
256
+ let refreshPromise = this.refreshTokenPromise;
257
+ if (!refreshPromise) {
258
+ refreshPromise = this.transport.refreshToken();
259
+ this.refreshTokenPromise = refreshPromise;
260
+ }
261
+ const newToken = await refreshPromise;
262
+ if (!newToken || newToken.trim() === "") {
263
+ this.refreshTokenPromise = null;
264
+ this.onTokenRefreshPromise = null;
265
+ throw new Error("Refresh token returned empty token");
266
+ }
267
+ if (!this.transport.onTokenRefresh) {
268
+ this.refreshTokenPromise = null;
269
+ this.onTokenRefreshPromise = null;
270
+ throw new Error(
271
+ "onTokenRefresh callback is required when refreshToken is provided. The callback must update the token storage so getAuthToken() returns the new token."
272
+ );
273
+ }
274
+ let onTokenRefreshPromise = this.onTokenRefreshPromise;
275
+ if (!onTokenRefreshPromise) {
276
+ onTokenRefreshPromise = this.transport.onTokenRefresh(newToken);
277
+ this.onTokenRefreshPromise = onTokenRefreshPromise;
278
+ }
279
+ await onTokenRefreshPromise;
280
+ this.refreshTokenPromise = null;
281
+ this.onTokenRefreshPromise = null;
282
+ const timeoutMs = init?.timeoutMs || this.transport.timeoutMs;
283
+ if (timeoutMs && requestStartTime !== void 0) {
284
+ const elapsed = Date.now() - startTime;
285
+ const remaining = timeoutMs - elapsed;
286
+ if (remaining <= 0) {
287
+ throw httpError;
288
+ }
289
+ }
290
+ return this.makeRequest(path, init, retryCount + 1, startTime);
291
+ } catch (refreshError) {
292
+ this.refreshTokenPromise = null;
293
+ this.onTokenRefreshPromise = null;
294
+ if (refreshError instanceof Error && refreshError.message.includes("onTokenRefresh")) {
295
+ throw refreshError;
296
+ }
297
+ throw httpError;
298
+ }
299
+ }
300
+ throw httpError;
276
301
  }
277
302
  return this.parseResponse(response, init?.parse);
278
303
  } catch (error) {
279
304
  if (error instanceof HTTPError) {
280
305
  throw error;
281
306
  }
307
+ if (error instanceof Error && error.message.includes("onTokenRefresh")) {
308
+ throw error;
309
+ }
282
310
  throw new HTTPError(
283
311
  0,
284
312
  "Network Error",
@@ -302,10 +330,6 @@ var WebHttpClient = class {
302
330
  }
303
331
  }
304
332
  createAbortSignal(init) {
305
- const isTauri = typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
306
- if (isTauri) {
307
- return void 0;
308
- }
309
333
  const signals = [];
310
334
  if (this.transport.defaultAbortSignal) {
311
335
  signals.push(this.transport.defaultAbortSignal);
@@ -384,6 +408,7 @@ function createBrowserHttpClient(options) {
384
408
  baseUrl: options.baseUrl,
385
409
  getAuthToken: options.getAuthToken,
386
410
  onTokenRefresh: options.onTokenRefresh,
411
+ refreshToken: options.refreshToken,
387
412
  defaultHeaders: options.defaultHeaders,
388
413
  timeoutMs: options.timeoutMs,
389
414
  credentials: options.credentials,
@@ -408,6 +433,7 @@ function createNodeHttpClient(options) {
408
433
  baseUrl: options.baseUrl,
409
434
  getAuthToken: options.getAuthToken,
410
435
  onTokenRefresh: options.onTokenRefresh,
436
+ refreshToken: options.refreshToken,
411
437
  defaultHeaders: options.defaultHeaders,
412
438
  timeoutMs: options.timeoutMs,
413
439
  credentials: options.credentials,
@@ -663,122 +689,117 @@ function createAuthApiClientFromHttpClient(httpClient, _config) {
663
689
  }
664
690
 
665
691
  // src/admin-api/admin-client.ts
692
+ function unwrap(response) {
693
+ return response.data;
694
+ }
666
695
  var AdminApiClient = class {
667
696
  constructor(httpClient) {
668
697
  this.httpClient = httpClient;
669
698
  }
670
- // Health and Status Endpoints
699
+ // ---- Health and Status (public, no auth) ----
671
700
  async healthCheck() {
672
- const response = await this.httpClient.get("/health");
673
- if (!response.data) {
674
- throw new Error("Health response data is null");
675
- }
676
- return response.data;
701
+ return unwrap(await this.httpClient.get("/admin-api/health"));
677
702
  }
678
703
  async isAuthed() {
679
- return this.httpClient.get("/is-authed");
704
+ return this.httpClient.get("/admin-api/is-authed");
680
705
  }
681
- // Application Management Endpoints
706
+ // ---- Application Management ----
682
707
  async installApplication(request) {
683
- return this.httpClient.post(
684
- "/install-application",
685
- request
686
- );
708
+ return unwrap(await this.httpClient.post("/admin-api/install-application", request));
687
709
  }
688
710
  async installDevApplication(request) {
689
- return this.httpClient.post(
690
- "/install-dev-application",
691
- request
692
- );
711
+ return unwrap(await this.httpClient.post("/admin-api/install-dev-application", request));
693
712
  }
694
713
  async uninstallApplication(appId) {
695
- return this.httpClient.delete(
696
- `/applications/${appId}`
697
- );
714
+ return unwrap(await this.httpClient.delete(`/admin-api/applications/${appId}`));
698
715
  }
699
716
  async listApplications() {
700
- return this.httpClient.get("/applications");
717
+ return unwrap(await this.httpClient.get("/admin-api/applications"));
701
718
  }
702
719
  async getApplication(appId) {
720
+ return unwrap(await this.httpClient.get(`/admin-api/applications/${appId}`));
721
+ }
722
+ // ---- Package Management ----
723
+ async getLatestPackageVersion(packageName) {
703
724
  return this.httpClient.get(
704
- `/applications/${appId}`
725
+ `/admin-api/packages/${encodeURIComponent(packageName)}/latest`
705
726
  );
706
727
  }
707
- // Context Management Endpoints
728
+ // ---- Context Management ----
708
729
  async createContext(request) {
709
- return this.httpClient.post("/contexts", request);
730
+ return unwrap(await this.httpClient.post("/admin-api/contexts", request));
710
731
  }
711
732
  async deleteContext(contextId) {
712
- return this.httpClient.delete(
713
- `/contexts/${contextId}`
714
- );
733
+ return unwrap(await this.httpClient.delete(`/admin-api/contexts/${contextId}`));
715
734
  }
716
735
  async getContexts() {
717
- return this.httpClient.get("/contexts");
736
+ return unwrap(await this.httpClient.get("/admin-api/contexts"));
718
737
  }
719
738
  async getContext(contextId) {
720
- return this.httpClient.get(`/contexts/${contextId}`);
739
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/${contextId}`));
721
740
  }
722
- // Blob Management Endpoints
723
- async uploadBlob(request) {
724
- return this.httpClient.post("/blobs", request);
741
+ async getContextsForApplication(applicationId) {
742
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/for-application/${applicationId}`));
725
743
  }
726
- async deleteBlob(blobId) {
727
- return this.httpClient.delete(`/blobs/${blobId}`);
744
+ // ---- Context Identity ----
745
+ async generateContextIdentity() {
746
+ return unwrap(await this.httpClient.post("/admin-api/identity/context", {}));
728
747
  }
729
- async listBlobs() {
730
- return this.httpClient.get("/blobs");
748
+ async getContextIdentities(contextId) {
749
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/${contextId}/identities`));
731
750
  }
732
- async getBlob(blobId) {
733
- return this.httpClient.get(`/blobs/${blobId}`);
751
+ async getContextIdentitiesOwned(contextId) {
752
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/${contextId}/identities-owned`));
734
753
  }
735
- // Alias Management Endpoints
736
- async createAlias(request) {
737
- return this.httpClient.post("/alias", request);
754
+ // ---- Context Invite / Join ----
755
+ async inviteToContext(request) {
756
+ return unwrap(await this.httpClient.post("/admin-api/contexts/invite", request));
738
757
  }
739
- async deleteAlias(aliasId) {
740
- return this.httpClient.delete(`/alias/${aliasId}`);
758
+ async joinContext(request) {
759
+ return unwrap(await this.httpClient.post("/admin-api/contexts/join", request));
741
760
  }
742
- async listAliases() {
743
- return this.httpClient.get("/alias");
761
+ // ---- Blob Management ----
762
+ async uploadBlob(data) {
763
+ return unwrap(await this.httpClient.put("/admin-api/blobs", data));
744
764
  }
745
- async getAlias(aliasId) {
746
- return this.httpClient.get(`/alias/${aliasId}`);
765
+ async deleteBlob(blobId) {
766
+ return unwrap(await this.httpClient.delete(`/admin-api/blobs/${blobId}`));
747
767
  }
748
- // Network Management Endpoints
749
- async getNetworkPeers() {
750
- return this.httpClient.get("/network/peers");
768
+ async listBlobs() {
769
+ return unwrap(await this.httpClient.get("/admin-api/blobs"));
751
770
  }
752
- async getNetworkStats() {
753
- return this.httpClient.get("/network/stats");
771
+ async getBlob(blobId) {
772
+ return unwrap(await this.httpClient.get(`/admin-api/blobs/${blobId}`));
754
773
  }
755
- async getNetworkConfig() {
756
- return this.httpClient.get("/network/config");
774
+ // ---- Alias Management ----
775
+ // Server uses type-specific alias routes: /admin-api/alias/{create,lookup,delete,list}/{context,application}
776
+ async createContextAlias(request) {
777
+ return this.httpClient.post("/admin-api/alias/create/context", request);
757
778
  }
758
- async updateNetworkConfig(request) {
759
- return this.httpClient.put(
760
- "/network/config",
761
- request
762
- );
779
+ async createApplicationAlias(request) {
780
+ return this.httpClient.post("/admin-api/alias/create/application", request);
763
781
  }
764
- async getPeersCount() {
765
- return this.httpClient.get("/network/peers/count");
782
+ async lookupContextAlias(name) {
783
+ return this.httpClient.post(`/admin-api/alias/lookup/context/${encodeURIComponent(name)}`, {});
766
784
  }
767
- // System Management Endpoints
768
- async getSystemInfo() {
769
- return this.httpClient.get("/system/info");
785
+ async lookupApplicationAlias(name) {
786
+ return this.httpClient.post(`/admin-api/alias/lookup/application/${encodeURIComponent(name)}`, {});
770
787
  }
771
- async getSystemLogs() {
772
- return this.httpClient.get("/system/logs");
788
+ async deleteContextAlias(name) {
789
+ return this.httpClient.post(`/admin-api/alias/delete/context/${encodeURIComponent(name)}`, {});
773
790
  }
774
- async getSystemMetrics() {
775
- return this.httpClient.get("/system/metrics");
791
+ async deleteApplicationAlias(name) {
792
+ return this.httpClient.post(`/admin-api/alias/delete/application/${encodeURIComponent(name)}`, {});
776
793
  }
777
- async restartSystem() {
778
- return this.httpClient.post("/system/restart");
794
+ async listContextAliases() {
795
+ return unwrap(await this.httpClient.get("/admin-api/alias/list/context"));
779
796
  }
780
- async shutdownSystem() {
781
- return this.httpClient.post("/system/shutdown");
797
+ async listApplicationAliases() {
798
+ return unwrap(await this.httpClient.get("/admin-api/alias/list/application"));
799
+ }
800
+ // ---- Network ----
801
+ async getPeersCount() {
802
+ return this.httpClient.get("/admin-api/peers");
782
803
  }
783
804
  };
784
805
 
@@ -836,15 +857,495 @@ function createAdminApiClientFromHttpClient(httpClient, _config) {
836
857
  return new AdminApiClient(httpClient);
837
858
  }
838
859
 
860
+ // src/auth/index.ts
861
+ function parseAuthCallback(url) {
862
+ try {
863
+ const hashIndex = url.indexOf("#");
864
+ if (hashIndex === -1) return null;
865
+ const hash = url.substring(hashIndex + 1);
866
+ const params = new URLSearchParams(hash);
867
+ const accessToken = params.get("access_token");
868
+ if (!accessToken) return null;
869
+ return {
870
+ accessToken,
871
+ refreshToken: params.get("refresh_token") ?? "",
872
+ applicationId: params.get("application_id") ?? "",
873
+ contextId: params.get("context_id") ?? "",
874
+ contextIdentity: params.get("context_identity") ?? "",
875
+ nodeUrl: params.get("node_url") ?? ""
876
+ };
877
+ } catch {
878
+ return null;
879
+ }
880
+ }
881
+ function buildAuthLoginUrl(nodeUrl, opts) {
882
+ const params = new URLSearchParams();
883
+ params.set("callback-url", opts.callbackUrl);
884
+ if (opts.permissions && opts.permissions.length > 0) {
885
+ params.set("permissions", opts.permissions.join(","));
886
+ }
887
+ params.set("mode", opts.mode);
888
+ if (opts.packageName) {
889
+ params.set("package-name", opts.packageName);
890
+ if (opts.packageVersion) {
891
+ params.set("package-version", opts.packageVersion);
892
+ }
893
+ if (opts.registryUrl) {
894
+ params.set("registry-url", opts.registryUrl);
895
+ }
896
+ }
897
+ const base = nodeUrl.replace(/\/+$/, "");
898
+ return `${base}/auth/login?${params.toString()}`;
899
+ }
900
+
901
+ // src/rpc/index.ts
902
+ var RpcError = class extends Error {
903
+ constructor(code, message, data, type) {
904
+ super(message);
905
+ this.name = "RpcError";
906
+ this.code = code;
907
+ this.data = data;
908
+ this.type = type;
909
+ }
910
+ };
911
+ var RpcClient = class {
912
+ constructor(opts) {
913
+ this.httpClient = opts.httpClient;
914
+ }
915
+ async execute(params) {
916
+ const body = {
917
+ jsonrpc: "2.0",
918
+ id: 1,
919
+ method: "execute",
920
+ params: {
921
+ contextId: params.contextId,
922
+ method: params.method,
923
+ argsJson: params.argsJson ?? {},
924
+ executorPublicKey: params.executorPublicKey
925
+ }
926
+ };
927
+ const response = await this.httpClient.post(
928
+ "/jsonrpc",
929
+ body
930
+ );
931
+ if (response.error) {
932
+ const err = response.error;
933
+ const code = err.code ?? -1;
934
+ const message = err.message ?? err.type ?? "RPC error";
935
+ throw new RpcError(code, message, err.data, err.type);
936
+ }
937
+ if (response.result && "output" in response.result) {
938
+ return response.result.output;
939
+ }
940
+ return response.result;
941
+ }
942
+ };
943
+
944
+ // src/events/sse.ts
945
+ var SseClient = class {
946
+ constructor(opts) {
947
+ this.sessionId = null;
948
+ this.abortController = null;
949
+ this.reconnectTimer = null;
950
+ this.subscribedContextIds = /* @__PURE__ */ new Set();
951
+ this.closed = false;
952
+ this.listeners = { connect: [], event: [], error: [] };
953
+ this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
954
+ this.getAuthToken = opts.getAuthToken;
955
+ this.reconnectDelayMs = opts.reconnectDelayMs ?? 3e3;
956
+ }
957
+ on(event, handler) {
958
+ const key = event;
959
+ if (key in this.listeners) {
960
+ const arr = this.listeners[key];
961
+ if (!arr.includes(handler)) arr.push(handler);
962
+ }
963
+ }
964
+ off(event, handler) {
965
+ const key = event;
966
+ if (key in this.listeners) {
967
+ const arr = this.listeners[key];
968
+ const idx = arr.indexOf(handler);
969
+ if (idx !== -1) arr.splice(idx, 1);
970
+ }
971
+ }
972
+ emit(event, arg) {
973
+ const key = event;
974
+ if (key in this.listeners) {
975
+ for (const handler of this.listeners[key]) {
976
+ try {
977
+ handler(arg);
978
+ } catch {
979
+ }
980
+ }
981
+ }
982
+ }
983
+ async connect() {
984
+ if (this.abortController && !this.closed) {
985
+ return;
986
+ }
987
+ this.closed = false;
988
+ this.abortController = new AbortController();
989
+ try {
990
+ const token = await this.getAuthToken();
991
+ const response = await fetch(`${this.baseUrl}/sse`, {
992
+ headers: {
993
+ "Authorization": `Bearer ${token}`,
994
+ "Accept": "text/event-stream"
995
+ },
996
+ signal: this.abortController.signal
997
+ });
998
+ if (!response.ok) {
999
+ throw new Error(`SSE connection failed: ${response.status}`);
1000
+ }
1001
+ if (!response.body) {
1002
+ throw new Error("SSE response has no body");
1003
+ }
1004
+ this.readStream(response.body).catch((err) => {
1005
+ if (this.closed) return;
1006
+ const error = err instanceof Error ? err : new Error(String(err));
1007
+ this.emit("error", error);
1008
+ this.scheduleReconnect();
1009
+ });
1010
+ } catch (err) {
1011
+ if (this.closed) return;
1012
+ const error = err instanceof Error ? err : new Error(String(err));
1013
+ this.emit("error", error);
1014
+ this.scheduleReconnect();
1015
+ }
1016
+ }
1017
+ async readStream(body) {
1018
+ const reader = body.getReader();
1019
+ const decoder = new TextDecoder();
1020
+ let buffer = "";
1021
+ try {
1022
+ for (; ; ) {
1023
+ const { done, value } = await reader.read();
1024
+ if (done) break;
1025
+ buffer += decoder.decode(value, { stream: true });
1026
+ const lines = buffer.split("\n");
1027
+ buffer = lines.pop() ?? "";
1028
+ for (const line of lines) {
1029
+ if (line.startsWith("data:")) {
1030
+ const jsonStr = line.slice(5).trim();
1031
+ if (jsonStr) {
1032
+ this.handleMessage(jsonStr);
1033
+ }
1034
+ }
1035
+ }
1036
+ }
1037
+ buffer += decoder.decode();
1038
+ if (buffer.startsWith("data:")) {
1039
+ const jsonStr = buffer.slice(5).trim();
1040
+ if (jsonStr) {
1041
+ this.handleMessage(jsonStr);
1042
+ }
1043
+ }
1044
+ } catch (err) {
1045
+ if (this.closed) return;
1046
+ const error = err instanceof Error ? err : new Error(String(err));
1047
+ this.emit("error", error);
1048
+ }
1049
+ if (!this.closed) {
1050
+ this.scheduleReconnect();
1051
+ }
1052
+ }
1053
+ handleMessage(jsonStr) {
1054
+ try {
1055
+ const msg = JSON.parse(jsonStr);
1056
+ if (msg.type === "connect" && msg.session_id) {
1057
+ this.sessionId = msg.session_id;
1058
+ this.emit("connect", msg.session_id);
1059
+ if (this.subscribedContextIds.size > 0) {
1060
+ this.sendSubscription("subscribe", [...this.subscribedContextIds]);
1061
+ }
1062
+ return;
1063
+ }
1064
+ if (msg.result && msg.result.contextId) {
1065
+ let eventData = msg.result.data;
1066
+ if (Array.isArray(eventData)) {
1067
+ try {
1068
+ const bytes = new Uint8Array(eventData);
1069
+ const text = new TextDecoder().decode(bytes);
1070
+ eventData = JSON.parse(text);
1071
+ } catch {
1072
+ }
1073
+ }
1074
+ this.emit("event", {
1075
+ contextId: msg.result.contextId,
1076
+ data: eventData
1077
+ });
1078
+ }
1079
+ } catch {
1080
+ }
1081
+ }
1082
+ async subscribe(contextIds) {
1083
+ const newIds = contextIds.filter((id) => !this.subscribedContextIds.has(id));
1084
+ for (const id of contextIds) {
1085
+ this.subscribedContextIds.add(id);
1086
+ }
1087
+ if (newIds.length > 0 && this.sessionId) {
1088
+ await this.sendSubscription("subscribe", newIds);
1089
+ }
1090
+ }
1091
+ async unsubscribe(contextIds) {
1092
+ const hadIds = contextIds.filter((id) => this.subscribedContextIds.has(id));
1093
+ for (const id of contextIds) {
1094
+ this.subscribedContextIds.delete(id);
1095
+ }
1096
+ if (hadIds.length > 0 && this.sessionId) {
1097
+ await this.sendSubscription("unsubscribe", hadIds);
1098
+ }
1099
+ }
1100
+ async sendSubscription(method, contextIds) {
1101
+ try {
1102
+ const token = await this.getAuthToken();
1103
+ const response = await fetch(`${this.baseUrl}/sse/subscription`, {
1104
+ method: "POST",
1105
+ headers: {
1106
+ "Authorization": `Bearer ${token}`,
1107
+ "Content-Type": "application/json"
1108
+ },
1109
+ body: JSON.stringify({
1110
+ id: this.sessionId,
1111
+ method,
1112
+ params: { contextIds }
1113
+ })
1114
+ });
1115
+ if (!response.ok) {
1116
+ this.emit("error", new Error(`SSE ${method} failed: ${response.status}`));
1117
+ }
1118
+ } catch (err) {
1119
+ this.emit("error", err instanceof Error ? err : new Error(`SSE ${method} failed`));
1120
+ }
1121
+ }
1122
+ forceReconnect() {
1123
+ if (this.abortController) {
1124
+ this.abortController.abort();
1125
+ this.abortController = null;
1126
+ }
1127
+ this.sessionId = null;
1128
+ this.connect();
1129
+ }
1130
+ scheduleReconnect() {
1131
+ if (this.closed) return;
1132
+ if (this.reconnectTimer) {
1133
+ clearTimeout(this.reconnectTimer);
1134
+ }
1135
+ this.reconnectTimer = setTimeout(() => {
1136
+ this.reconnectTimer = null;
1137
+ this.forceReconnect();
1138
+ }, this.reconnectDelayMs);
1139
+ }
1140
+ close() {
1141
+ this.closed = true;
1142
+ if (this.abortController) {
1143
+ this.abortController.abort();
1144
+ this.abortController = null;
1145
+ }
1146
+ if (this.reconnectTimer) {
1147
+ clearTimeout(this.reconnectTimer);
1148
+ this.reconnectTimer = null;
1149
+ }
1150
+ this.sessionId = null;
1151
+ this.subscribedContextIds.clear();
1152
+ }
1153
+ };
1154
+
1155
+ // src/events/ws.ts
1156
+ var _WsClient = class _WsClient {
1157
+ constructor(opts) {
1158
+ this.ws = null;
1159
+ this.closed = false;
1160
+ this.reconnectAttempt = 0;
1161
+ this.reconnectTimer = null;
1162
+ this.subscribedContextIds = /* @__PURE__ */ new Set();
1163
+ this.listeners = { connect: [], event: [], error: [] };
1164
+ this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
1165
+ this.getAuthToken = opts.getAuthToken;
1166
+ }
1167
+ on(event, handler) {
1168
+ const key = event;
1169
+ if (key in this.listeners) {
1170
+ const arr = this.listeners[key];
1171
+ if (!arr.includes(handler)) arr.push(handler);
1172
+ }
1173
+ }
1174
+ off(event, handler) {
1175
+ const key = event;
1176
+ if (key in this.listeners) {
1177
+ const arr = this.listeners[key];
1178
+ const idx = arr.indexOf(handler);
1179
+ if (idx !== -1) arr.splice(idx, 1);
1180
+ }
1181
+ }
1182
+ emit(event, arg) {
1183
+ const key = event;
1184
+ if (key in this.listeners) {
1185
+ for (const handler of this.listeners[key]) {
1186
+ try {
1187
+ handler(arg);
1188
+ } catch {
1189
+ }
1190
+ }
1191
+ }
1192
+ }
1193
+ async connect() {
1194
+ if (this.ws && (this.ws.readyState === WebSocket.CONNECTING || this.ws.readyState === WebSocket.OPEN)) {
1195
+ return;
1196
+ }
1197
+ this.closed = false;
1198
+ try {
1199
+ const token = await this.getAuthToken();
1200
+ if (!token) {
1201
+ throw new Error("No authentication token available for WebSocket connection");
1202
+ }
1203
+ const wsUrl = this.baseUrl.replace(/^http/, "ws");
1204
+ this.ws = new WebSocket(`${wsUrl}/ws?token=${encodeURIComponent(token)}`);
1205
+ this.ws.onopen = () => {
1206
+ this.reconnectAttempt = 0;
1207
+ this.emit("connect");
1208
+ if (this.subscribedContextIds.size > 0) {
1209
+ this.sendMessage({
1210
+ id: null,
1211
+ method: "subscribe",
1212
+ params: { contextIds: [...this.subscribedContextIds] }
1213
+ });
1214
+ }
1215
+ };
1216
+ this.ws.onmessage = (event) => {
1217
+ this.handleMessage(event.data);
1218
+ };
1219
+ this.ws.onerror = () => {
1220
+ this.emit("error", new Error("WebSocket error"));
1221
+ };
1222
+ this.ws.onclose = () => {
1223
+ if (!this.closed) {
1224
+ this.scheduleReconnect();
1225
+ }
1226
+ };
1227
+ } catch (err) {
1228
+ if (this.closed) return;
1229
+ const error = err instanceof Error ? err : new Error(String(err));
1230
+ this.emit("error", error);
1231
+ this.scheduleReconnect();
1232
+ }
1233
+ }
1234
+ handleMessage(raw) {
1235
+ if (typeof raw !== "string") return;
1236
+ try {
1237
+ const msg = JSON.parse(raw);
1238
+ if (msg.result && msg.result.contextId) {
1239
+ let eventData = msg.result.data;
1240
+ if (Array.isArray(eventData)) {
1241
+ try {
1242
+ const bytes = new Uint8Array(eventData);
1243
+ const text = new TextDecoder().decode(bytes);
1244
+ eventData = JSON.parse(text);
1245
+ } catch {
1246
+ }
1247
+ }
1248
+ this.emit("event", {
1249
+ contextId: msg.result.contextId,
1250
+ data: eventData
1251
+ });
1252
+ }
1253
+ } catch {
1254
+ }
1255
+ }
1256
+ subscribe(contextIds) {
1257
+ for (const id of contextIds) {
1258
+ this.subscribedContextIds.add(id);
1259
+ }
1260
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
1261
+ this.sendMessage({
1262
+ id: null,
1263
+ method: "subscribe",
1264
+ params: { contextIds }
1265
+ });
1266
+ }
1267
+ }
1268
+ unsubscribe(contextIds) {
1269
+ for (const id of contextIds) {
1270
+ this.subscribedContextIds.delete(id);
1271
+ }
1272
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
1273
+ this.sendMessage({
1274
+ id: null,
1275
+ method: "unsubscribe",
1276
+ params: { contextIds }
1277
+ });
1278
+ }
1279
+ }
1280
+ sendMessage(msg) {
1281
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
1282
+ this.ws.send(JSON.stringify(msg));
1283
+ }
1284
+ }
1285
+ scheduleReconnect() {
1286
+ if (this.closed) return;
1287
+ if (this.reconnectTimer) {
1288
+ clearTimeout(this.reconnectTimer);
1289
+ }
1290
+ const delay = Math.min(
1291
+ 1e3 * Math.pow(2, this.reconnectAttempt),
1292
+ _WsClient.MAX_BACKOFF_MS
1293
+ );
1294
+ this.reconnectAttempt++;
1295
+ this.reconnectTimer = setTimeout(() => {
1296
+ this.reconnectTimer = null;
1297
+ this.connect();
1298
+ }, delay);
1299
+ }
1300
+ close() {
1301
+ this.closed = true;
1302
+ if (this.reconnectTimer) {
1303
+ clearTimeout(this.reconnectTimer);
1304
+ this.reconnectTimer = null;
1305
+ }
1306
+ if (this.ws) {
1307
+ this.ws.onclose = null;
1308
+ this.ws.close();
1309
+ this.ws = null;
1310
+ }
1311
+ this.subscribedContextIds.clear();
1312
+ }
1313
+ };
1314
+ _WsClient.MAX_BACKOFF_MS = 3e4;
1315
+ var WsClient = _WsClient;
1316
+
839
1317
  // src/mero-js.ts
1318
+ function expiresAtFromJwt(token, fallbackMs) {
1319
+ try {
1320
+ const parts = token.split(".");
1321
+ if (parts.length === 3) {
1322
+ let b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
1323
+ while (b64.length % 4) b64 += "=";
1324
+ const payload = JSON.parse(atob(b64));
1325
+ if (typeof payload.exp === "number") {
1326
+ return payload.exp * 1e3;
1327
+ }
1328
+ }
1329
+ } catch {
1330
+ }
1331
+ return fallbackMs;
1332
+ }
840
1333
  var MeroJs = class {
841
1334
  constructor(config) {
842
1335
  this.tokenData = null;
843
1336
  this.refreshPromise = null;
1337
+ this.rpcClient = null;
1338
+ this.sseClient = null;
1339
+ this.wsClient = null;
1340
+ this.wsWarned = false;
844
1341
  this.config = {
845
1342
  timeoutMs: 1e4,
846
1343
  ...config
847
1344
  };
1345
+ this.tokenStore = config.tokenStore ?? null;
1346
+ if (this.tokenStore) {
1347
+ this.tokenData = this.tokenStore.getTokens();
1348
+ }
848
1349
  const isTauri = typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
849
1350
  this.httpClient = createBrowserHttpClient({
850
1351
  baseUrl: this.config.baseUrl,
@@ -852,6 +1353,16 @@ var MeroJs = class {
852
1353
  const token = await this.getValidToken();
853
1354
  return token?.access_token || "";
854
1355
  },
1356
+ refreshToken: async () => {
1357
+ const refreshed = await this.performTokenRefresh();
1358
+ return refreshed.access_token;
1359
+ },
1360
+ onTokenRefresh: async (newToken) => {
1361
+ if (this.tokenData) {
1362
+ this.tokenData.access_token = newToken;
1363
+ this.tokenStore?.setTokens(this.tokenData);
1364
+ }
1365
+ },
855
1366
  timeoutMs: this.config.timeoutMs,
856
1367
  credentials: this.config.requestCredentials ?? (isTauri ? "omit" : void 0)
857
1368
  });
@@ -884,6 +1395,50 @@ var MeroJs = class {
884
1395
  get admin() {
885
1396
  return this.adminClient;
886
1397
  }
1398
+ /**
1399
+ * Get the RPC client (lazy initialized)
1400
+ */
1401
+ get rpc() {
1402
+ if (!this.rpcClient) {
1403
+ this.rpcClient = new RpcClient({ httpClient: this.httpClient });
1404
+ }
1405
+ return this.rpcClient;
1406
+ }
1407
+ /**
1408
+ * Get the SSE event client (lazy initialized)
1409
+ */
1410
+ get events() {
1411
+ if (!this.sseClient) {
1412
+ this.sseClient = new SseClient({
1413
+ baseUrl: this.config.baseUrl,
1414
+ getAuthToken: async () => {
1415
+ const token = await this.getValidToken();
1416
+ return token?.access_token || "";
1417
+ }
1418
+ });
1419
+ }
1420
+ return this.sseClient;
1421
+ }
1422
+ /**
1423
+ * Get the WebSocket event client (lazy initialized).
1424
+ * @experimental Use `events` (SSE) for production. WsClient is experimental.
1425
+ */
1426
+ get ws() {
1427
+ if (!this.wsWarned) {
1428
+ this.wsWarned = true;
1429
+ console.warn("[mero-js] WsClient is experimental. Use mero.events (SSE) for production.");
1430
+ }
1431
+ if (!this.wsClient) {
1432
+ this.wsClient = new WsClient({
1433
+ baseUrl: this.config.baseUrl,
1434
+ getAuthToken: async () => {
1435
+ const token = await this.getValidToken();
1436
+ return token?.access_token || "";
1437
+ }
1438
+ });
1439
+ }
1440
+ return this.wsClient;
1441
+ }
887
1442
  /**
888
1443
  * Authenticate with the provided credentials
889
1444
  * This will create the root key on first use
@@ -906,12 +1461,13 @@ var MeroJs = class {
906
1461
  }
907
1462
  };
908
1463
  const response = await this.authClient.generateTokens(requestBody);
1464
+ const accessToken = response.data.access_token;
909
1465
  this.tokenData = {
910
- access_token: response.data.access_token,
1466
+ access_token: accessToken,
911
1467
  refresh_token: response.data.refresh_token,
912
- expires_at: Date.now() + 24 * 60 * 60 * 1e3
913
- // Default to 24 hours
1468
+ expires_at: expiresAtFromJwt(accessToken, Date.now() + 36e5)
914
1469
  };
1470
+ this.tokenStore?.setTokens(this.tokenData);
915
1471
  return this.tokenData;
916
1472
  } catch (error) {
917
1473
  throw new Error(
@@ -920,16 +1476,12 @@ var MeroJs = class {
920
1476
  }
921
1477
  }
922
1478
  /**
923
- * Get a valid token, refreshing if necessary
1479
+ * Get a valid token. Returns the current token as-is.
1480
+ * The server rejects refresh attempts while the access token is still valid,
1481
+ * so we never proactively refresh. Instead, the WebHttpClient handles 401
1482
+ * responses reactively via the refreshToken transport hook.
924
1483
  */
925
1484
  async getValidToken() {
926
- if (!this.tokenData) {
927
- return null;
928
- }
929
- const bufferTime = 5 * 60 * 1e3;
930
- if (Date.now() >= this.tokenData.expires_at - bufferTime) {
931
- return await this.refreshToken();
932
- }
933
1485
  return this.tokenData;
934
1486
  }
935
1487
  /**
@@ -962,15 +1514,15 @@ var MeroJs = class {
962
1514
  access_token: this.tokenData.access_token,
963
1515
  refresh_token: this.tokenData.refresh_token
964
1516
  });
1517
+ const accessToken = response.data.access_token;
965
1518
  this.tokenData = {
966
- access_token: response.data.access_token,
1519
+ access_token: accessToken,
967
1520
  refresh_token: response.data.refresh_token,
968
- expires_at: Date.now() + 24 * 60 * 60 * 1e3
969
- // Default to 24 hours
1521
+ expires_at: expiresAtFromJwt(accessToken, Date.now() + 36e5)
970
1522
  };
1523
+ this.tokenStore?.setTokens(this.tokenData);
971
1524
  return this.tokenData;
972
1525
  } catch (error) {
973
- this.clearToken();
974
1526
  throw new Error(
975
1527
  `Token refresh failed: ${error instanceof Error ? error.message : "Unknown error"}`
976
1528
  );
@@ -981,6 +1533,7 @@ var MeroJs = class {
981
1533
  */
982
1534
  clearToken() {
983
1535
  this.tokenData = null;
1536
+ this.tokenStore?.clear();
984
1537
  }
985
1538
  /**
986
1539
  * Check if the SDK is authenticated
@@ -988,14 +1541,97 @@ var MeroJs = class {
988
1541
  isAuthenticated() {
989
1542
  return this.tokenData !== null;
990
1543
  }
1544
+ /**
1545
+ * Set token data directly (e.g., from auth callback).
1546
+ * If `expires_at` is missing or 0, attempts to parse the JWT exp claim,
1547
+ * falling back to 1 hour from now.
1548
+ */
1549
+ setTokenData(data) {
1550
+ const expiresAt = data.expires_at || expiresAtFromJwt(data.access_token, Date.now() + 36e5);
1551
+ this.tokenData = { ...data, expires_at: expiresAt };
1552
+ this.tokenStore?.setTokens(this.tokenData);
1553
+ }
991
1554
  /**
992
1555
  * Get the current token data (for debugging)
993
1556
  */
994
1557
  getTokenData() {
995
1558
  return this.tokenData;
996
1559
  }
1560
+ /**
1561
+ * Close all event connections and clean up resources
1562
+ */
1563
+ close() {
1564
+ this.sseClient?.close();
1565
+ this.wsClient?.close();
1566
+ }
1567
+ /**
1568
+ * Parse an auth callback URL hash fragment (static utility)
1569
+ */
1570
+ static parseAuthCallback(url) {
1571
+ return parseAuthCallback(url);
1572
+ }
1573
+ /**
1574
+ * Build an auth login URL (static utility)
1575
+ */
1576
+ static buildAuthLoginUrl(nodeUrl, opts) {
1577
+ return buildAuthLoginUrl(nodeUrl, opts);
1578
+ }
997
1579
  };
998
1580
  function createMeroJs(config) {
999
1581
  return new MeroJs(config);
1000
1582
  }
1583
+
1584
+ // src/token-store/index.ts
1585
+ var MemoryTokenStore = class {
1586
+ constructor() {
1587
+ this.tokens = null;
1588
+ }
1589
+ getTokens() {
1590
+ return this.tokens;
1591
+ }
1592
+ setTokens(data) {
1593
+ this.tokens = data;
1594
+ }
1595
+ clear() {
1596
+ this.tokens = null;
1597
+ }
1598
+ };
1599
+ var STORAGE_KEY = "mero-tokens";
1600
+ var LocalStorageTokenStore = class {
1601
+ constructor(key = STORAGE_KEY) {
1602
+ this.key = key;
1603
+ }
1604
+ getTokens() {
1605
+ try {
1606
+ if (typeof localStorage === "undefined") return null;
1607
+ const raw = localStorage.getItem(this.key);
1608
+ if (!raw) return null;
1609
+ const parsed = JSON.parse(raw);
1610
+ if (parsed && parsed.access_token && parsed.refresh_token) {
1611
+ return {
1612
+ access_token: parsed.access_token,
1613
+ refresh_token: parsed.refresh_token,
1614
+ expires_at: typeof parsed.expires_at === "number" ? parsed.expires_at : Date.now() + 36e5
1615
+ };
1616
+ }
1617
+ return null;
1618
+ } catch {
1619
+ return null;
1620
+ }
1621
+ }
1622
+ setTokens(data) {
1623
+ try {
1624
+ if (typeof localStorage === "undefined") return;
1625
+ localStorage.setItem(this.key, JSON.stringify(data));
1626
+ } catch {
1627
+ }
1628
+ }
1629
+ clear() {
1630
+ try {
1631
+ if (typeof localStorage === "undefined") return;
1632
+ localStorage.removeItem(this.key);
1633
+ } catch {
1634
+ }
1635
+ }
1636
+ };
1001
1637
  //# sourceMappingURL=index.cjs.map