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