@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.mjs CHANGED
@@ -62,6 +62,10 @@ function headersToRecord(headers) {
62
62
  var WebHttpClient = class {
63
63
  constructor(transport) {
64
64
  this.transport = transport;
65
+ // Cache for concurrent refresh token calls to prevent race conditions
66
+ this.refreshTokenPromise = null;
67
+ // Cache for concurrent onTokenRefresh calls to prevent duplicate callbacks
68
+ this.onTokenRefreshPromise = null;
65
69
  }
66
70
  async get(path, init) {
67
71
  return this.request(path, { ...init, method: "GET" });
@@ -116,61 +120,10 @@ var WebHttpClient = class {
116
120
  async request(path, init) {
117
121
  return this.makeRequest(path, init);
118
122
  }
119
- async makeRequest(path, init) {
123
+ async makeRequest(path, init, retryCount = 0, requestStartTime) {
124
+ const MAX_RETRY_ATTEMPTS = 1;
120
125
  const url = this.buildUrl(path);
121
- const hasTauriInternals = typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
122
- const hasTauri = typeof window !== "undefined" && "__TAURI__" in window;
123
- const isTauri = hasTauriInternals || hasTauri || this.transport.credentials === "omit";
124
- if (isTauri) {
125
- const headers2 = await this.buildHeaders(init?.headers);
126
- let headersObj2;
127
- if (headers2 instanceof Headers) {
128
- headersObj2 = {};
129
- headers2.forEach((value, key) => {
130
- headersObj2[key] = value;
131
- });
132
- } else {
133
- headersObj2 = headers2;
134
- }
135
- const requestInit2 = {};
136
- if (init?.method && init.method !== "GET") {
137
- requestInit2.method = init.method;
138
- }
139
- if (headersObj2 && Object.keys(headersObj2).length > 0) {
140
- requestInit2.headers = headersObj2;
141
- }
142
- if (init?.body !== void 0 && init.body !== null) {
143
- requestInit2.body = init.body;
144
- }
145
- if (this.transport.credentials !== void 0) {
146
- requestInit2.credentials = this.transport.credentials;
147
- }
148
- try {
149
- const response = await globalThis.fetch(url, requestInit2);
150
- if (!response.ok) {
151
- const bodyText = await this.getBodyText(response);
152
- throw new HTTPError(
153
- response.status,
154
- response.statusText,
155
- url,
156
- response.headers,
157
- bodyText
158
- );
159
- }
160
- return this.parseResponse(response, init?.parse);
161
- } catch (error) {
162
- if (error instanceof HTTPError) {
163
- throw error;
164
- }
165
- throw new HTTPError(
166
- 0,
167
- "Network Error",
168
- url,
169
- new Headers(),
170
- error instanceof Error ? error.message : "Unknown error"
171
- );
172
- }
173
- }
126
+ const startTime = requestStartTime ?? Date.now();
174
127
  const signal = this.createAbortSignal(init);
175
128
  const headers = await this.buildHeaders(init?.headers);
176
129
  let headersObj;
@@ -186,11 +139,27 @@ var WebHttpClient = class {
186
139
  method: init?.method || "GET",
187
140
  headers: headersObj
188
141
  };
189
- if (init?.body !== void 0) {
142
+ const isStreamBody = init?.body instanceof ReadableStream || typeof init?.body === "object" && init?.body !== null && "getReader" in init.body && !(init.body instanceof Blob);
143
+ if (init?.body !== void 0 && !isStreamBody) {
144
+ requestInit.body = init.body;
145
+ } else if (init?.body !== void 0 && isStreamBody && retryCount === 0) {
190
146
  requestInit.body = init.body;
191
147
  }
192
- if (signal) {
193
- requestInit.signal = signal;
148
+ let retrySignal;
149
+ if (retryCount > 0 && requestStartTime !== void 0) {
150
+ const timeoutMs = init?.timeoutMs || this.transport.timeoutMs;
151
+ if (timeoutMs) {
152
+ const elapsed = Date.now() - startTime;
153
+ const remaining = Math.max(0, timeoutMs - elapsed);
154
+ retrySignal = this.createAbortSignal({ ...init, timeoutMs: remaining });
155
+ } else {
156
+ retrySignal = this.createAbortSignal(init);
157
+ }
158
+ } else {
159
+ retrySignal = signal;
160
+ }
161
+ if (retrySignal) {
162
+ requestInit.signal = retrySignal;
194
163
  }
195
164
  if (this.transport.credentials !== void 0) {
196
165
  requestInit.credentials = this.transport.credentials;
@@ -220,19 +189,70 @@ var WebHttpClient = class {
220
189
  const response = await this.transport.fetch(url, requestInit);
221
190
  if (!response.ok) {
222
191
  const bodyText = await this.getBodyText(response);
223
- throw new HTTPError(
192
+ const httpError = new HTTPError(
224
193
  response.status,
225
194
  response.statusText,
226
195
  url,
227
196
  response.headers,
228
197
  bodyText
229
198
  );
199
+ const userAborted = init?.signal?.aborted === true;
200
+ if (response.status === 401 && this.transport.refreshToken && response.headers.get("x-auth-error") === "token_expired" && retryCount < MAX_RETRY_ATTEMPTS && !isStreamBody && !userAborted) {
201
+ try {
202
+ let refreshPromise = this.refreshTokenPromise;
203
+ if (!refreshPromise) {
204
+ refreshPromise = this.transport.refreshToken();
205
+ this.refreshTokenPromise = refreshPromise;
206
+ }
207
+ const newToken = await refreshPromise;
208
+ if (!newToken || newToken.trim() === "") {
209
+ this.refreshTokenPromise = null;
210
+ this.onTokenRefreshPromise = null;
211
+ throw new Error("Refresh token returned empty token");
212
+ }
213
+ if (!this.transport.onTokenRefresh) {
214
+ this.refreshTokenPromise = null;
215
+ this.onTokenRefreshPromise = null;
216
+ throw new Error(
217
+ "onTokenRefresh callback is required when refreshToken is provided. The callback must update the token storage so getAuthToken() returns the new token."
218
+ );
219
+ }
220
+ let onTokenRefreshPromise = this.onTokenRefreshPromise;
221
+ if (!onTokenRefreshPromise) {
222
+ onTokenRefreshPromise = this.transport.onTokenRefresh(newToken);
223
+ this.onTokenRefreshPromise = onTokenRefreshPromise;
224
+ }
225
+ await onTokenRefreshPromise;
226
+ this.refreshTokenPromise = null;
227
+ this.onTokenRefreshPromise = null;
228
+ const timeoutMs = init?.timeoutMs || this.transport.timeoutMs;
229
+ if (timeoutMs && requestStartTime !== void 0) {
230
+ const elapsed = Date.now() - startTime;
231
+ const remaining = timeoutMs - elapsed;
232
+ if (remaining <= 0) {
233
+ throw httpError;
234
+ }
235
+ }
236
+ return this.makeRequest(path, init, retryCount + 1, startTime);
237
+ } catch (refreshError) {
238
+ this.refreshTokenPromise = null;
239
+ this.onTokenRefreshPromise = null;
240
+ if (refreshError instanceof Error && refreshError.message.includes("onTokenRefresh")) {
241
+ throw refreshError;
242
+ }
243
+ throw httpError;
244
+ }
245
+ }
246
+ throw httpError;
230
247
  }
231
248
  return this.parseResponse(response, init?.parse);
232
249
  } catch (error) {
233
250
  if (error instanceof HTTPError) {
234
251
  throw error;
235
252
  }
253
+ if (error instanceof Error && error.message.includes("onTokenRefresh")) {
254
+ throw error;
255
+ }
236
256
  throw new HTTPError(
237
257
  0,
238
258
  "Network Error",
@@ -256,10 +276,6 @@ var WebHttpClient = class {
256
276
  }
257
277
  }
258
278
  createAbortSignal(init) {
259
- const isTauri = typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
260
- if (isTauri) {
261
- return void 0;
262
- }
263
279
  const signals = [];
264
280
  if (this.transport.defaultAbortSignal) {
265
281
  signals.push(this.transport.defaultAbortSignal);
@@ -338,6 +354,7 @@ function createBrowserHttpClient(options) {
338
354
  baseUrl: options.baseUrl,
339
355
  getAuthToken: options.getAuthToken,
340
356
  onTokenRefresh: options.onTokenRefresh,
357
+ refreshToken: options.refreshToken,
341
358
  defaultHeaders: options.defaultHeaders,
342
359
  timeoutMs: options.timeoutMs,
343
360
  credentials: options.credentials,
@@ -362,6 +379,7 @@ function createNodeHttpClient(options) {
362
379
  baseUrl: options.baseUrl,
363
380
  getAuthToken: options.getAuthToken,
364
381
  onTokenRefresh: options.onTokenRefresh,
382
+ refreshToken: options.refreshToken,
365
383
  defaultHeaders: options.defaultHeaders,
366
384
  timeoutMs: options.timeoutMs,
367
385
  credentials: options.credentials,
@@ -617,122 +635,117 @@ function createAuthApiClientFromHttpClient(httpClient, _config) {
617
635
  }
618
636
 
619
637
  // src/admin-api/admin-client.ts
638
+ function unwrap(response) {
639
+ return response.data;
640
+ }
620
641
  var AdminApiClient = class {
621
642
  constructor(httpClient) {
622
643
  this.httpClient = httpClient;
623
644
  }
624
- // Health and Status Endpoints
645
+ // ---- Health and Status (public, no auth) ----
625
646
  async healthCheck() {
626
- const response = await this.httpClient.get("/health");
627
- if (!response.data) {
628
- throw new Error("Health response data is null");
629
- }
630
- return response.data;
647
+ return unwrap(await this.httpClient.get("/admin-api/health"));
631
648
  }
632
649
  async isAuthed() {
633
- return this.httpClient.get("/is-authed");
650
+ return this.httpClient.get("/admin-api/is-authed");
634
651
  }
635
- // Application Management Endpoints
652
+ // ---- Application Management ----
636
653
  async installApplication(request) {
637
- return this.httpClient.post(
638
- "/install-application",
639
- request
640
- );
654
+ return unwrap(await this.httpClient.post("/admin-api/install-application", request));
641
655
  }
642
656
  async installDevApplication(request) {
643
- return this.httpClient.post(
644
- "/install-dev-application",
645
- request
646
- );
657
+ return unwrap(await this.httpClient.post("/admin-api/install-dev-application", request));
647
658
  }
648
659
  async uninstallApplication(appId) {
649
- return this.httpClient.delete(
650
- `/applications/${appId}`
651
- );
660
+ return unwrap(await this.httpClient.delete(`/admin-api/applications/${appId}`));
652
661
  }
653
662
  async listApplications() {
654
- return this.httpClient.get("/applications");
663
+ return unwrap(await this.httpClient.get("/admin-api/applications"));
655
664
  }
656
665
  async getApplication(appId) {
666
+ return unwrap(await this.httpClient.get(`/admin-api/applications/${appId}`));
667
+ }
668
+ // ---- Package Management ----
669
+ async getLatestPackageVersion(packageName) {
657
670
  return this.httpClient.get(
658
- `/applications/${appId}`
671
+ `/admin-api/packages/${encodeURIComponent(packageName)}/latest`
659
672
  );
660
673
  }
661
- // Context Management Endpoints
674
+ // ---- Context Management ----
662
675
  async createContext(request) {
663
- return this.httpClient.post("/contexts", request);
676
+ return unwrap(await this.httpClient.post("/admin-api/contexts", request));
664
677
  }
665
678
  async deleteContext(contextId) {
666
- return this.httpClient.delete(
667
- `/contexts/${contextId}`
668
- );
679
+ return unwrap(await this.httpClient.delete(`/admin-api/contexts/${contextId}`));
669
680
  }
670
681
  async getContexts() {
671
- return this.httpClient.get("/contexts");
682
+ return unwrap(await this.httpClient.get("/admin-api/contexts"));
672
683
  }
673
684
  async getContext(contextId) {
674
- return this.httpClient.get(`/contexts/${contextId}`);
685
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/${contextId}`));
675
686
  }
676
- // Blob Management Endpoints
677
- async uploadBlob(request) {
678
- return this.httpClient.post("/blobs", request);
687
+ async getContextsForApplication(applicationId) {
688
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/for-application/${applicationId}`));
679
689
  }
680
- async deleteBlob(blobId) {
681
- return this.httpClient.delete(`/blobs/${blobId}`);
690
+ // ---- Context Identity ----
691
+ async generateContextIdentity() {
692
+ return unwrap(await this.httpClient.post("/admin-api/identity/context", {}));
682
693
  }
683
- async listBlobs() {
684
- return this.httpClient.get("/blobs");
694
+ async getContextIdentities(contextId) {
695
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/${contextId}/identities`));
685
696
  }
686
- async getBlob(blobId) {
687
- return this.httpClient.get(`/blobs/${blobId}`);
697
+ async getContextIdentitiesOwned(contextId) {
698
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/${contextId}/identities-owned`));
688
699
  }
689
- // Alias Management Endpoints
690
- async createAlias(request) {
691
- return this.httpClient.post("/alias", request);
700
+ // ---- Context Invite / Join ----
701
+ async inviteToContext(request) {
702
+ return unwrap(await this.httpClient.post("/admin-api/contexts/invite", request));
692
703
  }
693
- async deleteAlias(aliasId) {
694
- return this.httpClient.delete(`/alias/${aliasId}`);
704
+ async joinContext(request) {
705
+ return unwrap(await this.httpClient.post("/admin-api/contexts/join", request));
695
706
  }
696
- async listAliases() {
697
- return this.httpClient.get("/alias");
707
+ // ---- Blob Management ----
708
+ async uploadBlob(data) {
709
+ return unwrap(await this.httpClient.put("/admin-api/blobs", data));
698
710
  }
699
- async getAlias(aliasId) {
700
- return this.httpClient.get(`/alias/${aliasId}`);
711
+ async deleteBlob(blobId) {
712
+ return unwrap(await this.httpClient.delete(`/admin-api/blobs/${blobId}`));
701
713
  }
702
- // Network Management Endpoints
703
- async getNetworkPeers() {
704
- return this.httpClient.get("/network/peers");
714
+ async listBlobs() {
715
+ return unwrap(await this.httpClient.get("/admin-api/blobs"));
705
716
  }
706
- async getNetworkStats() {
707
- return this.httpClient.get("/network/stats");
717
+ async getBlob(blobId) {
718
+ return unwrap(await this.httpClient.get(`/admin-api/blobs/${blobId}`));
708
719
  }
709
- async getNetworkConfig() {
710
- return this.httpClient.get("/network/config");
720
+ // ---- Alias Management ----
721
+ // Server uses type-specific alias routes: /admin-api/alias/{create,lookup,delete,list}/{context,application}
722
+ async createContextAlias(request) {
723
+ return this.httpClient.post("/admin-api/alias/create/context", request);
711
724
  }
712
- async updateNetworkConfig(request) {
713
- return this.httpClient.put(
714
- "/network/config",
715
- request
716
- );
725
+ async createApplicationAlias(request) {
726
+ return this.httpClient.post("/admin-api/alias/create/application", request);
717
727
  }
718
- async getPeersCount() {
719
- return this.httpClient.get("/network/peers/count");
728
+ async lookupContextAlias(name) {
729
+ return this.httpClient.post(`/admin-api/alias/lookup/context/${encodeURIComponent(name)}`, {});
720
730
  }
721
- // System Management Endpoints
722
- async getSystemInfo() {
723
- return this.httpClient.get("/system/info");
731
+ async lookupApplicationAlias(name) {
732
+ return this.httpClient.post(`/admin-api/alias/lookup/application/${encodeURIComponent(name)}`, {});
724
733
  }
725
- async getSystemLogs() {
726
- return this.httpClient.get("/system/logs");
734
+ async deleteContextAlias(name) {
735
+ return this.httpClient.post(`/admin-api/alias/delete/context/${encodeURIComponent(name)}`, {});
727
736
  }
728
- async getSystemMetrics() {
729
- return this.httpClient.get("/system/metrics");
737
+ async deleteApplicationAlias(name) {
738
+ return this.httpClient.post(`/admin-api/alias/delete/application/${encodeURIComponent(name)}`, {});
730
739
  }
731
- async restartSystem() {
732
- return this.httpClient.post("/system/restart");
740
+ async listContextAliases() {
741
+ return unwrap(await this.httpClient.get("/admin-api/alias/list/context"));
733
742
  }
734
- async shutdownSystem() {
735
- return this.httpClient.post("/system/shutdown");
743
+ async listApplicationAliases() {
744
+ return unwrap(await this.httpClient.get("/admin-api/alias/list/application"));
745
+ }
746
+ // ---- Network ----
747
+ async getPeersCount() {
748
+ return this.httpClient.get("/admin-api/peers");
736
749
  }
737
750
  };
738
751
 
@@ -790,15 +803,495 @@ function createAdminApiClientFromHttpClient(httpClient, _config) {
790
803
  return new AdminApiClient(httpClient);
791
804
  }
792
805
 
806
+ // src/auth/index.ts
807
+ function parseAuthCallback(url) {
808
+ try {
809
+ const hashIndex = url.indexOf("#");
810
+ if (hashIndex === -1) return null;
811
+ const hash = url.substring(hashIndex + 1);
812
+ const params = new URLSearchParams(hash);
813
+ const accessToken = params.get("access_token");
814
+ if (!accessToken) return null;
815
+ return {
816
+ accessToken,
817
+ refreshToken: params.get("refresh_token") ?? "",
818
+ applicationId: params.get("application_id") ?? "",
819
+ contextId: params.get("context_id") ?? "",
820
+ contextIdentity: params.get("context_identity") ?? "",
821
+ nodeUrl: params.get("node_url") ?? ""
822
+ };
823
+ } catch {
824
+ return null;
825
+ }
826
+ }
827
+ function buildAuthLoginUrl(nodeUrl, opts) {
828
+ const params = new URLSearchParams();
829
+ params.set("callback-url", opts.callbackUrl);
830
+ if (opts.permissions && opts.permissions.length > 0) {
831
+ params.set("permissions", opts.permissions.join(","));
832
+ }
833
+ params.set("mode", opts.mode);
834
+ if (opts.packageName) {
835
+ params.set("package-name", opts.packageName);
836
+ if (opts.packageVersion) {
837
+ params.set("package-version", opts.packageVersion);
838
+ }
839
+ if (opts.registryUrl) {
840
+ params.set("registry-url", opts.registryUrl);
841
+ }
842
+ }
843
+ const base = nodeUrl.replace(/\/+$/, "");
844
+ return `${base}/auth/login?${params.toString()}`;
845
+ }
846
+
847
+ // src/rpc/index.ts
848
+ var RpcError = class extends Error {
849
+ constructor(code, message, data, type) {
850
+ super(message);
851
+ this.name = "RpcError";
852
+ this.code = code;
853
+ this.data = data;
854
+ this.type = type;
855
+ }
856
+ };
857
+ var RpcClient = class {
858
+ constructor(opts) {
859
+ this.httpClient = opts.httpClient;
860
+ }
861
+ async execute(params) {
862
+ const body = {
863
+ jsonrpc: "2.0",
864
+ id: 1,
865
+ method: "execute",
866
+ params: {
867
+ contextId: params.contextId,
868
+ method: params.method,
869
+ argsJson: params.argsJson ?? {},
870
+ executorPublicKey: params.executorPublicKey
871
+ }
872
+ };
873
+ const response = await this.httpClient.post(
874
+ "/jsonrpc",
875
+ body
876
+ );
877
+ if (response.error) {
878
+ const err = response.error;
879
+ const code = err.code ?? -1;
880
+ const message = err.message ?? err.type ?? "RPC error";
881
+ throw new RpcError(code, message, err.data, err.type);
882
+ }
883
+ if (response.result && "output" in response.result) {
884
+ return response.result.output;
885
+ }
886
+ return response.result;
887
+ }
888
+ };
889
+
890
+ // src/events/sse.ts
891
+ var SseClient = class {
892
+ constructor(opts) {
893
+ this.sessionId = null;
894
+ this.abortController = null;
895
+ this.reconnectTimer = null;
896
+ this.subscribedContextIds = /* @__PURE__ */ new Set();
897
+ this.closed = false;
898
+ this.listeners = { connect: [], event: [], error: [] };
899
+ this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
900
+ this.getAuthToken = opts.getAuthToken;
901
+ this.reconnectDelayMs = opts.reconnectDelayMs ?? 3e3;
902
+ }
903
+ on(event, handler) {
904
+ const key = event;
905
+ if (key in this.listeners) {
906
+ const arr = this.listeners[key];
907
+ if (!arr.includes(handler)) arr.push(handler);
908
+ }
909
+ }
910
+ off(event, handler) {
911
+ const key = event;
912
+ if (key in this.listeners) {
913
+ const arr = this.listeners[key];
914
+ const idx = arr.indexOf(handler);
915
+ if (idx !== -1) arr.splice(idx, 1);
916
+ }
917
+ }
918
+ emit(event, arg) {
919
+ const key = event;
920
+ if (key in this.listeners) {
921
+ for (const handler of this.listeners[key]) {
922
+ try {
923
+ handler(arg);
924
+ } catch {
925
+ }
926
+ }
927
+ }
928
+ }
929
+ async connect() {
930
+ if (this.abortController && !this.closed) {
931
+ return;
932
+ }
933
+ this.closed = false;
934
+ this.abortController = new AbortController();
935
+ try {
936
+ const token = await this.getAuthToken();
937
+ const response = await fetch(`${this.baseUrl}/sse`, {
938
+ headers: {
939
+ "Authorization": `Bearer ${token}`,
940
+ "Accept": "text/event-stream"
941
+ },
942
+ signal: this.abortController.signal
943
+ });
944
+ if (!response.ok) {
945
+ throw new Error(`SSE connection failed: ${response.status}`);
946
+ }
947
+ if (!response.body) {
948
+ throw new Error("SSE response has no body");
949
+ }
950
+ this.readStream(response.body).catch((err) => {
951
+ if (this.closed) return;
952
+ const error = err instanceof Error ? err : new Error(String(err));
953
+ this.emit("error", error);
954
+ this.scheduleReconnect();
955
+ });
956
+ } catch (err) {
957
+ if (this.closed) return;
958
+ const error = err instanceof Error ? err : new Error(String(err));
959
+ this.emit("error", error);
960
+ this.scheduleReconnect();
961
+ }
962
+ }
963
+ async readStream(body) {
964
+ const reader = body.getReader();
965
+ const decoder = new TextDecoder();
966
+ let buffer = "";
967
+ try {
968
+ for (; ; ) {
969
+ const { done, value } = await reader.read();
970
+ if (done) break;
971
+ buffer += decoder.decode(value, { stream: true });
972
+ const lines = buffer.split("\n");
973
+ buffer = lines.pop() ?? "";
974
+ for (const line of lines) {
975
+ if (line.startsWith("data:")) {
976
+ const jsonStr = line.slice(5).trim();
977
+ if (jsonStr) {
978
+ this.handleMessage(jsonStr);
979
+ }
980
+ }
981
+ }
982
+ }
983
+ buffer += decoder.decode();
984
+ if (buffer.startsWith("data:")) {
985
+ const jsonStr = buffer.slice(5).trim();
986
+ if (jsonStr) {
987
+ this.handleMessage(jsonStr);
988
+ }
989
+ }
990
+ } catch (err) {
991
+ if (this.closed) return;
992
+ const error = err instanceof Error ? err : new Error(String(err));
993
+ this.emit("error", error);
994
+ }
995
+ if (!this.closed) {
996
+ this.scheduleReconnect();
997
+ }
998
+ }
999
+ handleMessage(jsonStr) {
1000
+ try {
1001
+ const msg = JSON.parse(jsonStr);
1002
+ if (msg.type === "connect" && msg.session_id) {
1003
+ this.sessionId = msg.session_id;
1004
+ this.emit("connect", msg.session_id);
1005
+ if (this.subscribedContextIds.size > 0) {
1006
+ this.sendSubscription("subscribe", [...this.subscribedContextIds]);
1007
+ }
1008
+ return;
1009
+ }
1010
+ if (msg.result && msg.result.contextId) {
1011
+ let eventData = msg.result.data;
1012
+ if (Array.isArray(eventData)) {
1013
+ try {
1014
+ const bytes = new Uint8Array(eventData);
1015
+ const text = new TextDecoder().decode(bytes);
1016
+ eventData = JSON.parse(text);
1017
+ } catch {
1018
+ }
1019
+ }
1020
+ this.emit("event", {
1021
+ contextId: msg.result.contextId,
1022
+ data: eventData
1023
+ });
1024
+ }
1025
+ } catch {
1026
+ }
1027
+ }
1028
+ async subscribe(contextIds) {
1029
+ const newIds = contextIds.filter((id) => !this.subscribedContextIds.has(id));
1030
+ for (const id of contextIds) {
1031
+ this.subscribedContextIds.add(id);
1032
+ }
1033
+ if (newIds.length > 0 && this.sessionId) {
1034
+ await this.sendSubscription("subscribe", newIds);
1035
+ }
1036
+ }
1037
+ async unsubscribe(contextIds) {
1038
+ const hadIds = contextIds.filter((id) => this.subscribedContextIds.has(id));
1039
+ for (const id of contextIds) {
1040
+ this.subscribedContextIds.delete(id);
1041
+ }
1042
+ if (hadIds.length > 0 && this.sessionId) {
1043
+ await this.sendSubscription("unsubscribe", hadIds);
1044
+ }
1045
+ }
1046
+ async sendSubscription(method, contextIds) {
1047
+ try {
1048
+ const token = await this.getAuthToken();
1049
+ const response = await fetch(`${this.baseUrl}/sse/subscription`, {
1050
+ method: "POST",
1051
+ headers: {
1052
+ "Authorization": `Bearer ${token}`,
1053
+ "Content-Type": "application/json"
1054
+ },
1055
+ body: JSON.stringify({
1056
+ id: this.sessionId,
1057
+ method,
1058
+ params: { contextIds }
1059
+ })
1060
+ });
1061
+ if (!response.ok) {
1062
+ this.emit("error", new Error(`SSE ${method} failed: ${response.status}`));
1063
+ }
1064
+ } catch (err) {
1065
+ this.emit("error", err instanceof Error ? err : new Error(`SSE ${method} failed`));
1066
+ }
1067
+ }
1068
+ forceReconnect() {
1069
+ if (this.abortController) {
1070
+ this.abortController.abort();
1071
+ this.abortController = null;
1072
+ }
1073
+ this.sessionId = null;
1074
+ this.connect();
1075
+ }
1076
+ scheduleReconnect() {
1077
+ if (this.closed) return;
1078
+ if (this.reconnectTimer) {
1079
+ clearTimeout(this.reconnectTimer);
1080
+ }
1081
+ this.reconnectTimer = setTimeout(() => {
1082
+ this.reconnectTimer = null;
1083
+ this.forceReconnect();
1084
+ }, this.reconnectDelayMs);
1085
+ }
1086
+ close() {
1087
+ this.closed = true;
1088
+ if (this.abortController) {
1089
+ this.abortController.abort();
1090
+ this.abortController = null;
1091
+ }
1092
+ if (this.reconnectTimer) {
1093
+ clearTimeout(this.reconnectTimer);
1094
+ this.reconnectTimer = null;
1095
+ }
1096
+ this.sessionId = null;
1097
+ this.subscribedContextIds.clear();
1098
+ }
1099
+ };
1100
+
1101
+ // src/events/ws.ts
1102
+ var _WsClient = class _WsClient {
1103
+ constructor(opts) {
1104
+ this.ws = null;
1105
+ this.closed = false;
1106
+ this.reconnectAttempt = 0;
1107
+ this.reconnectTimer = null;
1108
+ this.subscribedContextIds = /* @__PURE__ */ new Set();
1109
+ this.listeners = { connect: [], event: [], error: [] };
1110
+ this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
1111
+ this.getAuthToken = opts.getAuthToken;
1112
+ }
1113
+ on(event, handler) {
1114
+ const key = event;
1115
+ if (key in this.listeners) {
1116
+ const arr = this.listeners[key];
1117
+ if (!arr.includes(handler)) arr.push(handler);
1118
+ }
1119
+ }
1120
+ off(event, handler) {
1121
+ const key = event;
1122
+ if (key in this.listeners) {
1123
+ const arr = this.listeners[key];
1124
+ const idx = arr.indexOf(handler);
1125
+ if (idx !== -1) arr.splice(idx, 1);
1126
+ }
1127
+ }
1128
+ emit(event, arg) {
1129
+ const key = event;
1130
+ if (key in this.listeners) {
1131
+ for (const handler of this.listeners[key]) {
1132
+ try {
1133
+ handler(arg);
1134
+ } catch {
1135
+ }
1136
+ }
1137
+ }
1138
+ }
1139
+ async connect() {
1140
+ if (this.ws && (this.ws.readyState === WebSocket.CONNECTING || this.ws.readyState === WebSocket.OPEN)) {
1141
+ return;
1142
+ }
1143
+ this.closed = false;
1144
+ try {
1145
+ const token = await this.getAuthToken();
1146
+ if (!token) {
1147
+ throw new Error("No authentication token available for WebSocket connection");
1148
+ }
1149
+ const wsUrl = this.baseUrl.replace(/^http/, "ws");
1150
+ this.ws = new WebSocket(`${wsUrl}/ws?token=${encodeURIComponent(token)}`);
1151
+ this.ws.onopen = () => {
1152
+ this.reconnectAttempt = 0;
1153
+ this.emit("connect");
1154
+ if (this.subscribedContextIds.size > 0) {
1155
+ this.sendMessage({
1156
+ id: null,
1157
+ method: "subscribe",
1158
+ params: { contextIds: [...this.subscribedContextIds] }
1159
+ });
1160
+ }
1161
+ };
1162
+ this.ws.onmessage = (event) => {
1163
+ this.handleMessage(event.data);
1164
+ };
1165
+ this.ws.onerror = () => {
1166
+ this.emit("error", new Error("WebSocket error"));
1167
+ };
1168
+ this.ws.onclose = () => {
1169
+ if (!this.closed) {
1170
+ this.scheduleReconnect();
1171
+ }
1172
+ };
1173
+ } catch (err) {
1174
+ if (this.closed) return;
1175
+ const error = err instanceof Error ? err : new Error(String(err));
1176
+ this.emit("error", error);
1177
+ this.scheduleReconnect();
1178
+ }
1179
+ }
1180
+ handleMessage(raw) {
1181
+ if (typeof raw !== "string") return;
1182
+ try {
1183
+ const msg = JSON.parse(raw);
1184
+ if (msg.result && msg.result.contextId) {
1185
+ let eventData = msg.result.data;
1186
+ if (Array.isArray(eventData)) {
1187
+ try {
1188
+ const bytes = new Uint8Array(eventData);
1189
+ const text = new TextDecoder().decode(bytes);
1190
+ eventData = JSON.parse(text);
1191
+ } catch {
1192
+ }
1193
+ }
1194
+ this.emit("event", {
1195
+ contextId: msg.result.contextId,
1196
+ data: eventData
1197
+ });
1198
+ }
1199
+ } catch {
1200
+ }
1201
+ }
1202
+ subscribe(contextIds) {
1203
+ for (const id of contextIds) {
1204
+ this.subscribedContextIds.add(id);
1205
+ }
1206
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
1207
+ this.sendMessage({
1208
+ id: null,
1209
+ method: "subscribe",
1210
+ params: { contextIds }
1211
+ });
1212
+ }
1213
+ }
1214
+ unsubscribe(contextIds) {
1215
+ for (const id of contextIds) {
1216
+ this.subscribedContextIds.delete(id);
1217
+ }
1218
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
1219
+ this.sendMessage({
1220
+ id: null,
1221
+ method: "unsubscribe",
1222
+ params: { contextIds }
1223
+ });
1224
+ }
1225
+ }
1226
+ sendMessage(msg) {
1227
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
1228
+ this.ws.send(JSON.stringify(msg));
1229
+ }
1230
+ }
1231
+ scheduleReconnect() {
1232
+ if (this.closed) return;
1233
+ if (this.reconnectTimer) {
1234
+ clearTimeout(this.reconnectTimer);
1235
+ }
1236
+ const delay = Math.min(
1237
+ 1e3 * Math.pow(2, this.reconnectAttempt),
1238
+ _WsClient.MAX_BACKOFF_MS
1239
+ );
1240
+ this.reconnectAttempt++;
1241
+ this.reconnectTimer = setTimeout(() => {
1242
+ this.reconnectTimer = null;
1243
+ this.connect();
1244
+ }, delay);
1245
+ }
1246
+ close() {
1247
+ this.closed = true;
1248
+ if (this.reconnectTimer) {
1249
+ clearTimeout(this.reconnectTimer);
1250
+ this.reconnectTimer = null;
1251
+ }
1252
+ if (this.ws) {
1253
+ this.ws.onclose = null;
1254
+ this.ws.close();
1255
+ this.ws = null;
1256
+ }
1257
+ this.subscribedContextIds.clear();
1258
+ }
1259
+ };
1260
+ _WsClient.MAX_BACKOFF_MS = 3e4;
1261
+ var WsClient = _WsClient;
1262
+
793
1263
  // src/mero-js.ts
1264
+ function expiresAtFromJwt(token, fallbackMs) {
1265
+ try {
1266
+ const parts = token.split(".");
1267
+ if (parts.length === 3) {
1268
+ let b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
1269
+ while (b64.length % 4) b64 += "=";
1270
+ const payload = JSON.parse(atob(b64));
1271
+ if (typeof payload.exp === "number") {
1272
+ return payload.exp * 1e3;
1273
+ }
1274
+ }
1275
+ } catch {
1276
+ }
1277
+ return fallbackMs;
1278
+ }
794
1279
  var MeroJs = class {
795
1280
  constructor(config) {
796
1281
  this.tokenData = null;
797
1282
  this.refreshPromise = null;
1283
+ this.rpcClient = null;
1284
+ this.sseClient = null;
1285
+ this.wsClient = null;
1286
+ this.wsWarned = false;
798
1287
  this.config = {
799
1288
  timeoutMs: 1e4,
800
1289
  ...config
801
1290
  };
1291
+ this.tokenStore = config.tokenStore ?? null;
1292
+ if (this.tokenStore) {
1293
+ this.tokenData = this.tokenStore.getTokens();
1294
+ }
802
1295
  const isTauri = typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
803
1296
  this.httpClient = createBrowserHttpClient({
804
1297
  baseUrl: this.config.baseUrl,
@@ -806,6 +1299,16 @@ var MeroJs = class {
806
1299
  const token = await this.getValidToken();
807
1300
  return token?.access_token || "";
808
1301
  },
1302
+ refreshToken: async () => {
1303
+ const refreshed = await this.performTokenRefresh();
1304
+ return refreshed.access_token;
1305
+ },
1306
+ onTokenRefresh: async (newToken) => {
1307
+ if (this.tokenData) {
1308
+ this.tokenData.access_token = newToken;
1309
+ this.tokenStore?.setTokens(this.tokenData);
1310
+ }
1311
+ },
809
1312
  timeoutMs: this.config.timeoutMs,
810
1313
  credentials: this.config.requestCredentials ?? (isTauri ? "omit" : void 0)
811
1314
  });
@@ -838,6 +1341,50 @@ var MeroJs = class {
838
1341
  get admin() {
839
1342
  return this.adminClient;
840
1343
  }
1344
+ /**
1345
+ * Get the RPC client (lazy initialized)
1346
+ */
1347
+ get rpc() {
1348
+ if (!this.rpcClient) {
1349
+ this.rpcClient = new RpcClient({ httpClient: this.httpClient });
1350
+ }
1351
+ return this.rpcClient;
1352
+ }
1353
+ /**
1354
+ * Get the SSE event client (lazy initialized)
1355
+ */
1356
+ get events() {
1357
+ if (!this.sseClient) {
1358
+ this.sseClient = new SseClient({
1359
+ baseUrl: this.config.baseUrl,
1360
+ getAuthToken: async () => {
1361
+ const token = await this.getValidToken();
1362
+ return token?.access_token || "";
1363
+ }
1364
+ });
1365
+ }
1366
+ return this.sseClient;
1367
+ }
1368
+ /**
1369
+ * Get the WebSocket event client (lazy initialized).
1370
+ * @experimental Use `events` (SSE) for production. WsClient is experimental.
1371
+ */
1372
+ get ws() {
1373
+ if (!this.wsWarned) {
1374
+ this.wsWarned = true;
1375
+ console.warn("[mero-js] WsClient is experimental. Use mero.events (SSE) for production.");
1376
+ }
1377
+ if (!this.wsClient) {
1378
+ this.wsClient = new WsClient({
1379
+ baseUrl: this.config.baseUrl,
1380
+ getAuthToken: async () => {
1381
+ const token = await this.getValidToken();
1382
+ return token?.access_token || "";
1383
+ }
1384
+ });
1385
+ }
1386
+ return this.wsClient;
1387
+ }
841
1388
  /**
842
1389
  * Authenticate with the provided credentials
843
1390
  * This will create the root key on first use
@@ -860,12 +1407,13 @@ var MeroJs = class {
860
1407
  }
861
1408
  };
862
1409
  const response = await this.authClient.generateTokens(requestBody);
1410
+ const accessToken = response.data.access_token;
863
1411
  this.tokenData = {
864
- access_token: response.data.access_token,
1412
+ access_token: accessToken,
865
1413
  refresh_token: response.data.refresh_token,
866
- expires_at: Date.now() + 24 * 60 * 60 * 1e3
867
- // Default to 24 hours
1414
+ expires_at: expiresAtFromJwt(accessToken, Date.now() + 36e5)
868
1415
  };
1416
+ this.tokenStore?.setTokens(this.tokenData);
869
1417
  return this.tokenData;
870
1418
  } catch (error) {
871
1419
  throw new Error(
@@ -874,16 +1422,12 @@ var MeroJs = class {
874
1422
  }
875
1423
  }
876
1424
  /**
877
- * Get a valid token, refreshing if necessary
1425
+ * Get a valid token. Returns the current token as-is.
1426
+ * The server rejects refresh attempts while the access token is still valid,
1427
+ * so we never proactively refresh. Instead, the WebHttpClient handles 401
1428
+ * responses reactively via the refreshToken transport hook.
878
1429
  */
879
1430
  async getValidToken() {
880
- if (!this.tokenData) {
881
- return null;
882
- }
883
- const bufferTime = 5 * 60 * 1e3;
884
- if (Date.now() >= this.tokenData.expires_at - bufferTime) {
885
- return await this.refreshToken();
886
- }
887
1431
  return this.tokenData;
888
1432
  }
889
1433
  /**
@@ -916,15 +1460,15 @@ var MeroJs = class {
916
1460
  access_token: this.tokenData.access_token,
917
1461
  refresh_token: this.tokenData.refresh_token
918
1462
  });
1463
+ const accessToken = response.data.access_token;
919
1464
  this.tokenData = {
920
- access_token: response.data.access_token,
1465
+ access_token: accessToken,
921
1466
  refresh_token: response.data.refresh_token,
922
- expires_at: Date.now() + 24 * 60 * 60 * 1e3
923
- // Default to 24 hours
1467
+ expires_at: expiresAtFromJwt(accessToken, Date.now() + 36e5)
924
1468
  };
1469
+ this.tokenStore?.setTokens(this.tokenData);
925
1470
  return this.tokenData;
926
1471
  } catch (error) {
927
- this.clearToken();
928
1472
  throw new Error(
929
1473
  `Token refresh failed: ${error instanceof Error ? error.message : "Unknown error"}`
930
1474
  );
@@ -935,6 +1479,7 @@ var MeroJs = class {
935
1479
  */
936
1480
  clearToken() {
937
1481
  this.tokenData = null;
1482
+ this.tokenStore?.clear();
938
1483
  }
939
1484
  /**
940
1485
  * Check if the SDK is authenticated
@@ -942,22 +1487,112 @@ var MeroJs = class {
942
1487
  isAuthenticated() {
943
1488
  return this.tokenData !== null;
944
1489
  }
1490
+ /**
1491
+ * Set token data directly (e.g., from auth callback).
1492
+ * If `expires_at` is missing or 0, attempts to parse the JWT exp claim,
1493
+ * falling back to 1 hour from now.
1494
+ */
1495
+ setTokenData(data) {
1496
+ const expiresAt = data.expires_at || expiresAtFromJwt(data.access_token, Date.now() + 36e5);
1497
+ this.tokenData = { ...data, expires_at: expiresAt };
1498
+ this.tokenStore?.setTokens(this.tokenData);
1499
+ }
945
1500
  /**
946
1501
  * Get the current token data (for debugging)
947
1502
  */
948
1503
  getTokenData() {
949
1504
  return this.tokenData;
950
1505
  }
1506
+ /**
1507
+ * Close all event connections and clean up resources
1508
+ */
1509
+ close() {
1510
+ this.sseClient?.close();
1511
+ this.wsClient?.close();
1512
+ }
1513
+ /**
1514
+ * Parse an auth callback URL hash fragment (static utility)
1515
+ */
1516
+ static parseAuthCallback(url) {
1517
+ return parseAuthCallback(url);
1518
+ }
1519
+ /**
1520
+ * Build an auth login URL (static utility)
1521
+ */
1522
+ static buildAuthLoginUrl(nodeUrl, opts) {
1523
+ return buildAuthLoginUrl(nodeUrl, opts);
1524
+ }
951
1525
  };
952
1526
  function createMeroJs(config) {
953
1527
  return new MeroJs(config);
954
1528
  }
1529
+
1530
+ // src/token-store/index.ts
1531
+ var MemoryTokenStore = class {
1532
+ constructor() {
1533
+ this.tokens = null;
1534
+ }
1535
+ getTokens() {
1536
+ return this.tokens;
1537
+ }
1538
+ setTokens(data) {
1539
+ this.tokens = data;
1540
+ }
1541
+ clear() {
1542
+ this.tokens = null;
1543
+ }
1544
+ };
1545
+ var STORAGE_KEY = "mero-tokens";
1546
+ var LocalStorageTokenStore = class {
1547
+ constructor(key = STORAGE_KEY) {
1548
+ this.key = key;
1549
+ }
1550
+ getTokens() {
1551
+ try {
1552
+ if (typeof localStorage === "undefined") return null;
1553
+ const raw = localStorage.getItem(this.key);
1554
+ if (!raw) return null;
1555
+ const parsed = JSON.parse(raw);
1556
+ if (parsed && parsed.access_token && parsed.refresh_token) {
1557
+ return {
1558
+ access_token: parsed.access_token,
1559
+ refresh_token: parsed.refresh_token,
1560
+ expires_at: typeof parsed.expires_at === "number" ? parsed.expires_at : Date.now() + 36e5
1561
+ };
1562
+ }
1563
+ return null;
1564
+ } catch {
1565
+ return null;
1566
+ }
1567
+ }
1568
+ setTokens(data) {
1569
+ try {
1570
+ if (typeof localStorage === "undefined") return;
1571
+ localStorage.setItem(this.key, JSON.stringify(data));
1572
+ } catch {
1573
+ }
1574
+ }
1575
+ clear() {
1576
+ try {
1577
+ if (typeof localStorage === "undefined") return;
1578
+ localStorage.removeItem(this.key);
1579
+ } catch {
1580
+ }
1581
+ }
1582
+ };
955
1583
  export {
956
1584
  AdminApiClient,
957
1585
  AuthApiClient,
958
1586
  HTTPError,
1587
+ LocalStorageTokenStore,
1588
+ MemoryTokenStore,
959
1589
  MeroJs,
1590
+ RpcClient,
1591
+ RpcError,
1592
+ SseClient,
960
1593
  WebHttpClient,
1594
+ WsClient,
1595
+ buildAuthLoginUrl,
961
1596
  combineSignals,
962
1597
  createAdminApiClient,
963
1598
  createAdminApiClientFromHttpClient,
@@ -974,6 +1609,7 @@ export {
974
1609
  createRetryableMethod,
975
1610
  createTimeoutSignal,
976
1611
  createUniversalHttpClient,
1612
+ parseAuthCallback,
977
1613
  withRetry
978
1614
  };
979
1615
  //# sourceMappingURL=index.mjs.map