ptech-shell-dev 0.1.0 → 1.6.3

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/index.js CHANGED
@@ -31,16 +31,607 @@ function createStandaloneAnalyticsService() {
31
31
  }
32
32
 
33
33
  // src/services/apiClient.ts
34
- function createStandaloneApiClient(apiBase) {
34
+ var ApiError = class _ApiError extends Error {
35
+ status;
36
+ code;
37
+ traceId;
38
+ correlationId;
39
+ isNetworkError;
40
+ isAuthError;
41
+ isTimeoutError;
42
+ validationErrors;
43
+ cause;
44
+ constructor(details, cause) {
45
+ super(details.message);
46
+ this.name = "ApiError";
47
+ this.status = details.status;
48
+ this.code = details.code;
49
+ this.traceId = details.traceId;
50
+ this.correlationId = details.correlationId;
51
+ this.isNetworkError = details.isNetworkError;
52
+ this.isAuthError = details.isAuthError;
53
+ this.isTimeoutError = details.isTimeoutError;
54
+ this.validationErrors = details.validationErrors;
55
+ this.cause = cause;
56
+ Object.setPrototypeOf(this, _ApiError.prototype);
57
+ }
58
+ };
59
+ var DEFAULT_TIMEOUT_MS = 2e4;
60
+ var DEFAULT_RETRY_OPTIONS = {
61
+ maxAttempts: 2,
62
+ baseDelayMs: 250,
63
+ maxDelayMs: 2e3,
64
+ retryOnStatuses: [429, 503]
65
+ };
66
+ var TRACE_HEADER = "traceparent";
67
+ var CORRELATION_HEADER = "x-correlation-id";
68
+ var CORRELATION_HEADERS = ["x-correlation-id", "x-request-id"];
69
+ var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
70
+ function isApiError(value) {
71
+ return value instanceof ApiError;
72
+ }
73
+ function normalizeApiBase(apiBase) {
74
+ return apiBase.replace(/\/+$/, "");
75
+ }
76
+ function isAbsoluteUrl(input) {
77
+ return /^(?:[a-z][a-z\d+\-.]*:)?\/\//i.test(input);
78
+ }
79
+ function resolveUrl(apiBase, input) {
80
+ if (isAbsoluteUrl(input) || apiBase.length === 0) {
81
+ return input;
82
+ }
83
+ if (input.startsWith("/")) {
84
+ return `${apiBase}${input}`;
85
+ }
86
+ return `${apiBase}/${input}`;
87
+ }
88
+ function generateHex(length) {
89
+ if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.getRandomValues === "function") {
90
+ const bytes = new Uint8Array(length / 2);
91
+ globalThis.crypto.getRandomValues(bytes);
92
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
93
+ }
94
+ let value = "";
95
+ for (let i = 0; i < length; i += 1) {
96
+ value += Math.floor(Math.random() * 16).toString(16);
97
+ }
98
+ return value;
99
+ }
100
+ function createTraceparent() {
101
+ const traceId = generateHex(32);
102
+ const parentId = generateHex(16);
103
+ return `00-${traceId}-${parentId}-01`;
104
+ }
105
+ function normalizeValidationErrors(value) {
106
+ if (!value || typeof value !== "object") {
107
+ return void 0;
108
+ }
109
+ const normalized = {};
110
+ for (const [key, fieldErrors] of Object.entries(value)) {
111
+ if (!Array.isArray(fieldErrors)) {
112
+ continue;
113
+ }
114
+ const messages = fieldErrors.filter((fieldError) => typeof fieldError === "string");
115
+ if (messages.length > 0) {
116
+ normalized[key] = messages;
117
+ }
118
+ }
119
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
120
+ }
121
+ function normalizeMethod(method) {
122
+ const normalized = method?.toUpperCase();
123
+ switch (normalized) {
124
+ case "GET":
125
+ case "POST":
126
+ case "PUT":
127
+ case "PATCH":
128
+ case "DELETE":
129
+ case "HEAD":
130
+ case "OPTIONS":
131
+ return normalized;
132
+ default:
133
+ return "GET";
134
+ }
135
+ }
136
+ function resolveBody(body, headers) {
137
+ if (body === void 0 || body === null) {
138
+ return void 0;
139
+ }
140
+ if (body instanceof FormData || body instanceof Blob || body instanceof ArrayBuffer || ArrayBuffer.isView(body) || body instanceof URLSearchParams || typeof body === "string") {
141
+ return body;
142
+ }
143
+ if (!headers.has("Content-Type")) {
144
+ headers.set("Content-Type", "application/json");
145
+ }
146
+ return JSON.stringify(body);
147
+ }
148
+ function isJsonContentType(contentType) {
149
+ if (!contentType) {
150
+ return false;
151
+ }
152
+ const normalized = contentType.toLowerCase();
153
+ return normalized.includes("application/json") || normalized.includes("+json");
154
+ }
155
+ async function parseBody(response, responseType) {
156
+ if (response.status === 204 || response.status === 205) {
157
+ return void 0;
158
+ }
159
+ if (responseType === "arrayBuffer") {
160
+ return response.arrayBuffer();
161
+ }
162
+ if (responseType === "blob") {
163
+ return response.blob();
164
+ }
165
+ const text = await response.text();
166
+ if (!text) {
167
+ return void 0;
168
+ }
169
+ if (responseType === "text") {
170
+ return text;
171
+ }
172
+ const shouldParseJson = responseType === "json" || isJsonContentType(response.headers.get("content-type"));
173
+ if (!shouldParseJson) {
174
+ return text;
175
+ }
176
+ try {
177
+ return JSON.parse(text);
178
+ } catch (error) {
179
+ if (responseType === "json") {
180
+ throw error;
181
+ }
182
+ return text;
183
+ }
184
+ }
185
+ function toProblemDetails(payload) {
186
+ if (!payload || typeof payload !== "object") {
187
+ return null;
188
+ }
189
+ return payload;
190
+ }
191
+ function resolveTraceId(problem, response) {
192
+ if (typeof problem?.traceId === "string" && problem.traceId.length > 0) {
193
+ return problem.traceId;
194
+ }
195
+ if (typeof problem?.extensions?.traceId === "string" && problem.extensions.traceId.length > 0) {
196
+ return problem.extensions.traceId;
197
+ }
198
+ const traceparent = response.headers.get(TRACE_HEADER);
199
+ if (traceparent) {
200
+ const parts = traceparent.split("-");
201
+ if (parts.length >= 4 && parts[1]) {
202
+ return parts[1];
203
+ }
204
+ return traceparent;
205
+ }
206
+ for (const headerName of CORRELATION_HEADERS) {
207
+ const value = response.headers.get(headerName);
208
+ if (value) {
209
+ return value;
210
+ }
211
+ }
212
+ return void 0;
213
+ }
214
+ function resolveErrorCode(problem) {
215
+ if (typeof problem?.code === "string" && problem.code.length > 0) {
216
+ return problem.code;
217
+ }
218
+ if (typeof problem?.extensions?.code === "string" && problem.extensions.code.length > 0) {
219
+ return problem.extensions.code;
220
+ }
221
+ return void 0;
222
+ }
223
+ function resolveCorrelationId(problem, response) {
224
+ if (typeof problem?.extensions?.correlationId === "string" && problem.extensions.correlationId.length > 0) {
225
+ return problem.extensions.correlationId;
226
+ }
227
+ for (const headerName of CORRELATION_HEADERS) {
228
+ const value = response.headers.get(headerName);
229
+ if (value) {
230
+ return value;
231
+ }
232
+ }
233
+ return void 0;
234
+ }
235
+ function resolveHttpErrorMessage(explicitMessage, problem, payload, status) {
236
+ if (explicitMessage) {
237
+ return explicitMessage;
238
+ }
239
+ if (typeof problem?.detail === "string" && problem.detail.length > 0) {
240
+ return problem.detail;
241
+ }
242
+ if (typeof problem?.title === "string" && problem.title.length > 0) {
243
+ return problem.title;
244
+ }
245
+ if (typeof payload === "string" && payload.length > 0) {
246
+ return payload;
247
+ }
248
+ return `Request failed with status ${status}`;
249
+ }
250
+ function createHttpError(options, response, payload, problem) {
251
+ const correlationId = resolveCorrelationId(problem, response);
252
+ const traceId = resolveTraceId(problem, response) ?? correlationId;
253
+ return new ApiError({
254
+ status: response.status,
255
+ code: resolveErrorCode(problem),
256
+ traceId,
257
+ correlationId,
258
+ message: resolveHttpErrorMessage(options.errorMessage, problem, payload, response.status),
259
+ isNetworkError: false,
260
+ isAuthError: response.status === 401 || response.status === 403,
261
+ isTimeoutError: false,
262
+ validationErrors: normalizeValidationErrors(problem?.errors)
263
+ });
264
+ }
265
+ function createNetworkError(options, cause, details) {
266
+ const message = options.errorMessage ?? (details.isTimeoutError ? "Request timeout" : "Network error");
267
+ const code = details.isAbortedByCaller ? "aborted" : details.isTimeoutError ? "timeout" : "network_error";
268
+ return new ApiError(
269
+ {
270
+ code,
271
+ message,
272
+ isNetworkError: true,
273
+ isAuthError: false,
274
+ isTimeoutError: details.isTimeoutError
275
+ },
276
+ cause
277
+ );
278
+ }
279
+ function createInvalidResponseError(options, response, cause) {
280
+ return new ApiError(
281
+ {
282
+ status: response.status,
283
+ code: "invalid_response",
284
+ traceId: resolveTraceId(null, response),
285
+ message: options.errorMessage ?? "Invalid response payload",
286
+ isNetworkError: false,
287
+ isAuthError: response.status === 401 || response.status === 403,
288
+ isTimeoutError: false
289
+ },
290
+ cause
291
+ );
292
+ }
293
+ function shouldPushPostErrorNotification(method, error) {
294
+ return method === "POST" && error.code !== "aborted";
295
+ }
296
+ function resolvePostErrorNotificationMessage(error) {
297
+ if (error.traceId) {
298
+ return `${error.message} (traceId: ${error.traceId})`;
299
+ }
300
+ return error.message;
301
+ }
302
+ function pushPostErrorNotification(method, requestOptions, error, options) {
303
+ if (!shouldPushPostErrorNotification(method, error)) {
304
+ return;
305
+ }
306
+ const notificationService = options.getNotificationService?.();
307
+ if (!notificationService) {
308
+ return;
309
+ }
310
+ try {
311
+ notificationService.push({
312
+ level: "error",
313
+ title: "POST request failed",
314
+ message: resolvePostErrorNotificationMessage(error),
315
+ source: `standalone.apiClient:${requestOptions.url}`,
316
+ ttlMs: 5e3
317
+ });
318
+ } catch {
319
+ }
320
+ }
321
+ function isAbortError(error) {
322
+ return typeof DOMException !== "undefined" && error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
323
+ }
324
+ function normalizeRetryOptions(input, defaultRetryOptions) {
325
+ if (input === false) {
326
+ return null;
327
+ }
328
+ const mergedInput = input ?? defaultRetryOptions;
329
+ if (mergedInput === false) {
330
+ return null;
331
+ }
332
+ const maxAttempts = Math.max(
333
+ 1,
334
+ Math.floor(mergedInput.maxAttempts ?? DEFAULT_RETRY_OPTIONS.maxAttempts)
335
+ );
336
+ const baseDelayMs = Math.max(
337
+ 0,
338
+ Math.floor(mergedInput.baseDelayMs ?? DEFAULT_RETRY_OPTIONS.baseDelayMs)
339
+ );
340
+ const maxDelayMs = Math.max(
341
+ baseDelayMs,
342
+ Math.floor(mergedInput.maxDelayMs ?? DEFAULT_RETRY_OPTIONS.maxDelayMs)
343
+ );
344
+ const retryOnStatuses = new Set(
345
+ (mergedInput.retryOnStatuses ?? DEFAULT_RETRY_OPTIONS.retryOnStatuses).filter((status) => Number.isInteger(status) && status >= 100 && status <= 599)
346
+ );
35
347
  return {
36
- fetch: async (path, init) => {
37
- const url = path.startsWith("http") ? path : `${apiBase}${path.startsWith("/") ? "" : "/"}${path}`;
38
- const headers = new Headers(init?.headers);
39
- if (!headers.has("Authorization")) {
40
- headers.set("Authorization", "Bearer dev-token");
348
+ maxAttempts,
349
+ baseDelayMs,
350
+ maxDelayMs,
351
+ retryOnStatuses
352
+ };
353
+ }
354
+ function shouldRetryResponse(response, retry) {
355
+ return retry.retryOnStatuses.has(response.status);
356
+ }
357
+ function shouldRetryError(error) {
358
+ return error.isNetworkError && error.code !== "aborted";
359
+ }
360
+ function computeRetryDelayMs(attempt, retry) {
361
+ const exponent = Math.max(0, attempt - 1);
362
+ const rawDelay = Math.min(retry.maxDelayMs, retry.baseDelayMs * 2 ** exponent);
363
+ const jitter = Math.floor(Math.random() * Math.max(1, Math.floor(rawDelay * 0.2)));
364
+ return rawDelay + jitter;
365
+ }
366
+ async function sleep(delayMs, signal) {
367
+ if (delayMs <= 0) {
368
+ return;
369
+ }
370
+ await new Promise((resolve, reject) => {
371
+ const timeoutId = setTimeout(() => {
372
+ if (signal && onAbort) {
373
+ signal.removeEventListener("abort", onAbort);
41
374
  }
42
- return fetch(url, { ...init, headers });
375
+ resolve();
376
+ }, delayMs);
377
+ if (!signal) {
378
+ return;
43
379
  }
380
+ const onAbort = () => {
381
+ clearTimeout(timeoutId);
382
+ signal.removeEventListener("abort", onAbort);
383
+ if (typeof DOMException !== "undefined") {
384
+ reject(new DOMException("Aborted", "AbortError"));
385
+ return;
386
+ }
387
+ const abortError = new Error("Aborted");
388
+ abortError.name = "AbortError";
389
+ reject(abortError);
390
+ };
391
+ if (signal.aborted) {
392
+ onAbort();
393
+ return;
394
+ }
395
+ signal.addEventListener("abort", onAbort, { once: true });
396
+ });
397
+ }
398
+ function resolveCreateOptions(input) {
399
+ if (typeof input === "string") {
400
+ return {
401
+ apiBase: normalizeApiBase(input),
402
+ defaultAuthScopes: [],
403
+ defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
404
+ defaultRetries: DEFAULT_RETRY_OPTIONS
405
+ };
406
+ }
407
+ return {
408
+ apiBase: normalizeApiBase(input.apiBase),
409
+ getUserService: input.getUserService ?? (input.userService ? () => input.userService : void 0),
410
+ getNotificationService: input.getNotificationService ?? (input.notificationService ? () => input.notificationService : void 0),
411
+ getRequestContext: input.getRequestContext ?? (input.requestContext ? () => input.requestContext : void 0),
412
+ defaultAuthScopes: input.defaultAuthScopes ?? [],
413
+ defaultTimeoutMs: input.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS,
414
+ defaultRetries: input.defaultRetries ?? DEFAULT_RETRY_OPTIONS
415
+ };
416
+ }
417
+ function applyRequestContextHeaders(headers, options) {
418
+ const contextService = options.getRequestContext?.();
419
+ if (!contextService) {
420
+ if (!headers.has(TRACE_HEADER)) {
421
+ headers.set(TRACE_HEADER, createTraceparent());
422
+ }
423
+ return;
424
+ }
425
+ const context = contextService.getSnapshot();
426
+ if (!headers.has(TRACE_HEADER)) {
427
+ headers.set(TRACE_HEADER, `00-${context.traceId}-${generateHex(16)}-01`);
428
+ }
429
+ if (!headers.has(CORRELATION_HEADER)) {
430
+ headers.set(CORRELATION_HEADER, context.correlationId);
431
+ }
432
+ }
433
+ function syncResponseContext(response, options) {
434
+ const contextService = options.getRequestContext?.();
435
+ if (!contextService) {
436
+ return;
437
+ }
438
+ const traceparent = response.headers.get(TRACE_HEADER);
439
+ const correlationId = response.headers.get(CORRELATION_HEADER) ?? response.headers.get("x-request-id");
440
+ const traceIdFromTraceparent = traceparent?.split("-")?.[1];
441
+ contextService.update({
442
+ traceId: traceIdFromTraceparent,
443
+ correlationId: correlationId ?? void 0
444
+ });
445
+ }
446
+ async function applyAuthorization(headers, requestOptions, options) {
447
+ if (requestOptions.auth === false || headers.has("Authorization")) {
448
+ return;
449
+ }
450
+ const userService = options.getUserService?.();
451
+ const acquireAccessToken = userService?.acquireAccessToken;
452
+ if (!acquireAccessToken) {
453
+ return;
454
+ }
455
+ const scopes = requestOptions.authScopes ?? options.defaultAuthScopes;
456
+ try {
457
+ const token = await acquireAccessToken({ scopes });
458
+ if (!token?.token) {
459
+ return;
460
+ }
461
+ const tokenValue = token.token.trim();
462
+ if (!token.tokenType && tokenValue.includes(" ")) {
463
+ headers.set("Authorization", tokenValue);
464
+ return;
465
+ }
466
+ const tokenType = token.tokenType?.trim() || "Bearer";
467
+ headers.set("Authorization", `${tokenType} ${tokenValue}`);
468
+ } catch {
469
+ }
470
+ }
471
+ async function fetchWithTimeout(url, init, timeoutMs, signal, requestOptions) {
472
+ const controller = new AbortController();
473
+ let timeoutId;
474
+ let isTimeoutError = false;
475
+ let abortListener;
476
+ if (signal) {
477
+ if (signal.aborted) {
478
+ controller.abort();
479
+ } else {
480
+ const onAbort = () => controller.abort();
481
+ signal.addEventListener("abort", onAbort, { once: true });
482
+ abortListener = () => signal.removeEventListener("abort", onAbort);
483
+ }
484
+ }
485
+ if (Number.isFinite(timeoutMs) && timeoutMs > 0) {
486
+ timeoutId = setTimeout(() => {
487
+ isTimeoutError = true;
488
+ controller.abort();
489
+ }, timeoutMs);
490
+ }
491
+ try {
492
+ return await fetch(url, {
493
+ ...init,
494
+ signal: controller.signal
495
+ });
496
+ } catch (error) {
497
+ if (isApiError(error)) {
498
+ throw error;
499
+ }
500
+ if (isAbortError(error)) {
501
+ throw createNetworkError(requestOptions, error, {
502
+ isTimeoutError,
503
+ isAbortedByCaller: Boolean(signal?.aborted) && !isTimeoutError
504
+ });
505
+ }
506
+ throw createNetworkError(requestOptions, error, {
507
+ isTimeoutError: false,
508
+ isAbortedByCaller: false
509
+ });
510
+ } finally {
511
+ if (timeoutId) {
512
+ clearTimeout(timeoutId);
513
+ }
514
+ abortListener?.();
515
+ }
516
+ }
517
+ async function executeRaw(requestOptions, options) {
518
+ const method = normalizeMethod(requestOptions.method);
519
+ const shouldUseRetry = IDEMPOTENT_METHODS.has(method);
520
+ const retry = normalizeRetryOptions(requestOptions.retries, options.defaultRetries);
521
+ const maxAttempts = shouldUseRetry && retry ? retry.maxAttempts : 1;
522
+ const timeoutMs = requestOptions.timeoutMs ?? options.defaultTimeoutMs;
523
+ const headers = new Headers(requestOptions.headers);
524
+ applyRequestContextHeaders(headers, options);
525
+ await applyAuthorization(headers, requestOptions, options);
526
+ const url = resolveUrl(options.apiBase, requestOptions.url);
527
+ const body = resolveBody(requestOptions.body, headers);
528
+ const baseInit = {
529
+ method,
530
+ headers,
531
+ body
532
+ };
533
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
534
+ try {
535
+ const response = await fetchWithTimeout(
536
+ url,
537
+ baseInit,
538
+ timeoutMs,
539
+ requestOptions.signal,
540
+ requestOptions
541
+ );
542
+ syncResponseContext(response, options);
543
+ if (retry && shouldUseRetry && attempt < maxAttempts && shouldRetryResponse(response, retry)) {
544
+ const delayMs = computeRetryDelayMs(attempt, retry);
545
+ await sleep(delayMs, requestOptions.signal);
546
+ continue;
547
+ }
548
+ return response;
549
+ } catch (error) {
550
+ const normalizedError = isApiError(error) ? error : isAbortError(error) ? createNetworkError(requestOptions, error, {
551
+ isTimeoutError: false,
552
+ isAbortedByCaller: true
553
+ }) : createNetworkError(requestOptions, error, {
554
+ isTimeoutError: false,
555
+ isAbortedByCaller: false
556
+ });
557
+ if (retry && shouldUseRetry && attempt < maxAttempts && shouldRetryError(normalizedError)) {
558
+ const delayMs = computeRetryDelayMs(attempt, retry);
559
+ await sleep(delayMs, requestOptions.signal);
560
+ continue;
561
+ }
562
+ throw normalizedError;
563
+ }
564
+ }
565
+ throw createNetworkError(requestOptions, null, {
566
+ isTimeoutError: false,
567
+ isAbortedByCaller: false
568
+ });
569
+ }
570
+ async function executeParsed(requestOptions, options) {
571
+ const method = normalizeMethod(requestOptions.method);
572
+ try {
573
+ const response = await executeRaw(requestOptions, options);
574
+ const responseType = requestOptions.responseType ?? "auto";
575
+ let payload;
576
+ try {
577
+ payload = await parseBody(response, responseType);
578
+ } catch (error) {
579
+ throw createInvalidResponseError(requestOptions, response, error);
580
+ }
581
+ if (!response.ok) {
582
+ const problem = toProblemDetails(payload);
583
+ throw createHttpError(requestOptions, response, payload, problem);
584
+ }
585
+ return payload;
586
+ } catch (error) {
587
+ const normalizedError = isApiError(error) ? error : isAbortError(error) ? createNetworkError(requestOptions, error, {
588
+ isTimeoutError: false,
589
+ isAbortedByCaller: true
590
+ }) : createNetworkError(requestOptions, error, {
591
+ isTimeoutError: false,
592
+ isAbortedByCaller: false
593
+ });
594
+ pushPostErrorNotification(method, requestOptions, normalizedError, options);
595
+ throw normalizedError;
596
+ }
597
+ }
598
+ function createStandaloneApiClient(options) {
599
+ const resolvedOptions = resolveCreateOptions(options);
600
+ return {
601
+ fetch: async (input, init) => {
602
+ const headers = new Headers(init?.headers);
603
+ applyRequestContextHeaders(headers, resolvedOptions);
604
+ await applyAuthorization(
605
+ headers,
606
+ {
607
+ url: input,
608
+ method: normalizeMethod(init?.method),
609
+ auth: true
610
+ },
611
+ resolvedOptions
612
+ );
613
+ const url = resolveUrl(resolvedOptions.apiBase, input);
614
+ const response = await fetch(url, {
615
+ ...init,
616
+ headers
617
+ });
618
+ syncResponseContext(response, resolvedOptions);
619
+ return response;
620
+ },
621
+ request: (requestOptions) => executeParsed(
622
+ {
623
+ ...requestOptions,
624
+ method: normalizeMethod(requestOptions.method)
625
+ },
626
+ resolvedOptions
627
+ ),
628
+ requestRaw: (requestOptions) => executeRaw(
629
+ {
630
+ ...requestOptions,
631
+ method: normalizeMethod(requestOptions.method)
632
+ },
633
+ resolvedOptions
634
+ )
44
635
  };
45
636
  }
46
637
 
@@ -156,21 +747,83 @@ function createStandaloneI18nService(initialLang) {
156
747
  }
157
748
 
158
749
  // src/services/navigation.ts
750
+ var NAVIGATION_BASE_URL = "https://mfe.navigation.local";
159
751
  function createStandaloneNavigationService(initialPath = "/standalone") {
160
752
  const listeners = /* @__PURE__ */ new Set();
161
- let currentPath = initialPath;
753
+ const history = [createInitialLocation(initialPath)];
754
+ let currentIndex = 0;
755
+ let currentAction = "POP";
162
756
  function emitChange() {
163
757
  for (const listener of listeners) {
164
758
  listener();
165
759
  }
166
760
  }
761
+ function getCurrentLocation() {
762
+ return history[currentIndex];
763
+ }
764
+ function createSnapshot2() {
765
+ const location = getCurrentLocation();
766
+ return {
767
+ action: currentAction,
768
+ location: { ...location },
769
+ canGoBack: currentIndex > 0,
770
+ canGoForward: currentIndex < history.length - 1
771
+ };
772
+ }
773
+ function createHref(to) {
774
+ return toToHref(to, getCurrentLocation());
775
+ }
167
776
  return {
168
- getPath: () => currentPath,
169
- navigate: (path) => {
170
- if (path === currentPath) {
777
+ getSnapshot: () => createSnapshot2(),
778
+ getPath: () => getCurrentLocation().pathname,
779
+ createHref,
780
+ navigate: (to, options) => {
781
+ if (typeof to === "number") {
782
+ const nextIndex = clamp(currentIndex + to, 0, history.length - 1);
783
+ if (nextIndex === currentIndex) {
784
+ return;
785
+ }
786
+ currentIndex = nextIndex;
787
+ currentAction = "POP";
788
+ emitChange();
789
+ return;
790
+ }
791
+ const current = getCurrentLocation();
792
+ const next = toLocation(to, current, options?.state);
793
+ const replace = options?.replace === true;
794
+ if (replace) {
795
+ if (isSameLocation(current, next)) {
796
+ return;
797
+ }
798
+ history[currentIndex] = next;
799
+ currentAction = "REPLACE";
800
+ emitChange();
171
801
  return;
172
802
  }
173
- currentPath = path;
803
+ if (isSameLocation(current, next)) {
804
+ return;
805
+ }
806
+ history.splice(currentIndex + 1, history.length - currentIndex - 1, next);
807
+ currentIndex += 1;
808
+ currentAction = "PUSH";
809
+ emitChange();
810
+ },
811
+ back: () => {
812
+ const nextIndex = currentIndex - 1;
813
+ if (nextIndex < 0) {
814
+ return;
815
+ }
816
+ currentIndex = nextIndex;
817
+ currentAction = "POP";
818
+ emitChange();
819
+ },
820
+ forward: () => {
821
+ const nextIndex = currentIndex + 1;
822
+ if (nextIndex >= history.length) {
823
+ return;
824
+ }
825
+ currentIndex = nextIndex;
826
+ currentAction = "POP";
174
827
  emitChange();
175
828
  },
176
829
  subscribe: (listener) => {
@@ -179,6 +832,66 @@ function createStandaloneNavigationService(initialPath = "/standalone") {
179
832
  }
180
833
  };
181
834
  }
835
+ function createInitialLocation(initialPath) {
836
+ const url = new URL(initialPath, `${NAVIGATION_BASE_URL}/`);
837
+ return {
838
+ pathname: url.pathname || "/",
839
+ search: url.search,
840
+ hash: url.hash,
841
+ state: void 0,
842
+ key: createLocationKey()
843
+ };
844
+ }
845
+ function createLocationKey() {
846
+ return Math.random().toString(36).slice(2, 10);
847
+ }
848
+ function normalizeSearch(search) {
849
+ if (!search) {
850
+ return "";
851
+ }
852
+ return search.startsWith("?") ? search : `?${search}`;
853
+ }
854
+ function normalizeHash(hash) {
855
+ if (!hash) {
856
+ return "";
857
+ }
858
+ return hash.startsWith("#") ? hash : `#${hash}`;
859
+ }
860
+ function toToHref(to, current) {
861
+ if (typeof to === "string") {
862
+ return to;
863
+ }
864
+ const pathname = to.pathname ?? current.pathname;
865
+ const search = to.search !== void 0 ? normalizeSearch(to.search) : current.search;
866
+ const hash = to.hash !== void 0 ? normalizeHash(to.hash) : current.hash;
867
+ return `${pathname || "/"}${search}${hash}`;
868
+ }
869
+ function toLocation(to, current, state) {
870
+ if (typeof to === "string") {
871
+ const currentHref = `${current.pathname}${current.search}${current.hash}`;
872
+ const resolved = new URL(to, `${NAVIGATION_BASE_URL}${currentHref || "/"}`);
873
+ return {
874
+ pathname: resolved.pathname || "/",
875
+ search: resolved.search,
876
+ hash: resolved.hash,
877
+ state,
878
+ key: createLocationKey()
879
+ };
880
+ }
881
+ return {
882
+ pathname: to.pathname ?? current.pathname,
883
+ search: to.search !== void 0 ? normalizeSearch(to.search) : current.search,
884
+ hash: to.hash !== void 0 ? normalizeHash(to.hash) : current.hash,
885
+ state,
886
+ key: createLocationKey()
887
+ };
888
+ }
889
+ function isSameLocation(left, right) {
890
+ return left.pathname === right.pathname && left.search === right.search && left.hash === right.hash && Object.is(left.state, right.state);
891
+ }
892
+ function clamp(value, min, max) {
893
+ return Math.min(Math.max(value, min), max);
894
+ }
182
895
 
183
896
  // src/services/notification.ts
184
897
  function createStandaloneNotificationService() {
@@ -209,6 +922,7 @@ function createStandaloneNotificationService() {
209
922
  const item = {
210
923
  id,
211
924
  level: input.level,
925
+ title: input.title,
212
926
  message: input.message,
213
927
  source: input.source,
214
928
  createdAt: Date.now()
@@ -232,16 +946,77 @@ function createStandaloneNotificationService() {
232
946
 
233
947
  // src/services/observability.ts
234
948
  var MAX_ENTRIES = 100;
949
+ var LOG_LEVELS = ["debug", "info", "warn", "error"];
950
+ var DEFAULT_ENABLED_LEVELS = {
951
+ debug: true,
952
+ info: true,
953
+ warn: true,
954
+ error: true
955
+ };
235
956
  var LOG_METHOD = {
236
957
  debug: "log",
237
958
  info: "info",
238
959
  warn: "warn",
239
960
  error: "error"
240
961
  };
241
- function createStandaloneObservabilityService() {
962
+ var LOG_PRIORITY = {
963
+ debug: 10,
964
+ info: 20,
965
+ warn: 30,
966
+ error: 40
967
+ };
968
+ function isProductionEnv() {
969
+ const nodeProcess = globalThis.process;
970
+ if (nodeProcess?.env?.NODE_ENV === "production") {
971
+ return true;
972
+ }
973
+ const bundlerEnv = import.meta.env;
974
+ return bundlerEnv?.PROD === true || bundlerEnv?.MODE === "production";
975
+ }
976
+ function cloneConfig(config) {
977
+ return {
978
+ minLevel: config.minLevel,
979
+ enabledLevels: { ...config.enabledLevels }
980
+ };
981
+ }
982
+ function createInitialConfig(configPatch) {
983
+ return {
984
+ minLevel: configPatch.minLevel ?? (isProductionEnv() ? "error" : "debug"),
985
+ enabledLevels: {
986
+ ...DEFAULT_ENABLED_LEVELS,
987
+ ...configPatch.enabledLevels ?? {}
988
+ }
989
+ };
990
+ }
991
+ function shouldRecordEntry(level, config) {
992
+ if (!config.enabledLevels[level]) {
993
+ return false;
994
+ }
995
+ return LOG_PRIORITY[level] >= LOG_PRIORITY[config.minLevel];
996
+ }
997
+ function applyConfigPatch(config, patch) {
998
+ return {
999
+ minLevel: patch.minLevel ?? config.minLevel,
1000
+ enabledLevels: patch.enabledLevels ? { ...config.enabledLevels, ...patch.enabledLevels } : { ...config.enabledLevels }
1001
+ };
1002
+ }
1003
+ function hasConfigChanged(current, next) {
1004
+ if (current.minLevel !== next.minLevel) {
1005
+ return true;
1006
+ }
1007
+ for (const level of LOG_LEVELS) {
1008
+ if (current.enabledLevels[level] !== next.enabledLevels[level]) {
1009
+ return true;
1010
+ }
1011
+ }
1012
+ return false;
1013
+ }
1014
+ function createStandaloneObservabilityService(configPatch = {}, options = {}) {
242
1015
  const listeners = /* @__PURE__ */ new Set();
243
1016
  const entries = [];
244
1017
  let nextId = 1;
1018
+ let config = createInitialConfig(configPatch);
1019
+ const redact = options.redact ?? ((data) => data);
245
1020
  function emitChange() {
246
1021
  for (const listener of listeners) {
247
1022
  listener();
@@ -249,6 +1024,9 @@ function createStandaloneObservabilityService() {
249
1024
  }
250
1025
  return {
251
1026
  log: (entry) => {
1027
+ if (!shouldRecordEntry(entry.level, config)) {
1028
+ return;
1029
+ }
252
1030
  const nextEntry = {
253
1031
  ...entry,
254
1032
  id: nextId,
@@ -261,12 +1039,35 @@ function createStandaloneObservabilityService() {
261
1039
  }
262
1040
  const method = LOG_METHOD[nextEntry.level];
263
1041
  console[method](`[${nextEntry.source}] ${nextEntry.message}`, nextEntry.data ?? "");
1042
+ if (options.telemetrySink) {
1043
+ const traceContext = options.getTraceContext?.();
1044
+ void Promise.resolve(
1045
+ options.telemetrySink.track({
1046
+ level: nextEntry.level,
1047
+ source: nextEntry.source,
1048
+ message: nextEntry.message,
1049
+ timestamp: nextEntry.timestamp,
1050
+ traceId: traceContext?.traceId,
1051
+ correlationId: traceContext?.correlationId,
1052
+ data: redact(nextEntry.data)
1053
+ })
1054
+ ).catch(() => void 0);
1055
+ }
264
1056
  emitChange();
265
1057
  },
266
1058
  getEntries: () => [...entries],
267
1059
  subscribe: (listener) => {
268
1060
  listeners.add(listener);
269
1061
  return () => listeners.delete(listener);
1062
+ },
1063
+ getConfig: () => cloneConfig(config),
1064
+ updateConfig: (patch) => {
1065
+ const nextConfig = applyConfigPatch(config, patch);
1066
+ if (!hasConfigChanged(config, nextConfig)) {
1067
+ return;
1068
+ }
1069
+ config = nextConfig;
1070
+ emitChange();
270
1071
  }
271
1072
  };
272
1073
  }
@@ -290,6 +1091,315 @@ function createStandalonePermissionService(initialPermissions = DEFAULT_PERMISSI
290
1091
  };
291
1092
  }
292
1093
 
1094
+ // src/services/realtime.ts
1095
+ var DEFAULT_RECONNECT = {
1096
+ initialDelayMs: 800,
1097
+ maxDelayMs: 15e3,
1098
+ maxAttempts: Number.POSITIVE_INFINITY
1099
+ };
1100
+ function normalizeReconnectOptions(input) {
1101
+ if (!input) {
1102
+ return { ...DEFAULT_RECONNECT };
1103
+ }
1104
+ return {
1105
+ initialDelayMs: Math.max(0, Math.floor(input.initialDelayMs ?? DEFAULT_RECONNECT.initialDelayMs)),
1106
+ maxDelayMs: Math.max(
1107
+ Math.floor(input.initialDelayMs ?? DEFAULT_RECONNECT.initialDelayMs),
1108
+ Math.floor(input.maxDelayMs ?? DEFAULT_RECONNECT.maxDelayMs)
1109
+ ),
1110
+ maxAttempts: input.maxAttempts === void 0 ? DEFAULT_RECONNECT.maxAttempts : Math.max(1, Math.floor(input.maxAttempts))
1111
+ };
1112
+ }
1113
+ function computeReconnectDelayMs(attempt, reconnect) {
1114
+ const exponent = Math.max(0, attempt - 1);
1115
+ const raw = Math.min(reconnect.maxDelayMs, reconnect.initialDelayMs * 2 ** exponent);
1116
+ const jitter = Math.floor(Math.random() * Math.max(1, Math.floor(raw * 0.2)));
1117
+ return raw + jitter;
1118
+ }
1119
+ function createNoopTransport() {
1120
+ let messageListener;
1121
+ let disconnectListener;
1122
+ return {
1123
+ start: async () => {
1124
+ messageListener = messageListener;
1125
+ disconnectListener = disconnectListener;
1126
+ },
1127
+ stop: async () => void 0,
1128
+ onMessage: (listener) => {
1129
+ messageListener = listener;
1130
+ return () => {
1131
+ if (messageListener === listener) {
1132
+ messageListener = void 0;
1133
+ }
1134
+ };
1135
+ },
1136
+ onDisconnect: (listener) => {
1137
+ disconnectListener = listener;
1138
+ return () => {
1139
+ if (disconnectListener === listener) {
1140
+ disconnectListener = void 0;
1141
+ }
1142
+ };
1143
+ },
1144
+ send: async () => void 0
1145
+ };
1146
+ }
1147
+ function resolveTraceContext(requestContext) {
1148
+ if (!requestContext) {
1149
+ return {};
1150
+ }
1151
+ const snapshot = requestContext.getSnapshot();
1152
+ return {
1153
+ traceId: snapshot.traceId,
1154
+ correlationId: snapshot.correlationId
1155
+ };
1156
+ }
1157
+ function createStandaloneRealtimeService(options = {}) {
1158
+ const listeners = /* @__PURE__ */ new Set();
1159
+ const eventListeners = /* @__PURE__ */ new Map();
1160
+ const reconnect = normalizeReconnectOptions(options.reconnect);
1161
+ const resolveUserService = options.getUserService ?? (options.userService ? () => options.userService : void 0);
1162
+ const resolveRequestContext = options.getRequestContext ?? (options.requestContext ? () => options.requestContext : void 0);
1163
+ const createTransport = options.createTransport ?? createNoopTransport;
1164
+ let state = "idle";
1165
+ let transport = null;
1166
+ let stopRequested = false;
1167
+ let activeAttempt = 0;
1168
+ let reconnectTimer;
1169
+ let unbindMessage;
1170
+ let unbindDisconnect;
1171
+ let inFlightStart = null;
1172
+ function emitStateChanged() {
1173
+ for (const listener of listeners) {
1174
+ listener();
1175
+ }
1176
+ }
1177
+ function setState(next) {
1178
+ if (state === next) {
1179
+ return;
1180
+ }
1181
+ state = next;
1182
+ emitStateChanged();
1183
+ }
1184
+ function clearReconnectTimer() {
1185
+ if (!reconnectTimer) {
1186
+ return;
1187
+ }
1188
+ clearTimeout(reconnectTimer);
1189
+ reconnectTimer = void 0;
1190
+ }
1191
+ function unbindTransportListeners() {
1192
+ unbindMessage?.();
1193
+ unbindMessage = void 0;
1194
+ unbindDisconnect?.();
1195
+ unbindDisconnect = void 0;
1196
+ }
1197
+ function dispatchEnvelope(envelope) {
1198
+ const listenersForEvent = eventListeners.get(envelope.event);
1199
+ if (!listenersForEvent || listenersForEvent.size === 0) {
1200
+ return;
1201
+ }
1202
+ for (const listener of listenersForEvent) {
1203
+ listener(envelope);
1204
+ }
1205
+ }
1206
+ async function resolveAccessToken() {
1207
+ const userService = resolveUserService?.();
1208
+ const acquireAccessToken = userService?.acquireAccessToken;
1209
+ if (!acquireAccessToken) {
1210
+ return void 0;
1211
+ }
1212
+ try {
1213
+ const token = await acquireAccessToken({ scopes: [] });
1214
+ return token?.token;
1215
+ } catch {
1216
+ return void 0;
1217
+ }
1218
+ }
1219
+ function scheduleReconnect() {
1220
+ if (stopRequested) {
1221
+ return;
1222
+ }
1223
+ if (activeAttempt >= reconnect.maxAttempts) {
1224
+ setState("disconnected");
1225
+ return;
1226
+ }
1227
+ activeAttempt += 1;
1228
+ const delayMs = computeReconnectDelayMs(activeAttempt, reconnect);
1229
+ setState("reconnecting");
1230
+ clearReconnectTimer();
1231
+ reconnectTimer = setTimeout(() => {
1232
+ reconnectTimer = void 0;
1233
+ void startInternal();
1234
+ }, delayMs);
1235
+ }
1236
+ async function bindTransportListeners(currentTransport) {
1237
+ unbindTransportListeners();
1238
+ unbindMessage = currentTransport.onMessage((incoming) => {
1239
+ const context = resolveTraceContext(resolveRequestContext?.());
1240
+ dispatchEnvelope({
1241
+ event: incoming.event,
1242
+ payload: incoming.payload,
1243
+ receivedAt: incoming.receivedAt || Date.now(),
1244
+ traceId: incoming.traceId ?? context.traceId,
1245
+ correlationId: incoming.correlationId ?? context.correlationId
1246
+ });
1247
+ });
1248
+ if (currentTransport.onDisconnect) {
1249
+ unbindDisconnect = currentTransport.onDisconnect(() => {
1250
+ if (stopRequested) {
1251
+ return;
1252
+ }
1253
+ scheduleReconnect();
1254
+ });
1255
+ }
1256
+ }
1257
+ async function startInternal() {
1258
+ if (inFlightStart) {
1259
+ return inFlightStart;
1260
+ }
1261
+ inFlightStart = (async () => {
1262
+ stopRequested = false;
1263
+ clearReconnectTimer();
1264
+ setState(activeAttempt > 0 ? "reconnecting" : "connecting");
1265
+ const nextTransport = createTransport();
1266
+ const token = await resolveAccessToken();
1267
+ const context = resolveTraceContext(resolveRequestContext?.());
1268
+ try {
1269
+ await nextTransport.start({
1270
+ accessToken: token,
1271
+ traceId: context.traceId,
1272
+ correlationId: context.correlationId
1273
+ });
1274
+ transport = nextTransport;
1275
+ await bindTransportListeners(nextTransport);
1276
+ activeAttempt = 0;
1277
+ setState("connected");
1278
+ } catch {
1279
+ transport = null;
1280
+ unbindTransportListeners();
1281
+ scheduleReconnect();
1282
+ }
1283
+ })();
1284
+ try {
1285
+ await inFlightStart;
1286
+ } finally {
1287
+ inFlightStart = null;
1288
+ }
1289
+ }
1290
+ async function stopInternal() {
1291
+ stopRequested = true;
1292
+ clearReconnectTimer();
1293
+ const current = transport;
1294
+ transport = null;
1295
+ unbindTransportListeners();
1296
+ if (current) {
1297
+ try {
1298
+ await current.stop();
1299
+ } catch {
1300
+ }
1301
+ }
1302
+ activeAttempt = 0;
1303
+ setState("disconnected");
1304
+ }
1305
+ return {
1306
+ start: async () => {
1307
+ if (state === "connected" || state === "connecting" || state === "reconnecting") {
1308
+ return;
1309
+ }
1310
+ await startInternal();
1311
+ },
1312
+ stop: async () => {
1313
+ await stopInternal();
1314
+ },
1315
+ restart: async () => {
1316
+ await stopInternal();
1317
+ await startInternal();
1318
+ },
1319
+ getState: () => state,
1320
+ subscribe: (listener) => {
1321
+ listeners.add(listener);
1322
+ return () => listeners.delete(listener);
1323
+ },
1324
+ on: (event, listener) => {
1325
+ let listenersForEvent = eventListeners.get(event);
1326
+ if (!listenersForEvent) {
1327
+ listenersForEvent = /* @__PURE__ */ new Set();
1328
+ eventListeners.set(event, listenersForEvent);
1329
+ }
1330
+ listenersForEvent.add(listener);
1331
+ return {
1332
+ unsubscribe: () => {
1333
+ const current = eventListeners.get(event);
1334
+ current?.delete(listener);
1335
+ if (current && current.size === 0) {
1336
+ eventListeners.delete(event);
1337
+ }
1338
+ }
1339
+ };
1340
+ },
1341
+ send: async (event, payload) => {
1342
+ if (!transport?.send) {
1343
+ return;
1344
+ }
1345
+ await transport.send(event, payload);
1346
+ }
1347
+ };
1348
+ }
1349
+
1350
+ // src/services/requestContext.ts
1351
+ function generateHex2(length) {
1352
+ if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.getRandomValues === "function") {
1353
+ const bytes = new Uint8Array(length / 2);
1354
+ globalThis.crypto.getRandomValues(bytes);
1355
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
1356
+ }
1357
+ let value = "";
1358
+ for (let index = 0; index < length; index += 1) {
1359
+ value += Math.floor(Math.random() * 16).toString(16);
1360
+ }
1361
+ return value;
1362
+ }
1363
+ function createSnapshot(seed) {
1364
+ return {
1365
+ traceId: seed?.traceId?.trim() || generateHex2(32),
1366
+ correlationId: seed?.correlationId?.trim() || generateHex2(16)
1367
+ };
1368
+ }
1369
+ function createRequestContextService(options = {}) {
1370
+ const listeners = /* @__PURE__ */ new Set();
1371
+ let snapshot = createSnapshot(options);
1372
+ function emitChange() {
1373
+ for (const listener of listeners) {
1374
+ listener();
1375
+ }
1376
+ }
1377
+ return {
1378
+ getSnapshot: () => ({ ...snapshot }),
1379
+ update: (next) => {
1380
+ const nextSnapshot = createSnapshot({
1381
+ traceId: next.traceId ?? snapshot.traceId,
1382
+ correlationId: next.correlationId ?? snapshot.correlationId
1383
+ });
1384
+ if (nextSnapshot.traceId === snapshot.traceId && nextSnapshot.correlationId === snapshot.correlationId) {
1385
+ return { ...snapshot };
1386
+ }
1387
+ snapshot = nextSnapshot;
1388
+ emitChange();
1389
+ return { ...snapshot };
1390
+ },
1391
+ rotate: () => {
1392
+ snapshot = createSnapshot();
1393
+ emitChange();
1394
+ return { ...snapshot };
1395
+ },
1396
+ subscribe: (listener) => {
1397
+ listeners.add(listener);
1398
+ return () => listeners.delete(listener);
1399
+ }
1400
+ };
1401
+ }
1402
+
293
1403
  // src/services/sharedState.ts
294
1404
  function createStandaloneSharedStateService() {
295
1405
  const records = /* @__PURE__ */ new Map();
@@ -406,11 +1516,61 @@ function createStandaloneSharedStateService() {
406
1516
  }
407
1517
 
408
1518
  // src/services/user.ts
409
- function createStandaloneUserService(user) {
1519
+ function createStandaloneUserService(seedUser) {
410
1520
  const listeners = /* @__PURE__ */ new Set();
411
- const currentUser = user;
1521
+ let currentUser = seedUser;
1522
+ function emitChanged() {
1523
+ for (const listener of listeners) {
1524
+ listener();
1525
+ }
1526
+ }
412
1527
  return {
413
1528
  getSnapshot: () => currentUser,
1529
+ getSession: () => ({
1530
+ state: currentUser ? "authenticated" : "unauthenticated",
1531
+ provider: "mock",
1532
+ account: currentUser ? {
1533
+ homeAccountId: currentUser.id,
1534
+ localAccountId: currentUser.id,
1535
+ tenantId: currentUser.tenantId,
1536
+ username: currentUser.username ?? currentUser.email ?? currentUser.name,
1537
+ name: currentUser.name,
1538
+ idTokenClaims: currentUser.claims
1539
+ } : void 0,
1540
+ idTokenClaims: currentUser?.claims
1541
+ }),
1542
+ acquireAccessToken: async (request) => {
1543
+ if (!currentUser) {
1544
+ return null;
1545
+ }
1546
+ return {
1547
+ token: `standalone-token-${currentUser.id}`,
1548
+ tokenType: "Bearer",
1549
+ scopes: request.scopes,
1550
+ expiresAt: Date.now() + 60 * 60 * 1e3
1551
+ };
1552
+ },
1553
+ login: async (options) => {
1554
+ if (currentUser) {
1555
+ return;
1556
+ }
1557
+ const loginHint = options?.loginHint?.trim();
1558
+ const id = loginHint ? loginHint.toLowerCase() : "dev";
1559
+ currentUser = {
1560
+ id,
1561
+ name: loginHint || "Dev User",
1562
+ username: loginHint || "dev",
1563
+ email: loginHint ? `${loginHint}@example.local` : "dev@example.local"
1564
+ };
1565
+ emitChanged();
1566
+ },
1567
+ logout: async () => {
1568
+ if (!currentUser) {
1569
+ return;
1570
+ }
1571
+ currentUser = null;
1572
+ emitChanged();
1573
+ },
414
1574
  subscribe: (listener) => {
415
1575
  listeners.add(listener);
416
1576
  return () => listeners.delete(listener);
@@ -424,13 +1584,24 @@ function initStandaloneServices(options = {}) {
424
1584
  apiBase = "http://localhost:4000",
425
1585
  lang = "vi",
426
1586
  user = { id: "dev", name: "Dev User" },
1587
+ observability: observabilityConfig = {},
1588
+ observabilityOptions,
1589
+ realtime: realtimeOptions,
427
1590
  customize,
428
1591
  registerMode = "if-missing"
429
1592
  } = options;
1593
+ const userService = createStandaloneUserService(user ?? null);
1594
+ const notificationService = createStandaloneNotificationService();
1595
+ const requestContext = createRequestContextService();
430
1596
  const services = {
431
1597
  i18n: createStandaloneI18nService(lang),
432
- userService: createStandaloneUserService(user ?? null),
433
- apiClient: createStandaloneApiClient(apiBase),
1598
+ userService,
1599
+ apiClient: createStandaloneApiClient({
1600
+ apiBase,
1601
+ userService,
1602
+ notificationService,
1603
+ requestContext
1604
+ }),
434
1605
  navigation: createStandaloneNavigationService("/standalone"),
435
1606
  configService: createStandaloneConfigService(
436
1607
  { [FEATURE_FLAGS2.uiExperimental]: true },
@@ -438,8 +1609,17 @@ function initStandaloneServices(options = {}) {
438
1609
  ),
439
1610
  permissionService: createStandalonePermissionService(),
440
1611
  sharedState: createStandaloneSharedStateService(),
441
- observability: createStandaloneObservabilityService(),
442
- notification: createStandaloneNotificationService(),
1612
+ requestContext,
1613
+ realtime: createStandaloneRealtimeService({
1614
+ ...realtimeOptions,
1615
+ userService,
1616
+ requestContext
1617
+ }),
1618
+ observability: createStandaloneObservabilityService(observabilityConfig, {
1619
+ ...observabilityOptions,
1620
+ getTraceContext: () => requestContext.getSnapshot()
1621
+ }),
1622
+ notification: notificationService,
443
1623
  analytics: createStandaloneAnalyticsService()
444
1624
  };
445
1625
  customize?.(services);
@@ -474,6 +1654,12 @@ function initStandaloneServices(options = {}) {
474
1654
  if (registerMode === "always" || !getService(TOKENS.sharedState)) {
475
1655
  registerService(TOKENS.sharedState, services.sharedState);
476
1656
  }
1657
+ if (registerMode === "always" || !getService(TOKENS.requestContext)) {
1658
+ registerService(TOKENS.requestContext, services.requestContext);
1659
+ }
1660
+ if (registerMode === "always" || !getService(TOKENS.realtime)) {
1661
+ registerService(TOKENS.realtime, services.realtime);
1662
+ }
477
1663
  if (registerMode === "always" || !getService(TOKENS.observability)) {
478
1664
  registerService(TOKENS.observability, services.observability);
479
1665
  }
@@ -484,9 +1670,464 @@ function initStandaloneServices(options = {}) {
484
1670
  registerService(TOKENS.analytics, services.analytics);
485
1671
  }
486
1672
  }
1673
+
1674
+ // src/services/apiErrorMapper.ts
1675
+ function isValidationError(error) {
1676
+ return Boolean(error.validationErrors && Object.keys(error.validationErrors).length > 0);
1677
+ }
1678
+ function mapApiErrorToUiError(error) {
1679
+ const supportTraceId = error.traceId ?? error.correlationId;
1680
+ if (error.isAuthError) {
1681
+ return {
1682
+ i18nKey: "errors.auth.required",
1683
+ params: {},
1684
+ supportTraceId,
1685
+ severity: "warning"
1686
+ };
1687
+ }
1688
+ if (error.isTimeoutError) {
1689
+ return {
1690
+ i18nKey: "errors.network.timeout",
1691
+ params: {},
1692
+ supportTraceId,
1693
+ severity: "error"
1694
+ };
1695
+ }
1696
+ if (error.isNetworkError) {
1697
+ return {
1698
+ i18nKey: "errors.network.unreachable",
1699
+ params: {},
1700
+ supportTraceId,
1701
+ severity: "error"
1702
+ };
1703
+ }
1704
+ if (isValidationError(error)) {
1705
+ return {
1706
+ i18nKey: "errors.validation.generic",
1707
+ params: { status: error.status ?? 400 },
1708
+ supportTraceId,
1709
+ severity: "warning"
1710
+ };
1711
+ }
1712
+ return {
1713
+ i18nKey: "errors.request.failed",
1714
+ params: { status: error.status ?? 500 },
1715
+ supportTraceId,
1716
+ severity: "error"
1717
+ };
1718
+ }
1719
+
1720
+ // src/services/msalUser.ts
1721
+ function toClaims(value) {
1722
+ if (!value || typeof value !== "object") {
1723
+ return void 0;
1724
+ }
1725
+ return value;
1726
+ }
1727
+ function pickRoles(claims) {
1728
+ if (!claims) {
1729
+ return void 0;
1730
+ }
1731
+ const roles = claims.roles;
1732
+ if (!Array.isArray(roles)) {
1733
+ return void 0;
1734
+ }
1735
+ return roles.filter((role) => typeof role === "string");
1736
+ }
1737
+ function pickFirstString(...values) {
1738
+ for (const value of values) {
1739
+ if (typeof value === "string" && value.length > 0) {
1740
+ return value;
1741
+ }
1742
+ }
1743
+ return void 0;
1744
+ }
1745
+ function defaultMapUser(account) {
1746
+ const claims = toClaims(account.idTokenClaims);
1747
+ const email = pickFirstString(claims?.preferred_username, claims?.email, claims?.upn);
1748
+ return {
1749
+ id: account.homeAccountId || account.localAccountId,
1750
+ name: pickFirstString(account.name, claims?.name, account.username) || account.username,
1751
+ username: account.username,
1752
+ email: email || account.username,
1753
+ tenantId: account.tenantId,
1754
+ roles: pickRoles(claims),
1755
+ claims
1756
+ };
1757
+ }
1758
+ function isMsalAccountInfoLike(value) {
1759
+ if (!value || typeof value !== "object") {
1760
+ return false;
1761
+ }
1762
+ const candidate = value;
1763
+ return typeof candidate.homeAccountId === "string" && typeof candidate.localAccountId === "string" && typeof candidate.username === "string";
1764
+ }
1765
+ function dedupeScopes(scopes) {
1766
+ return [...new Set(scopes.filter((scope) => scope.length > 0))];
1767
+ }
1768
+ function resolveScopes(input, defaults) {
1769
+ if (input && input.length > 0) {
1770
+ return dedupeScopes(input);
1771
+ }
1772
+ return dedupeScopes(defaults);
1773
+ }
1774
+ function toUserAccessToken(response, fallbackScopes) {
1775
+ return {
1776
+ token: response.accessToken,
1777
+ tokenType: response.tokenType,
1778
+ scopes: response.scopes && response.scopes.length > 0 ? response.scopes : fallbackScopes,
1779
+ expiresAt: response.expiresOn ? response.expiresOn.getTime() : void 0
1780
+ };
1781
+ }
1782
+ function createMsalUserService(options) {
1783
+ const {
1784
+ msal,
1785
+ defaultScopes = [],
1786
+ loginMode = "popup",
1787
+ logoutMode = "popup",
1788
+ mapUser = defaultMapUser,
1789
+ resolveAccount = (accounts) => accounts[0] ?? null
1790
+ } = options;
1791
+ const listeners = /* @__PURE__ */ new Set();
1792
+ let callbackId = null;
1793
+ let cachedIdToken;
1794
+ let cachedIdTokenClaims;
1795
+ function emitChanged() {
1796
+ for (const listener of listeners) {
1797
+ listener();
1798
+ }
1799
+ }
1800
+ function normalizeActiveAccount() {
1801
+ const active = msal.getActiveAccount();
1802
+ if (active) {
1803
+ return active;
1804
+ }
1805
+ const fallback = resolveAccount(msal.getAllAccounts());
1806
+ if (fallback && msal.setActiveAccount) {
1807
+ msal.setActiveAccount(fallback);
1808
+ }
1809
+ return fallback ?? null;
1810
+ }
1811
+ function updateSessionFromResponse(response) {
1812
+ const account = response?.account;
1813
+ if (account && msal.setActiveAccount) {
1814
+ msal.setActiveAccount(account);
1815
+ }
1816
+ if (response?.idToken) {
1817
+ cachedIdToken = response.idToken;
1818
+ }
1819
+ const claims = toClaims(response?.idTokenClaims);
1820
+ if (claims) {
1821
+ cachedIdTokenClaims = claims;
1822
+ }
1823
+ }
1824
+ function resolveAccountById(accountId) {
1825
+ for (const account of msal.getAllAccounts()) {
1826
+ if (account.homeAccountId === accountId || account.localAccountId === accountId) {
1827
+ return account;
1828
+ }
1829
+ }
1830
+ return null;
1831
+ }
1832
+ function attachMsalCallback() {
1833
+ if (callbackId || !msal.addEventCallback) {
1834
+ return;
1835
+ }
1836
+ callbackId = msal.addEventCallback((message) => {
1837
+ const payload = message.payload;
1838
+ if (payload && typeof payload === "object") {
1839
+ const account = payload.account;
1840
+ if (isMsalAccountInfoLike(account) && msal.setActiveAccount) {
1841
+ msal.setActiveAccount(account);
1842
+ }
1843
+ }
1844
+ emitChanged();
1845
+ });
1846
+ }
1847
+ function detachMsalCallback() {
1848
+ if (!callbackId || !msal.removeEventCallback) {
1849
+ callbackId = null;
1850
+ return;
1851
+ }
1852
+ msal.removeEventCallback(callbackId);
1853
+ callbackId = null;
1854
+ }
1855
+ async function acquireAccessToken(request) {
1856
+ const scopes = resolveScopes(request.scopes, defaultScopes);
1857
+ if (scopes.length === 0) {
1858
+ throw new Error("acquireAccessToken requires at least one scope.");
1859
+ }
1860
+ const account = normalizeActiveAccount();
1861
+ if (!account) {
1862
+ return null;
1863
+ }
1864
+ try {
1865
+ const silentResponse = await msal.acquireTokenSilent({
1866
+ scopes,
1867
+ account,
1868
+ forceRefresh: request.forceRefresh,
1869
+ claims: request.claims
1870
+ });
1871
+ updateSessionFromResponse(silentResponse);
1872
+ emitChanged();
1873
+ return toUserAccessToken(silentResponse, scopes);
1874
+ } catch (error) {
1875
+ if (!msal.acquireTokenPopup) {
1876
+ throw error;
1877
+ }
1878
+ const popupResponse = await msal.acquireTokenPopup({
1879
+ scopes,
1880
+ claims: request.claims,
1881
+ account,
1882
+ loginHint: account.username
1883
+ });
1884
+ updateSessionFromResponse(popupResponse);
1885
+ emitChanged();
1886
+ return toUserAccessToken(popupResponse, scopes);
1887
+ }
1888
+ }
1889
+ return {
1890
+ getSnapshot: () => {
1891
+ const account = normalizeActiveAccount();
1892
+ return account ? mapUser(account) : null;
1893
+ },
1894
+ getSession: () => {
1895
+ const account = normalizeActiveAccount();
1896
+ return {
1897
+ state: account ? "authenticated" : "unauthenticated",
1898
+ provider: "msal",
1899
+ account: account ?? void 0,
1900
+ idToken: cachedIdToken,
1901
+ idTokenClaims: cachedIdTokenClaims ?? toClaims(account?.idTokenClaims)
1902
+ };
1903
+ },
1904
+ acquireAccessToken,
1905
+ login: async (loginOptions) => {
1906
+ const scopes = resolveScopes(loginOptions?.scopes, defaultScopes);
1907
+ const request = {
1908
+ scopes: scopes.length > 0 ? scopes : void 0,
1909
+ loginHint: loginOptions?.loginHint,
1910
+ prompt: loginOptions?.prompt
1911
+ };
1912
+ if (loginMode === "redirect") {
1913
+ if (!msal.loginRedirect) {
1914
+ throw new Error("MSAL loginRedirect is not available.");
1915
+ }
1916
+ await msal.loginRedirect(request);
1917
+ return;
1918
+ }
1919
+ if (!msal.loginPopup) {
1920
+ throw new Error("MSAL loginPopup is not available.");
1921
+ }
1922
+ const loginResponse = await msal.loginPopup(request);
1923
+ updateSessionFromResponse(loginResponse);
1924
+ emitChanged();
1925
+ },
1926
+ logout: async (logoutOptions) => {
1927
+ const account = logoutOptions?.accountId ? resolveAccountById(logoutOptions.accountId) : normalizeActiveAccount();
1928
+ const request = {
1929
+ account: account ?? void 0,
1930
+ postLogoutRedirectUri: logoutOptions?.postLogoutRedirectUri
1931
+ };
1932
+ if (logoutMode === "redirect") {
1933
+ if (!msal.logoutRedirect) {
1934
+ throw new Error("MSAL logoutRedirect is not available.");
1935
+ }
1936
+ await msal.logoutRedirect(request);
1937
+ return;
1938
+ }
1939
+ if (!msal.logoutPopup) {
1940
+ throw new Error("MSAL logoutPopup is not available.");
1941
+ }
1942
+ await msal.logoutPopup(request);
1943
+ if (msal.setActiveAccount) {
1944
+ msal.setActiveAccount(null);
1945
+ }
1946
+ cachedIdToken = void 0;
1947
+ cachedIdTokenClaims = void 0;
1948
+ emitChanged();
1949
+ },
1950
+ subscribe: (listener) => {
1951
+ listeners.add(listener);
1952
+ if (listeners.size === 1) {
1953
+ attachMsalCallback();
1954
+ }
1955
+ return () => {
1956
+ listeners.delete(listener);
1957
+ if (listeners.size === 0) {
1958
+ detachMsalCallback();
1959
+ }
1960
+ };
1961
+ }
1962
+ };
1963
+ }
1964
+
1965
+ // src/services/reactRouterNavigationAdapter.ts
1966
+ function createReactRouterNavigationAdapter(options = {}) {
1967
+ const listeners = /* @__PURE__ */ new Set();
1968
+ let navigateFn;
1969
+ let createHrefFn;
1970
+ let currentIndex = resolveWindowHistoryIndex() ?? 0;
1971
+ let maxIndex = currentIndex;
1972
+ const initialLocation = normalizeLocation(options.initialLocation ?? { pathname: "/" });
1973
+ let snapshot = {
1974
+ action: options.initialAction ?? "POP",
1975
+ location: initialLocation,
1976
+ canGoBack: currentIndex > 0,
1977
+ canGoForward: false
1978
+ };
1979
+ function emitChange() {
1980
+ for (const listener of listeners) {
1981
+ listener();
1982
+ }
1983
+ }
1984
+ function setSnapshot(nextSnapshot) {
1985
+ if (!hasSnapshotChanged(snapshot, nextSnapshot)) {
1986
+ return;
1987
+ }
1988
+ snapshot = nextSnapshot;
1989
+ emitChange();
1990
+ }
1991
+ function createFallbackHref(to) {
1992
+ return resolveToHref(to, snapshot.location);
1993
+ }
1994
+ const service = {
1995
+ getSnapshot: () => ({
1996
+ action: snapshot.action,
1997
+ location: { ...snapshot.location },
1998
+ canGoBack: snapshot.canGoBack,
1999
+ canGoForward: snapshot.canGoForward
2000
+ }),
2001
+ getPath: () => snapshot.location.pathname,
2002
+ createHref: (to) => {
2003
+ if (createHrefFn) {
2004
+ try {
2005
+ return createHrefFn(to);
2006
+ } catch {
2007
+ return createFallbackHref(to);
2008
+ }
2009
+ }
2010
+ return createFallbackHref(to);
2011
+ },
2012
+ navigate: (to, optionsArg) => {
2013
+ if (!navigateFn) {
2014
+ return;
2015
+ }
2016
+ if (typeof to === "number") {
2017
+ navigateFn(to);
2018
+ return;
2019
+ }
2020
+ const { source: _source, ...routerOptions } = optionsArg ?? {};
2021
+ navigateFn(to, routerOptions);
2022
+ },
2023
+ back: () => {
2024
+ service.navigate(-1);
2025
+ },
2026
+ forward: () => {
2027
+ service.navigate(1);
2028
+ },
2029
+ subscribe: (listener) => {
2030
+ listeners.add(listener);
2031
+ return () => listeners.delete(listener);
2032
+ }
2033
+ };
2034
+ return {
2035
+ service,
2036
+ bindNavigator: (navigate, createHref) => {
2037
+ navigateFn = navigate;
2038
+ createHrefFn = createHref;
2039
+ },
2040
+ clearNavigator: () => {
2041
+ navigateFn = void 0;
2042
+ createHrefFn = void 0;
2043
+ },
2044
+ syncFromRouter: (location, action, capabilities) => {
2045
+ const normalizedLocation = normalizeLocation(location);
2046
+ const runtimeIndex = capabilities?.historyIndex ?? resolveWindowHistoryIndex();
2047
+ if (typeof runtimeIndex === "number" && Number.isInteger(runtimeIndex)) {
2048
+ currentIndex = runtimeIndex;
2049
+ if (runtimeIndex > maxIndex) {
2050
+ maxIndex = runtimeIndex;
2051
+ }
2052
+ } else if (action === "PUSH") {
2053
+ currentIndex += 1;
2054
+ maxIndex = currentIndex;
2055
+ } else if (action === "POP" && currentIndex > 0) {
2056
+ currentIndex -= 1;
2057
+ }
2058
+ const canGoBack = capabilities?.canGoBack ?? currentIndex > 0;
2059
+ const canGoForward = capabilities?.canGoForward ?? currentIndex < maxIndex;
2060
+ setSnapshot({
2061
+ action,
2062
+ location: normalizedLocation,
2063
+ canGoBack,
2064
+ canGoForward
2065
+ });
2066
+ }
2067
+ };
2068
+ }
2069
+ function resolveWindowHistoryIndex() {
2070
+ if (typeof window === "undefined" || typeof window.history === "undefined") {
2071
+ return void 0;
2072
+ }
2073
+ const historyState = window.history.state;
2074
+ if (!historyState || typeof historyState.idx !== "number") {
2075
+ return void 0;
2076
+ }
2077
+ const idx = historyState.idx;
2078
+ if (!Number.isInteger(idx) || idx < 0) {
2079
+ return void 0;
2080
+ }
2081
+ return idx;
2082
+ }
2083
+ function normalizeLocation(input) {
2084
+ return {
2085
+ pathname: input.pathname || "/",
2086
+ search: normalizeSearch2(input.search ?? ""),
2087
+ hash: normalizeHash2(input.hash ?? ""),
2088
+ state: input.state,
2089
+ key: input.key || createLocationKey2()
2090
+ };
2091
+ }
2092
+ function createLocationKey2() {
2093
+ return Math.random().toString(36).slice(2, 10);
2094
+ }
2095
+ function normalizeSearch2(search) {
2096
+ if (!search) {
2097
+ return "";
2098
+ }
2099
+ return search.startsWith("?") ? search : `?${search}`;
2100
+ }
2101
+ function normalizeHash2(hash) {
2102
+ if (!hash) {
2103
+ return "";
2104
+ }
2105
+ return hash.startsWith("#") ? hash : `#${hash}`;
2106
+ }
2107
+ function resolveToHref(to, current) {
2108
+ if (typeof to === "string") {
2109
+ return to;
2110
+ }
2111
+ const pathname = to.pathname ?? current.pathname;
2112
+ const search = to.search !== void 0 ? normalizeSearch2(to.search) : current.search;
2113
+ const hash = to.hash !== void 0 ? normalizeHash2(to.hash) : current.hash;
2114
+ return `${pathname || "/"}${search}${hash}`;
2115
+ }
2116
+ function hasSnapshotChanged(previous, nextValue) {
2117
+ const prevLocation = previous.location;
2118
+ const nextLocation = nextValue.location;
2119
+ return previous.action !== nextValue.action || previous.canGoBack !== nextValue.canGoBack || previous.canGoForward !== nextValue.canGoForward || prevLocation.pathname !== nextLocation.pathname || prevLocation.search !== nextLocation.search || prevLocation.hash !== nextLocation.hash || prevLocation.key !== nextLocation.key || !Object.is(prevLocation.state, nextLocation.state);
2120
+ }
487
2121
  export {
2122
+ ApiError,
2123
+ createMsalUserService,
2124
+ createReactRouterNavigationAdapter,
2125
+ createRequestContextService,
488
2126
  createStandaloneApiClient,
489
2127
  createStandaloneI18nService,
2128
+ createStandaloneRealtimeService,
490
2129
  createStandaloneUserService,
491
- initStandaloneServices
2130
+ initStandaloneServices,
2131
+ isApiError,
2132
+ mapApiErrorToUiError
492
2133
  };