@calimero-network/mero-js 1.1.0 → 1.2.1

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 (50) 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/web-client.d.ts.map +1 -1
  26. package/dist/http-client/web-client.js +2 -4
  27. package/dist/http-client/web-client.js.map +1 -1
  28. package/dist/index.browser.mjs +2 -1
  29. package/dist/index.browser.mjs.map +4 -4
  30. package/dist/index.cjs +705 -88
  31. package/dist/index.cjs.map +3 -3
  32. package/dist/index.d.ts +8 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +8 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/index.mjs +705 -88
  37. package/dist/index.mjs.map +3 -3
  38. package/dist/mero-js.d.ts +47 -1
  39. package/dist/mero-js.d.ts.map +1 -1
  40. package/dist/mero-js.js +132 -16
  41. package/dist/mero-js.js.map +1 -1
  42. package/dist/rpc/index.d.ts +21 -0
  43. package/dist/rpc/index.d.ts.map +1 -0
  44. package/dist/rpc/index.js +39 -0
  45. package/dist/rpc/index.js.map +1 -0
  46. package/dist/token-store/index.d.ts +20 -0
  47. package/dist/token-store/index.d.ts.map +1 -0
  48. package/dist/token-store/index.js +62 -0
  49. package/dist/token-store/index.js.map +1 -0
  50. 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);
@@ -243,8 +251,7 @@ var WebHttpClient = class {
243
251
  bodyText
244
252
  );
245
253
  const userAborted = init?.signal?.aborted === true;
246
- if (response.status === 401 && this.transport.refreshToken && response.headers.get("x-auth-error") === "token_expired" && retryCount < MAX_RETRY_ATTEMPTS && !isStreamBody && // Can't retry with stream bodies
247
- !userAborted) {
254
+ if (response.status === 401 && this.transport.refreshToken && response.headers.get("x-auth-error") === "token_expired" && retryCount < MAX_RETRY_ATTEMPTS && !isStreamBody && !userAborted) {
248
255
  try {
249
256
  let refreshPromise = this.refreshTokenPromise;
250
257
  if (!refreshPromise) {
@@ -682,122 +689,117 @@ function createAuthApiClientFromHttpClient(httpClient, _config) {
682
689
  }
683
690
 
684
691
  // src/admin-api/admin-client.ts
692
+ function unwrap(response) {
693
+ return response.data;
694
+ }
685
695
  var AdminApiClient = class {
686
696
  constructor(httpClient) {
687
697
  this.httpClient = httpClient;
688
698
  }
689
- // Health and Status Endpoints
699
+ // ---- Health and Status (public, no auth) ----
690
700
  async healthCheck() {
691
- const response = await this.httpClient.get("/health");
692
- if (!response.data) {
693
- throw new Error("Health response data is null");
694
- }
695
- return response.data;
701
+ return unwrap(await this.httpClient.get("/admin-api/health"));
696
702
  }
697
703
  async isAuthed() {
698
- return this.httpClient.get("/is-authed");
704
+ return this.httpClient.get("/admin-api/is-authed");
699
705
  }
700
- // Application Management Endpoints
706
+ // ---- Application Management ----
701
707
  async installApplication(request) {
702
- return this.httpClient.post(
703
- "/install-application",
704
- request
705
- );
708
+ return unwrap(await this.httpClient.post("/admin-api/install-application", request));
706
709
  }
707
710
  async installDevApplication(request) {
708
- return this.httpClient.post(
709
- "/install-dev-application",
710
- request
711
- );
711
+ return unwrap(await this.httpClient.post("/admin-api/install-dev-application", request));
712
712
  }
713
713
  async uninstallApplication(appId) {
714
- return this.httpClient.delete(
715
- `/applications/${appId}`
716
- );
714
+ return unwrap(await this.httpClient.delete(`/admin-api/applications/${appId}`));
717
715
  }
718
716
  async listApplications() {
719
- return this.httpClient.get("/applications");
717
+ return unwrap(await this.httpClient.get("/admin-api/applications"));
720
718
  }
721
719
  async getApplication(appId) {
720
+ return unwrap(await this.httpClient.get(`/admin-api/applications/${appId}`));
721
+ }
722
+ // ---- Package Management ----
723
+ async getLatestPackageVersion(packageName) {
722
724
  return this.httpClient.get(
723
- `/applications/${appId}`
725
+ `/admin-api/packages/${encodeURIComponent(packageName)}/latest`
724
726
  );
725
727
  }
726
- // Context Management Endpoints
728
+ // ---- Context Management ----
727
729
  async createContext(request) {
728
- return this.httpClient.post("/contexts", request);
730
+ return unwrap(await this.httpClient.post("/admin-api/contexts", request));
729
731
  }
730
732
  async deleteContext(contextId) {
731
- return this.httpClient.delete(
732
- `/contexts/${contextId}`
733
- );
733
+ return unwrap(await this.httpClient.delete(`/admin-api/contexts/${contextId}`));
734
734
  }
735
735
  async getContexts() {
736
- return this.httpClient.get("/contexts");
736
+ return unwrap(await this.httpClient.get("/admin-api/contexts"));
737
737
  }
738
738
  async getContext(contextId) {
739
- return this.httpClient.get(`/contexts/${contextId}`);
739
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/${contextId}`));
740
740
  }
741
- // Blob Management Endpoints
742
- async uploadBlob(request) {
743
- return this.httpClient.post("/blobs", request);
741
+ async getContextsForApplication(applicationId) {
742
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/for-application/${applicationId}`));
744
743
  }
745
- async deleteBlob(blobId) {
746
- return this.httpClient.delete(`/blobs/${blobId}`);
744
+ // ---- Context Identity ----
745
+ async generateContextIdentity() {
746
+ return unwrap(await this.httpClient.post("/admin-api/identity/context", {}));
747
747
  }
748
- async listBlobs() {
749
- return this.httpClient.get("/blobs");
748
+ async getContextIdentities(contextId) {
749
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/${contextId}/identities`));
750
750
  }
751
- async getBlob(blobId) {
752
- return this.httpClient.get(`/blobs/${blobId}`);
751
+ async getContextIdentitiesOwned(contextId) {
752
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/${contextId}/identities-owned`));
753
753
  }
754
- // Alias Management Endpoints
755
- async createAlias(request) {
756
- 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));
757
757
  }
758
- async deleteAlias(aliasId) {
759
- return this.httpClient.delete(`/alias/${aliasId}`);
758
+ async joinContext(request) {
759
+ return unwrap(await this.httpClient.post("/admin-api/contexts/join", request));
760
760
  }
761
- async listAliases() {
762
- return this.httpClient.get("/alias");
761
+ // ---- Blob Management ----
762
+ async uploadBlob(data) {
763
+ return unwrap(await this.httpClient.put("/admin-api/blobs", data));
763
764
  }
764
- async getAlias(aliasId) {
765
- return this.httpClient.get(`/alias/${aliasId}`);
765
+ async deleteBlob(blobId) {
766
+ return unwrap(await this.httpClient.delete(`/admin-api/blobs/${blobId}`));
766
767
  }
767
- // Network Management Endpoints
768
- async getNetworkPeers() {
769
- return this.httpClient.get("/network/peers");
768
+ async listBlobs() {
769
+ return unwrap(await this.httpClient.get("/admin-api/blobs"));
770
770
  }
771
- async getNetworkStats() {
772
- return this.httpClient.get("/network/stats");
771
+ async getBlob(blobId) {
772
+ return unwrap(await this.httpClient.get(`/admin-api/blobs/${blobId}`));
773
773
  }
774
- async getNetworkConfig() {
775
- 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);
776
778
  }
777
- async updateNetworkConfig(request) {
778
- return this.httpClient.put(
779
- "/network/config",
780
- request
781
- );
779
+ async createApplicationAlias(request) {
780
+ return this.httpClient.post("/admin-api/alias/create/application", request);
782
781
  }
783
- async getPeersCount() {
784
- return this.httpClient.get("/network/peers/count");
782
+ async lookupContextAlias(name) {
783
+ return this.httpClient.post(`/admin-api/alias/lookup/context/${encodeURIComponent(name)}`, {});
784
+ }
785
+ async lookupApplicationAlias(name) {
786
+ return this.httpClient.post(`/admin-api/alias/lookup/application/${encodeURIComponent(name)}`, {});
785
787
  }
786
- // System Management Endpoints
787
- async getSystemInfo() {
788
- return this.httpClient.get("/system/info");
788
+ async deleteContextAlias(name) {
789
+ return this.httpClient.post(`/admin-api/alias/delete/context/${encodeURIComponent(name)}`, {});
789
790
  }
790
- async getSystemLogs() {
791
- return this.httpClient.get("/system/logs");
791
+ async deleteApplicationAlias(name) {
792
+ return this.httpClient.post(`/admin-api/alias/delete/application/${encodeURIComponent(name)}`, {});
792
793
  }
793
- async getSystemMetrics() {
794
- return this.httpClient.get("/system/metrics");
794
+ async listContextAliases() {
795
+ return unwrap(await this.httpClient.get("/admin-api/alias/list/context"));
795
796
  }
796
- async restartSystem() {
797
- return this.httpClient.post("/system/restart");
797
+ async listApplicationAliases() {
798
+ return unwrap(await this.httpClient.get("/admin-api/alias/list/application"));
798
799
  }
799
- async shutdownSystem() {
800
- return this.httpClient.post("/system/shutdown");
800
+ // ---- Network ----
801
+ async getPeersCount() {
802
+ return this.httpClient.get("/admin-api/peers");
801
803
  }
802
804
  };
803
805
 
@@ -855,15 +857,495 @@ function createAdminApiClientFromHttpClient(httpClient, _config) {
855
857
  return new AdminApiClient(httpClient);
856
858
  }
857
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
+
858
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
+ }
859
1333
  var MeroJs = class {
860
1334
  constructor(config) {
861
1335
  this.tokenData = null;
862
1336
  this.refreshPromise = null;
1337
+ this.rpcClient = null;
1338
+ this.sseClient = null;
1339
+ this.wsClient = null;
1340
+ this.wsWarned = false;
863
1341
  this.config = {
864
1342
  timeoutMs: 1e4,
865
1343
  ...config
866
1344
  };
1345
+ this.tokenStore = config.tokenStore ?? null;
1346
+ if (this.tokenStore) {
1347
+ this.tokenData = this.tokenStore.getTokens();
1348
+ }
867
1349
  const isTauri = typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
868
1350
  this.httpClient = createBrowserHttpClient({
869
1351
  baseUrl: this.config.baseUrl,
@@ -871,6 +1353,16 @@ var MeroJs = class {
871
1353
  const token = await this.getValidToken();
872
1354
  return token?.access_token || "";
873
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
+ },
874
1366
  timeoutMs: this.config.timeoutMs,
875
1367
  credentials: this.config.requestCredentials ?? (isTauri ? "omit" : void 0)
876
1368
  });
@@ -903,6 +1395,50 @@ var MeroJs = class {
903
1395
  get admin() {
904
1396
  return this.adminClient;
905
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
+ }
906
1442
  /**
907
1443
  * Authenticate with the provided credentials
908
1444
  * This will create the root key on first use
@@ -925,12 +1461,13 @@ var MeroJs = class {
925
1461
  }
926
1462
  };
927
1463
  const response = await this.authClient.generateTokens(requestBody);
1464
+ const accessToken = response.data.access_token;
928
1465
  this.tokenData = {
929
- access_token: response.data.access_token,
1466
+ access_token: accessToken,
930
1467
  refresh_token: response.data.refresh_token,
931
- expires_at: Date.now() + 24 * 60 * 60 * 1e3
932
- // Default to 24 hours
1468
+ expires_at: expiresAtFromJwt(accessToken, Date.now() + 36e5)
933
1469
  };
1470
+ this.tokenStore?.setTokens(this.tokenData);
934
1471
  return this.tokenData;
935
1472
  } catch (error) {
936
1473
  throw new Error(
@@ -939,16 +1476,12 @@ var MeroJs = class {
939
1476
  }
940
1477
  }
941
1478
  /**
942
- * 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.
943
1483
  */
944
1484
  async getValidToken() {
945
- if (!this.tokenData) {
946
- return null;
947
- }
948
- const bufferTime = 5 * 60 * 1e3;
949
- if (Date.now() >= this.tokenData.expires_at - bufferTime) {
950
- return await this.refreshToken();
951
- }
952
1485
  return this.tokenData;
953
1486
  }
954
1487
  /**
@@ -981,15 +1514,15 @@ var MeroJs = class {
981
1514
  access_token: this.tokenData.access_token,
982
1515
  refresh_token: this.tokenData.refresh_token
983
1516
  });
1517
+ const accessToken = response.data.access_token;
984
1518
  this.tokenData = {
985
- access_token: response.data.access_token,
1519
+ access_token: accessToken,
986
1520
  refresh_token: response.data.refresh_token,
987
- expires_at: Date.now() + 24 * 60 * 60 * 1e3
988
- // Default to 24 hours
1521
+ expires_at: expiresAtFromJwt(accessToken, Date.now() + 36e5)
989
1522
  };
1523
+ this.tokenStore?.setTokens(this.tokenData);
990
1524
  return this.tokenData;
991
1525
  } catch (error) {
992
- this.clearToken();
993
1526
  throw new Error(
994
1527
  `Token refresh failed: ${error instanceof Error ? error.message : "Unknown error"}`
995
1528
  );
@@ -1000,6 +1533,7 @@ var MeroJs = class {
1000
1533
  */
1001
1534
  clearToken() {
1002
1535
  this.tokenData = null;
1536
+ this.tokenStore?.clear();
1003
1537
  }
1004
1538
  /**
1005
1539
  * Check if the SDK is authenticated
@@ -1007,14 +1541,97 @@ var MeroJs = class {
1007
1541
  isAuthenticated() {
1008
1542
  return this.tokenData !== null;
1009
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
+ }
1010
1554
  /**
1011
1555
  * Get the current token data (for debugging)
1012
1556
  */
1013
1557
  getTokenData() {
1014
1558
  return this.tokenData;
1015
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
+ }
1016
1579
  };
1017
1580
  function createMeroJs(config) {
1018
1581
  return new MeroJs(config);
1019
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
+ };
1020
1637
  //# sourceMappingURL=index.cjs.map