@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.
- package/dist/admin-api/admin-client.d.ts +29 -27
- package/dist/admin-api/admin-client.d.ts.map +1 -1
- package/dist/admin-api/admin-client.js +70 -58
- package/dist/admin-api/admin-client.js.map +1 -1
- package/dist/admin-api/admin-types.d.ts +57 -159
- package/dist/admin-api/admin-types.d.ts.map +1 -1
- package/dist/admin-api/admin-types.js +1 -1
- package/dist/admin-api/admin-types.js.map +1 -1
- package/dist/auth/index.d.ts +26 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +51 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/events/index.d.ts +5 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +3 -0
- package/dist/events/index.js.map +1 -0
- package/dist/events/sse.d.ts +41 -0
- package/dist/events/sse.d.ts.map +1 -0
- package/dist/events/sse.js +237 -0
- package/dist/events/sse.js.map +1 -0
- package/dist/events/ws.d.ts +42 -0
- package/dist/events/ws.d.ts.map +1 -0
- package/dist/events/ws.js +178 -0
- package/dist/events/ws.js.map +1 -0
- package/dist/http-client/http-factory.d.ts +18 -0
- package/dist/http-client/http-factory.d.ts.map +1 -1
- package/dist/http-client/http-factory.js +2 -0
- package/dist/http-client/http-factory.js.map +1 -1
- package/dist/http-client/http-types.d.ts +6 -0
- package/dist/http-client/http-types.d.ts.map +1 -1
- package/dist/http-client/web-client.d.ts +2 -0
- package/dist/http-client/web-client.d.ts.map +1 -1
- package/dist/http-client/web-client.js +129 -58
- package/dist/http-client/web-client.js.map +1 -1
- package/dist/index.browser.mjs +2 -1
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +784 -148
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +784 -148
- package/dist/index.mjs.map +4 -4
- package/dist/mero-js.d.ts +47 -1
- package/dist/mero-js.d.ts.map +1 -1
- package/dist/mero-js.js +132 -16
- package/dist/mero-js.js.map +1 -1
- package/dist/rpc/index.d.ts +21 -0
- package/dist/rpc/index.d.ts.map +1 -0
- package/dist/rpc/index.js +39 -0
- package/dist/rpc/index.js.map +1 -0
- package/dist/token-store/index.d.ts +20 -0
- package/dist/token-store/index.d.ts.map +1 -0
- package/dist/token-store/index.js +62 -0
- package/dist/token-store/index.js.map +1 -0
- 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
|
|
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
|
-
|
|
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
|
-
|
|
193
|
-
|
|
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
|
-
|
|
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
|
|
645
|
+
// ---- Health and Status (public, no auth) ----
|
|
625
646
|
async healthCheck() {
|
|
626
|
-
|
|
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
|
|
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
|
-
`/
|
|
671
|
+
`/admin-api/packages/${encodeURIComponent(packageName)}/latest`
|
|
659
672
|
);
|
|
660
673
|
}
|
|
661
|
-
// Context Management
|
|
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
|
-
|
|
677
|
-
|
|
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
|
-
|
|
681
|
-
|
|
690
|
+
// ---- Context Identity ----
|
|
691
|
+
async generateContextIdentity() {
|
|
692
|
+
return unwrap(await this.httpClient.post("/admin-api/identity/context", {}));
|
|
682
693
|
}
|
|
683
|
-
async
|
|
684
|
-
return this.httpClient.get(
|
|
694
|
+
async getContextIdentities(contextId) {
|
|
695
|
+
return unwrap(await this.httpClient.get(`/admin-api/contexts/${contextId}/identities`));
|
|
685
696
|
}
|
|
686
|
-
async
|
|
687
|
-
return this.httpClient.get(`/
|
|
697
|
+
async getContextIdentitiesOwned(contextId) {
|
|
698
|
+
return unwrap(await this.httpClient.get(`/admin-api/contexts/${contextId}/identities-owned`));
|
|
688
699
|
}
|
|
689
|
-
//
|
|
690
|
-
async
|
|
691
|
-
return this.httpClient.post("/
|
|
700
|
+
// ---- Context Invite / Join ----
|
|
701
|
+
async inviteToContext(request) {
|
|
702
|
+
return unwrap(await this.httpClient.post("/admin-api/contexts/invite", request));
|
|
692
703
|
}
|
|
693
|
-
async
|
|
694
|
-
return this.httpClient.
|
|
704
|
+
async joinContext(request) {
|
|
705
|
+
return unwrap(await this.httpClient.post("/admin-api/contexts/join", request));
|
|
695
706
|
}
|
|
696
|
-
|
|
697
|
-
|
|
707
|
+
// ---- Blob Management ----
|
|
708
|
+
async uploadBlob(data) {
|
|
709
|
+
return unwrap(await this.httpClient.put("/admin-api/blobs", data));
|
|
698
710
|
}
|
|
699
|
-
async
|
|
700
|
-
return this.httpClient.
|
|
711
|
+
async deleteBlob(blobId) {
|
|
712
|
+
return unwrap(await this.httpClient.delete(`/admin-api/blobs/${blobId}`));
|
|
701
713
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
return this.httpClient.get("/network/peers");
|
|
714
|
+
async listBlobs() {
|
|
715
|
+
return unwrap(await this.httpClient.get("/admin-api/blobs"));
|
|
705
716
|
}
|
|
706
|
-
async
|
|
707
|
-
return this.httpClient.get(
|
|
717
|
+
async getBlob(blobId) {
|
|
718
|
+
return unwrap(await this.httpClient.get(`/admin-api/blobs/${blobId}`));
|
|
708
719
|
}
|
|
709
|
-
|
|
710
|
-
|
|
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
|
|
713
|
-
return this.httpClient.
|
|
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
|
|
719
|
-
return this.httpClient.
|
|
728
|
+
async lookupContextAlias(name) {
|
|
729
|
+
return this.httpClient.post(`/admin-api/alias/lookup/context/${encodeURIComponent(name)}`, {});
|
|
720
730
|
}
|
|
721
|
-
|
|
722
|
-
|
|
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
|
|
726
|
-
return this.httpClient.
|
|
734
|
+
async deleteContextAlias(name) {
|
|
735
|
+
return this.httpClient.post(`/admin-api/alias/delete/context/${encodeURIComponent(name)}`, {});
|
|
727
736
|
}
|
|
728
|
-
async
|
|
729
|
-
return this.httpClient.
|
|
737
|
+
async deleteApplicationAlias(name) {
|
|
738
|
+
return this.httpClient.post(`/admin-api/alias/delete/application/${encodeURIComponent(name)}`, {});
|
|
730
739
|
}
|
|
731
|
-
async
|
|
732
|
-
return this.httpClient.
|
|
740
|
+
async listContextAliases() {
|
|
741
|
+
return unwrap(await this.httpClient.get("/admin-api/alias/list/context"));
|
|
733
742
|
}
|
|
734
|
-
async
|
|
735
|
-
return this.httpClient.
|
|
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:
|
|
1412
|
+
access_token: accessToken,
|
|
865
1413
|
refresh_token: response.data.refresh_token,
|
|
866
|
-
expires_at: Date.now() +
|
|
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
|
|
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:
|
|
1465
|
+
access_token: accessToken,
|
|
921
1466
|
refresh_token: response.data.refresh_token,
|
|
922
|
-
expires_at: Date.now() +
|
|
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
|