@calimero-network/mero-js 1.1.0 → 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 (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.mjs CHANGED
@@ -197,8 +197,7 @@ var WebHttpClient = class {
197
197
  bodyText
198
198
  );
199
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 && // Can't retry with stream bodies
201
- !userAborted) {
200
+ if (response.status === 401 && this.transport.refreshToken && response.headers.get("x-auth-error") === "token_expired" && retryCount < MAX_RETRY_ATTEMPTS && !isStreamBody && !userAborted) {
202
201
  try {
203
202
  let refreshPromise = this.refreshTokenPromise;
204
203
  if (!refreshPromise) {
@@ -636,122 +635,117 @@ function createAuthApiClientFromHttpClient(httpClient, _config) {
636
635
  }
637
636
 
638
637
  // src/admin-api/admin-client.ts
638
+ function unwrap(response) {
639
+ return response.data;
640
+ }
639
641
  var AdminApiClient = class {
640
642
  constructor(httpClient) {
641
643
  this.httpClient = httpClient;
642
644
  }
643
- // Health and Status Endpoints
645
+ // ---- Health and Status (public, no auth) ----
644
646
  async healthCheck() {
645
- const response = await this.httpClient.get("/health");
646
- if (!response.data) {
647
- throw new Error("Health response data is null");
648
- }
649
- return response.data;
647
+ return unwrap(await this.httpClient.get("/admin-api/health"));
650
648
  }
651
649
  async isAuthed() {
652
- return this.httpClient.get("/is-authed");
650
+ return this.httpClient.get("/admin-api/is-authed");
653
651
  }
654
- // Application Management Endpoints
652
+ // ---- Application Management ----
655
653
  async installApplication(request) {
656
- return this.httpClient.post(
657
- "/install-application",
658
- request
659
- );
654
+ return unwrap(await this.httpClient.post("/admin-api/install-application", request));
660
655
  }
661
656
  async installDevApplication(request) {
662
- return this.httpClient.post(
663
- "/install-dev-application",
664
- request
665
- );
657
+ return unwrap(await this.httpClient.post("/admin-api/install-dev-application", request));
666
658
  }
667
659
  async uninstallApplication(appId) {
668
- return this.httpClient.delete(
669
- `/applications/${appId}`
670
- );
660
+ return unwrap(await this.httpClient.delete(`/admin-api/applications/${appId}`));
671
661
  }
672
662
  async listApplications() {
673
- return this.httpClient.get("/applications");
663
+ return unwrap(await this.httpClient.get("/admin-api/applications"));
674
664
  }
675
665
  async getApplication(appId) {
666
+ return unwrap(await this.httpClient.get(`/admin-api/applications/${appId}`));
667
+ }
668
+ // ---- Package Management ----
669
+ async getLatestPackageVersion(packageName) {
676
670
  return this.httpClient.get(
677
- `/applications/${appId}`
671
+ `/admin-api/packages/${encodeURIComponent(packageName)}/latest`
678
672
  );
679
673
  }
680
- // Context Management Endpoints
674
+ // ---- Context Management ----
681
675
  async createContext(request) {
682
- return this.httpClient.post("/contexts", request);
676
+ return unwrap(await this.httpClient.post("/admin-api/contexts", request));
683
677
  }
684
678
  async deleteContext(contextId) {
685
- return this.httpClient.delete(
686
- `/contexts/${contextId}`
687
- );
679
+ return unwrap(await this.httpClient.delete(`/admin-api/contexts/${contextId}`));
688
680
  }
689
681
  async getContexts() {
690
- return this.httpClient.get("/contexts");
682
+ return unwrap(await this.httpClient.get("/admin-api/contexts"));
691
683
  }
692
684
  async getContext(contextId) {
693
- return this.httpClient.get(`/contexts/${contextId}`);
685
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/${contextId}`));
694
686
  }
695
- // Blob Management Endpoints
696
- async uploadBlob(request) {
697
- return this.httpClient.post("/blobs", request);
687
+ async getContextsForApplication(applicationId) {
688
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/for-application/${applicationId}`));
698
689
  }
699
- async deleteBlob(blobId) {
700
- return this.httpClient.delete(`/blobs/${blobId}`);
690
+ // ---- Context Identity ----
691
+ async generateContextIdentity() {
692
+ return unwrap(await this.httpClient.post("/admin-api/identity/context", {}));
701
693
  }
702
- async listBlobs() {
703
- return this.httpClient.get("/blobs");
694
+ async getContextIdentities(contextId) {
695
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/${contextId}/identities`));
704
696
  }
705
- async getBlob(blobId) {
706
- return this.httpClient.get(`/blobs/${blobId}`);
697
+ async getContextIdentitiesOwned(contextId) {
698
+ return unwrap(await this.httpClient.get(`/admin-api/contexts/${contextId}/identities-owned`));
707
699
  }
708
- // Alias Management Endpoints
709
- async createAlias(request) {
710
- 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));
711
703
  }
712
- async deleteAlias(aliasId) {
713
- return this.httpClient.delete(`/alias/${aliasId}`);
704
+ async joinContext(request) {
705
+ return unwrap(await this.httpClient.post("/admin-api/contexts/join", request));
714
706
  }
715
- async listAliases() {
716
- return this.httpClient.get("/alias");
707
+ // ---- Blob Management ----
708
+ async uploadBlob(data) {
709
+ return unwrap(await this.httpClient.put("/admin-api/blobs", data));
717
710
  }
718
- async getAlias(aliasId) {
719
- return this.httpClient.get(`/alias/${aliasId}`);
711
+ async deleteBlob(blobId) {
712
+ return unwrap(await this.httpClient.delete(`/admin-api/blobs/${blobId}`));
720
713
  }
721
- // Network Management Endpoints
722
- async getNetworkPeers() {
723
- return this.httpClient.get("/network/peers");
714
+ async listBlobs() {
715
+ return unwrap(await this.httpClient.get("/admin-api/blobs"));
724
716
  }
725
- async getNetworkStats() {
726
- return this.httpClient.get("/network/stats");
717
+ async getBlob(blobId) {
718
+ return unwrap(await this.httpClient.get(`/admin-api/blobs/${blobId}`));
727
719
  }
728
- async getNetworkConfig() {
729
- 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);
730
724
  }
731
- async updateNetworkConfig(request) {
732
- return this.httpClient.put(
733
- "/network/config",
734
- request
735
- );
725
+ async createApplicationAlias(request) {
726
+ return this.httpClient.post("/admin-api/alias/create/application", request);
736
727
  }
737
- async getPeersCount() {
738
- return this.httpClient.get("/network/peers/count");
728
+ async lookupContextAlias(name) {
729
+ return this.httpClient.post(`/admin-api/alias/lookup/context/${encodeURIComponent(name)}`, {});
730
+ }
731
+ async lookupApplicationAlias(name) {
732
+ return this.httpClient.post(`/admin-api/alias/lookup/application/${encodeURIComponent(name)}`, {});
739
733
  }
740
- // System Management Endpoints
741
- async getSystemInfo() {
742
- return this.httpClient.get("/system/info");
734
+ async deleteContextAlias(name) {
735
+ return this.httpClient.post(`/admin-api/alias/delete/context/${encodeURIComponent(name)}`, {});
743
736
  }
744
- async getSystemLogs() {
745
- return this.httpClient.get("/system/logs");
737
+ async deleteApplicationAlias(name) {
738
+ return this.httpClient.post(`/admin-api/alias/delete/application/${encodeURIComponent(name)}`, {});
746
739
  }
747
- async getSystemMetrics() {
748
- return this.httpClient.get("/system/metrics");
740
+ async listContextAliases() {
741
+ return unwrap(await this.httpClient.get("/admin-api/alias/list/context"));
749
742
  }
750
- async restartSystem() {
751
- return this.httpClient.post("/system/restart");
743
+ async listApplicationAliases() {
744
+ return unwrap(await this.httpClient.get("/admin-api/alias/list/application"));
752
745
  }
753
- async shutdownSystem() {
754
- return this.httpClient.post("/system/shutdown");
746
+ // ---- Network ----
747
+ async getPeersCount() {
748
+ return this.httpClient.get("/admin-api/peers");
755
749
  }
756
750
  };
757
751
 
@@ -809,15 +803,495 @@ function createAdminApiClientFromHttpClient(httpClient, _config) {
809
803
  return new AdminApiClient(httpClient);
810
804
  }
811
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
+
812
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
+ }
813
1279
  var MeroJs = class {
814
1280
  constructor(config) {
815
1281
  this.tokenData = null;
816
1282
  this.refreshPromise = null;
1283
+ this.rpcClient = null;
1284
+ this.sseClient = null;
1285
+ this.wsClient = null;
1286
+ this.wsWarned = false;
817
1287
  this.config = {
818
1288
  timeoutMs: 1e4,
819
1289
  ...config
820
1290
  };
1291
+ this.tokenStore = config.tokenStore ?? null;
1292
+ if (this.tokenStore) {
1293
+ this.tokenData = this.tokenStore.getTokens();
1294
+ }
821
1295
  const isTauri = typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
822
1296
  this.httpClient = createBrowserHttpClient({
823
1297
  baseUrl: this.config.baseUrl,
@@ -825,6 +1299,16 @@ var MeroJs = class {
825
1299
  const token = await this.getValidToken();
826
1300
  return token?.access_token || "";
827
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
+ },
828
1312
  timeoutMs: this.config.timeoutMs,
829
1313
  credentials: this.config.requestCredentials ?? (isTauri ? "omit" : void 0)
830
1314
  });
@@ -857,6 +1341,50 @@ var MeroJs = class {
857
1341
  get admin() {
858
1342
  return this.adminClient;
859
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
+ }
860
1388
  /**
861
1389
  * Authenticate with the provided credentials
862
1390
  * This will create the root key on first use
@@ -879,12 +1407,13 @@ var MeroJs = class {
879
1407
  }
880
1408
  };
881
1409
  const response = await this.authClient.generateTokens(requestBody);
1410
+ const accessToken = response.data.access_token;
882
1411
  this.tokenData = {
883
- access_token: response.data.access_token,
1412
+ access_token: accessToken,
884
1413
  refresh_token: response.data.refresh_token,
885
- expires_at: Date.now() + 24 * 60 * 60 * 1e3
886
- // Default to 24 hours
1414
+ expires_at: expiresAtFromJwt(accessToken, Date.now() + 36e5)
887
1415
  };
1416
+ this.tokenStore?.setTokens(this.tokenData);
888
1417
  return this.tokenData;
889
1418
  } catch (error) {
890
1419
  throw new Error(
@@ -893,16 +1422,12 @@ var MeroJs = class {
893
1422
  }
894
1423
  }
895
1424
  /**
896
- * 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.
897
1429
  */
898
1430
  async getValidToken() {
899
- if (!this.tokenData) {
900
- return null;
901
- }
902
- const bufferTime = 5 * 60 * 1e3;
903
- if (Date.now() >= this.tokenData.expires_at - bufferTime) {
904
- return await this.refreshToken();
905
- }
906
1431
  return this.tokenData;
907
1432
  }
908
1433
  /**
@@ -935,15 +1460,15 @@ var MeroJs = class {
935
1460
  access_token: this.tokenData.access_token,
936
1461
  refresh_token: this.tokenData.refresh_token
937
1462
  });
1463
+ const accessToken = response.data.access_token;
938
1464
  this.tokenData = {
939
- access_token: response.data.access_token,
1465
+ access_token: accessToken,
940
1466
  refresh_token: response.data.refresh_token,
941
- expires_at: Date.now() + 24 * 60 * 60 * 1e3
942
- // Default to 24 hours
1467
+ expires_at: expiresAtFromJwt(accessToken, Date.now() + 36e5)
943
1468
  };
1469
+ this.tokenStore?.setTokens(this.tokenData);
944
1470
  return this.tokenData;
945
1471
  } catch (error) {
946
- this.clearToken();
947
1472
  throw new Error(
948
1473
  `Token refresh failed: ${error instanceof Error ? error.message : "Unknown error"}`
949
1474
  );
@@ -954,6 +1479,7 @@ var MeroJs = class {
954
1479
  */
955
1480
  clearToken() {
956
1481
  this.tokenData = null;
1482
+ this.tokenStore?.clear();
957
1483
  }
958
1484
  /**
959
1485
  * Check if the SDK is authenticated
@@ -961,22 +1487,112 @@ var MeroJs = class {
961
1487
  isAuthenticated() {
962
1488
  return this.tokenData !== null;
963
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
+ }
964
1500
  /**
965
1501
  * Get the current token data (for debugging)
966
1502
  */
967
1503
  getTokenData() {
968
1504
  return this.tokenData;
969
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
+ }
970
1525
  };
971
1526
  function createMeroJs(config) {
972
1527
  return new MeroJs(config);
973
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
+ };
974
1583
  export {
975
1584
  AdminApiClient,
976
1585
  AuthApiClient,
977
1586
  HTTPError,
1587
+ LocalStorageTokenStore,
1588
+ MemoryTokenStore,
978
1589
  MeroJs,
1590
+ RpcClient,
1591
+ RpcError,
1592
+ SseClient,
979
1593
  WebHttpClient,
1594
+ WsClient,
1595
+ buildAuthLoginUrl,
980
1596
  combineSignals,
981
1597
  createAdminApiClient,
982
1598
  createAdminApiClientFromHttpClient,
@@ -993,6 +1609,7 @@ export {
993
1609
  createRetryableMethod,
994
1610
  createTimeoutSignal,
995
1611
  createUniversalHttpClient,
1612
+ parseAuthCallback,
996
1613
  withRetry
997
1614
  };
998
1615
  //# sourceMappingURL=index.mjs.map