@geolonia/geonicdb-sdk 0.19.2 → 0.19.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/README.md +36 -20
- package/geonicdb.cjs +264 -7
- package/geonicdb.cjs.map +2 -2
- package/geonicdb.d.ts +131 -16
- package/geonicdb.iife.js +264 -7
- package/geonicdb.iife.js.map +2 -2
- package/geonicdb.mjs +264 -7
- package/geonicdb.mjs.map +2 -2
- package/package.json +1 -1
- package/react/index.d.ts +109 -7
package/geonicdb.cjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/sdk/index.ts", "../../src/sdk/events.ts", "../../src/sdk/constants.ts", "../../src/sdk/dpop.ts", "../../src/sdk/pow.ts", "../../src/sdk/errors.ts", "../../src/sdk/cache.ts", "../../src/sdk/auth.ts", "../../src/sdk/websocket.ts", "../../src/sdk/cache-persistence.ts", "../../src/sdk/webpush.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\n/**\n * GeonicDB JavaScript SDK\n *\n * NGSI-LD Context Broker client for browser and Node.js.\n * Handles API key auth (DPoP + PoW), Bearer JWT, entity CRUD,\n * WebSocket event streaming, and automatic reconnection.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\ndeclare const document: any;\ndeclare const window: any;\ndeclare const navigator: any;\n\nimport { EventEmitter } from './events';\nimport { AuthManager } from './auth';\nimport { WebSocketManager } from './websocket';\nimport { createErrorFromResponse } from './errors';\nimport { SdkCache } from './cache';\nimport { createCachePersistence } from './cache-persistence';\nimport {\n SDK_CACHE_MAX_ENTRIES_DEFAULT,\n SDK_POLL_INTERVAL_MS_DEFAULT,\n WEBPUSH_VAPID_KEY_PATH,\n} from './constants';\nimport { urlBase64ToUint8Array, matchesApplicationServerKey } from './webpush';\nimport type { WebPushSubscribeOptions } from './webpush';\nimport type {\n GeonicDBOptions,\n CredentialsOptions,\n CountEntitiesParams,\n GetEntitiesParams,\n GetTemporalEntitiesParams,\n EntityTypeList,\n AttributeList,\n LoginOptions,\n PollHandle,\n PollOptions,\n SubscribeOptions,\n LoginResponse,\n RefreshedCredentials,\n} from './types';\n\n// Re-export types for consumers\nexport type {\n GeonicDBOptions,\n CredentialsOptions,\n RefreshedCredentials,\n CountEntitiesParams,\n GetEntitiesParams,\n GetTemporalEntitiesParams,\n EntityTypeList,\n AttributeList,\n LoginOptions,\n SubscribeOptions,\n EntityEvent,\n ReconnectingEvent,\n LoginResponse,\n GeonicDBEventMap,\n CacheEvent,\n PollOptions,\n PollHandle,\n} from './types';\n\n// Re-export Web Push helpers/types (#3014 / #3060)\nexport { urlBase64ToUint8Array, toNgsiv2WebPushNotification, toNgsiLdWebPushEndpoint } from './webpush';\nexport type { WebPushKeys, WebPushDeliveryOptions, WebPushSubscribeOptions } from './webpush';\n\n// Re-export error classes for consumers\nexport {\n GeonicDBError,\n AuthenticationError,\n AuthorizationError,\n NotFoundError,\n ConflictError,\n ValidationError,\n RateLimitError,\n NetworkError,\n PasswordResetRequiredError,\n TemporaryPasswordExpiredError,\n} from './errors';\nexport type { RequestContext } from './errors';\n\n/**\n * GeonicDB SDK client.\n *\n * Handles API key auth (DPoP + PoW), Bearer JWT, entity CRUD,\n * WebSocket event streaming, and automatic reconnection.\n *\n * @example\n * ```typescript\n * import GeonicDB from '@geolonia/geonicdb-sdk';\n *\n * const db = new GeonicDB({\n * apiKey: 'your-api-key',\n * tenant: 'your-tenant',\n * baseUrl: 'https://your-geonicdb.example.com',\n * });\n *\n * const entities = await db.getEntities({ type: 'Room' });\n * ```\n */\nexport class GeonicDB extends EventEmitter {\n private _auth: AuthManager;\n private _ws: WebSocketManager;\n\n /**\n * Legacy callback for token refresh events.\n * Prefer `db.on('tokenRefresh', callback)` for new code.\n * @deprecated Use `db.on('tokenRefresh', callback)` instead.\n */\n onTokenRefresh: ((creds: RefreshedCredentials) => void) | null = null;\n\n constructor(options?: GeonicDBOptions) {\n super();\n const opts = options || {};\n\n // Auto-detect from script tag in browser\n let apiKey = opts.apiKey || '';\n let tenant = opts.tenant || '';\n let baseUrl = opts.baseUrl || '';\n\n /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */\n /* istanbul ignore next: document.currentScript is only populated when the\n SDK bundle is loaded via a <script> tag in a real browser; jsdom does\n not expose it for the test runner. Covered by E2E / manual browser tests. */\n if (typeof document !== 'undefined' && document.currentScript) {\n const script = document.currentScript;\n if (!apiKey) apiKey = script?.getAttribute?.('data-api-key') || '';\n if (!tenant) tenant = script?.getAttribute?.('data-tenant') || '';\n if (!baseUrl) baseUrl = script?.getAttribute?.('data-base-url') || '';\n }\n /* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */\n\n // Request paths are concatenated onto the base URL verbatim, so a trailing\n // slash would produce `//auth/login`. The server routes that outside the\n // auth-exempt prefix, so the policy engine answers\n // `403 Access denied: no applicable policy` \u2014 a permissions error for what\n // is only a malformed URL. `geonic config` stores the base URL with a\n // trailing slash, which makes this easy to hit when copying that value.\n baseUrl = baseUrl.replace(/\\/+$/, '');\n\n this._auth = new AuthManager(baseUrl, apiKey, tenant, opts.debug, opts.anonymous);\n this._auth.onTokenRefresh = (creds): void => {\n this.onTokenRefresh?.(creds);\n this.emit('tokenRefresh', creds);\n };\n\n // #1043 Phase A: client-side cache. Enabled by default. Pass `cache: false` to opt out.\n if (opts.cache !== false) {\n const cache = new SdkCache(opts.cacheMaxEntries ?? SDK_CACHE_MAX_ENTRIES_DEFAULT);\n // #1262: persist cached validators across reloads in browsers (IndexedDB).\n // Returns null in Node-like runtimes \u2014 the cache then stays in-memory.\n const persistence = createCachePersistence();\n if (persistence) cache.attachPersistence(persistence);\n this._auth.setCache(cache);\n this._auth.setCacheEventEmitter((name, payload) => this.emit(name, payload));\n }\n\n this._ws = new WebSocketManager(\n this._auth,\n baseUrl,\n tenant,\n (event, data) => {\n this.emit(event, data);\n // \u6CE8: \u65E7\u5B9F\u88C5\u3067\u306F entityCreated / entityUpdated / entityDeleted \u3067\n // \u30AD\u30E3\u30C3\u30B7\u30E5\u30A8\u30F3\u30C8\u30EA\u3092\u524A\u9664\u3057\u3066\u3044\u305F\u304C\u3001`Cache-Control: private, no-cache`\n // \u306B\u3088\u308A\u6BCE\u8AAD\u307F\u53D6\u308A\u3067 `If-None-Match` revalidation \u304C\u8D70\u308B\u8A2D\u8A08\u306E\u305F\u3081\u3001\n // \u524A\u9664\u3059\u308B\u3068 ETag \u3092\u5931\u3063\u3066\u6BCE\u56DE full 200 \u306B\u306A\u308A 304 \u5E2F\u57DF\u7BC0\u7D04\u304C\u7121\u52B9\u5316\u3055\u308C\u3066\u3044\u305F\n // (incident 2026-04-28 \u3067 pulse \u306E\u4F53\u611F\u901F\u5EA6\u52A3\u5316\u3068\u3057\u3066\u9855\u5728\u5316)\u3002\n // \u30AD\u30E3\u30C3\u30B7\u30E5\u30A8\u30F3\u30C8\u30EA\u306F\u4FDD\u6301\u3057\u3001\u30B5\u30FC\u30D0\u5074\u306E ETag \u30DE\u30C3\u30C1\u3067\u81EA\u52D5\u7684\u306B\n // 304 (=fresh cache hit) / 200 (=\u672C\u5F53\u306B\u5909\u66F4\u3042\u308A\u306E\u307F full body) \u304C\n // \u632F\u308A\u5206\u3051\u3089\u308C\u308B\u3002\u610F\u56F3\u7684\u306A\u5168\u6D88\u53BB\u306F `clearCache()` \u3092\u4F7F\u3046\u3053\u3068\u3002\n },\n opts.wsEndpoint,\n );\n }\n\n /**\n * Drop every cached response. Useful in tests and on manual auth changes.\n *\n * Emits a `cacheInvalidated` event for each removed entry so listeners can\n * track explicit flushes (the WebSocket-driven auto-invalidation was removed\n * in #1060 to preserve the ETag/304 revalidation path; `clearCache()` is now\n * the only path that emits this event).\n */\n clearCache(): void {\n const cache = this._auth.getCache();\n if (!cache) return;\n const removed = cache.deleteWhere(() => true);\n for (const key of removed) {\n this._auth.emitCacheEvent('cacheInvalidated', { key, path: key.split(':').slice(1).join(':') });\n }\n // #1262: also flush the persisted mirror so a reload does not resurrect\n // entries the consumer explicitly dropped. Best-effort, fire-and-forget.\n void cache.clearPersisted();\n }\n\n // --- Authentication ---\n\n /**\n * Login with email and password.\n *\n * The session is sender-constrained with DPoP (RFC 9449) automatically and\n * transparently \u2014 there is no DPoP option to set. A token leaked from\n * storage (e.g. localStorage XSS) is therefore unusable from another origin.\n * On runtimes without Web Crypto, the SDK silently keeps a plain Bearer\n * session and login still succeeds.\n *\n * @example\n * ```ts\n * // Standard login \u2014 DPoP applied transparently when supported.\n * await db.login('user@example.com', 'password');\n *\n * // Keep the user signed in across page reloads (browser SPA UX).\n * await db.login('user@example.com', 'password', { rememberSession: true });\n * ```\n */\n async login(\n email: string,\n password: string,\n options?: LoginOptions,\n ): Promise<LoginResponse> {\n return this._auth.login(email, password, options);\n }\n\n /**\n * Rehydrate a session previously persisted via\n * `login(email, password, { rememberSession: true })`.\n *\n * Returns true if a session for this tenant was found and loaded; false\n * if there was no entry, IndexedDB is unavailable, or the persisted\n * session was malformed. Call this at app startup before deciding\n * whether to render the login form.\n *\n * @example\n * ```ts\n * const db = new GeonicDB({ baseUrl, tenant });\n * if (await db.restoreSession()) {\n * // Session (tokens + non-extractable key) recovered \u2014 boot the app.\n * } else {\n * // No session; show the login form.\n * }\n * ```\n */\n async restoreSession(): Promise<boolean> {\n return this._auth.restoreSession();\n }\n\n /**\n * Whether the SDK is currently operating in anonymous mode (no token held).\n * Returns true only when `anonymous: true` was passed to the constructor\n * AND no credentials have been set via `login()` / `setCredentials()`.\n */\n isAnonymous(): boolean {\n return this._auth.isAnonymous();\n }\n\n /**\n * Set credentials externally (e.g. from a login API response).\n * When tokenType is 'Bearer' with a refreshToken, DPoP/PoW is bypassed entirely.\n */\n setCredentials(opts: CredentialsOptions): this {\n this._auth.setCredentials(opts);\n return this;\n }\n\n /** Clear all credentials and disconnect. */\n logout(): void {\n this._ws.disconnect();\n this._auth.logout();\n }\n\n // --- Entity CRUD (NGSI-LD) ---\n\n /** Create a new entity. */\n async createEntity(\n entity: Record<string, unknown>,\n ): Promise<{ created: true }> {\n const res = await this._auth.request('POST', '/ngsi-ld/v1/entities', entity);\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n throw createErrorFromResponse(res.status, e, 'Create failed');\n }\n return { created: true };\n }\n\n /** Get a single entity by ID. */\n async getEntity(entityId: string): Promise<Record<string, unknown>> {\n const res = await this._auth.request(\n 'GET',\n '/ngsi-ld/v1/entities/' + encodeURIComponent(entityId),\n );\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n throw createErrorFromResponse(res.status, e, 'Not found');\n }\n return (await res.json()) as Record<string, unknown>;\n }\n\n /** Query entities with optional filters. */\n async getEntities(\n params?: GetEntitiesParams,\n ): Promise<Record<string, unknown>[]> {\n const res = await this._auth.request('GET', buildEntitiesPath(params));\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n throw createErrorFromResponse(res.status, e, 'Query failed');\n }\n return (await res.json()) as Record<string, unknown>[];\n }\n\n /**\n * Poll for entity list changes using ETag-based revalidation (#991 Phase A).\n *\n * The handle's `stop()` ends the loop. Each tick performs a normal\n * `getEntities()` call; the SDK's cache layer issues `If-None-Match`\n * automatically. The poll detects \"no change\" by comparing the previous\n * ETag with the response's ETag (the cache exposes the cached ETag on 304\n * replays as well, so the comparison stays valid in both 200 and 304\n * paths).\n *\n * @example\n * ```typescript\n * const handle = db.poll({ type: 'Room' }, {\n * interval: 5000,\n * onData: (rooms) => render(rooms),\n * onNoChange: () => {},\n * });\n * // ...later\n * handle.stop();\n * ```\n */\n poll(\n params: GetEntitiesParams | undefined,\n options: PollOptions<Record<string, unknown>[]>,\n ): PollHandle {\n const interval = options.interval ?? SDK_POLL_INTERVAL_MS_DEFAULT;\n if (!Number.isFinite(interval) || interval <= 0) {\n throw new Error(`poll interval must be a positive number (got ${String(interval)})`);\n }\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let prevEtag: string | undefined;\n let prevPath: string | undefined;\n\n const buildPath = (): string => buildEntitiesPath(params);\n\n const tick = async (): Promise<void> => {\n if (stopped) return;\n try {\n const path = (prevPath ??= buildPath());\n const res = await this._auth.request('GET', path);\n if (stopped) return;\n const etag = res.headers.get('etag') ?? undefined;\n if (prevEtag !== undefined && etag !== undefined && prevEtag === etag) {\n options.onNoChange?.();\n } else {\n if (!res.ok) {\n throw createErrorFromResponse(res.status, {}, 'Poll request failed');\n }\n const data = (await res.json()) as Record<string, unknown>[];\n prevEtag = etag;\n options.onData?.(data);\n }\n } catch (err) {\n options.onError?.(err);\n } finally {\n if (!stopped) {\n timer = setTimeout(() => { void tick(); }, interval);\n }\n }\n };\n\n void tick();\n\n return {\n stop(): void {\n stopped = true;\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n },\n };\n }\n\n /** Count entities matching the given filters. */\n async count(\n params?: CountEntitiesParams,\n ): Promise<number> {\n // #1132: \u5171\u901A\u30D3\u30EB\u30C0\u7D4C\u7531\u3067\u3001CountEntitiesParams \u306B\u8FFD\u52A0\u3055\u308C\u305F\u30D5\u30A3\u30FC\u30EB\u30C9\n // (scopeQ \u306A\u3069) \u3092\u81EA\u52D5\u3067 URL \u306B\u4E57\u305B\u308B\n const path = buildPathWithParams('/ngsi-ld/v1/entities', {\n count: true,\n limit: 0,\n type: params?.type,\n q: params?.q,\n scopeQ: params?.scopeQ,\n // #2278: clause 5.7.2.4 \u306E \"too wide query\" \u514D\u9664\u6761\u4EF6 (e) local scope\u3002\n local: params?.local,\n });\n const res = await this._auth.request('GET', path);\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n throw createErrorFromResponse(res.status, e, 'Count failed');\n }\n const countHeader = res.headers.get('NGSILD-Results-Count');\n return countHeader ? parseInt(countHeader, 10) : 0;\n }\n\n /** Update entity attributes (partial). */\n async updateEntity(\n entityId: string,\n attrs: Record<string, unknown>,\n ): Promise<{ updated: true }> {\n const res = await this._auth.request(\n 'PATCH',\n '/ngsi-ld/v1/entities/' + encodeURIComponent(entityId) + '/attrs',\n attrs,\n );\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n throw createErrorFromResponse(res.status, e, 'Update failed');\n }\n return { updated: true };\n }\n\n /** Delete an entity. */\n async deleteEntity(entityId: string): Promise<{ deleted: true }> {\n const res = await this._auth.request(\n 'DELETE',\n '/ngsi-ld/v1/entities/' + encodeURIComponent(entityId),\n );\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n throw createErrorFromResponse(res.status, e, 'Delete failed');\n }\n return { deleted: true };\n }\n\n // --- Types / Attributes Discovery ---\n\n /**\n * List all entity types.\n *\n * `details` \u672A\u6307\u5B9A\u306E `GET /types` \u306F ETSI OpenAPI v1.8.1 \u6E96\u62E0\u306E\n * `EntityTypeList` \u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\uFF08`{ id, type: 'EntityTypeList', typeList }`\uFF09\u3092\u8FD4\u3059\u3002\n * \u578B\u540D\u306E\u4E00\u89A7\u306F `typeList` \u304B\u3089\u53D6\u5F97\u3059\u308B\u3002\n */\n async getTypes(): Promise<EntityTypeList> {\n return this._jsonGet('/ngsi-ld/v1/types', 'Types query failed');\n }\n\n /** Get details for a specific entity type. */\n async getType(typeName: string): Promise<Record<string, unknown>> {\n return this._jsonGet(\n '/ngsi-ld/v1/types/' + encodeURIComponent(typeName),\n 'Type not found',\n );\n }\n\n /**\n * List all attributes.\n *\n * `details` \u672A\u6307\u5B9A\u306E `GET /attributes` \u306F ETSI OpenAPI v1.8.1 \u6E96\u62E0\u306E\n * `AttributeList` \u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\uFF08`{ id, type: 'AttributeList', attributeList }`\uFF09\u3092\u8FD4\u3059\u3002\n * \u5C5E\u6027\u540D\u306E\u4E00\u89A7\u306F `attributeList` \u304B\u3089\u53D6\u5F97\u3059\u308B\u3002\n */\n async getAttributes(): Promise<AttributeList> {\n return this._jsonGet('/ngsi-ld/v1/attributes', 'Attributes query failed');\n }\n\n /** Get details for a specific attribute. */\n async getAttribute(attrName: string): Promise<Record<string, unknown>> {\n return this._jsonGet(\n '/ngsi-ld/v1/attributes/' + encodeURIComponent(attrName),\n 'Attribute not found',\n );\n }\n\n // --- Temporal API ---\n\n /** Query temporal entities. */\n async getTemporalEntities(\n params?: GetTemporalEntitiesParams,\n ): Promise<Record<string, unknown>[]> {\n // #1132: \u5171\u901A\u30D3\u30EB\u30C0\u7D4C\u7531\u3002GetTemporalEntitiesParams \u306B\u8FFD\u52A0\u3055\u308C\u305F\u30D5\u30A3\u30FC\u30EB\u30C9\n // (scopeQ \u306A\u3069) \u3092\u81EA\u52D5\u3067 URL \u306B\u4E57\u305B\u308B\n return this._jsonGet(\n buildPathWithParams(\n '/ngsi-ld/v1/temporal/entities',\n params as Record<string, unknown> | undefined,\n ),\n 'Temporal query failed',\n );\n }\n\n /** Get temporal representation of a single entity. */\n async getTemporalEntity(entityId: string): Promise<Record<string, unknown>> {\n return this._jsonGet(\n '/ngsi-ld/v1/temporal/entities/' + encodeURIComponent(entityId),\n 'Temporal entity not found',\n );\n }\n\n // --- Batch Operations ---\n\n /** Create multiple entities in a single request. */\n async batchCreate(\n entities: Record<string, unknown>[],\n ): Promise<Record<string, unknown>> {\n return this._jsonPost(\n '/ngsi-ld/v1/entityOperations/create',\n entities,\n 'Batch create failed',\n );\n }\n\n /**\n * Upsert multiple entities in a single request.\n *\n * #1742: the server follows ETSI GS CIM 009 clause 5.6.8.5 / Table 6.15.3.1-1:\n * - **201** with a `string[]` of the **created** entity IDs, when at least one entity\n * was newly created and none failed (merely updated entities are not listed);\n * - **204** with no body, when every entity already existed and was updated\n * (`_jsonPost` maps this to `{}`);\n * - **207** with a `BatchOperationResult` (`{ created, updated, errors }`), on partial\n * or total failure.\n *\n * The return type reflects all three shapes. Narrow with `Array.isArray(result)` to\n * detect the \"something was created\" case.\n * https://cim.etsi.org/NGSI-LD/official/clause-6.html\n */\n async batchUpsert(\n entities: Record<string, unknown>[],\n ): Promise<string[] | Record<string, unknown>> {\n return this._jsonPost(\n '/ngsi-ld/v1/entityOperations/upsert',\n entities,\n 'Batch upsert failed',\n );\n }\n\n /** Update multiple entities in a single request. */\n async batchUpdate(\n entities: Record<string, unknown>[],\n ): Promise<Record<string, unknown>> {\n return this._jsonPost(\n '/ngsi-ld/v1/entityOperations/update',\n entities,\n 'Batch update failed',\n );\n }\n\n /** Delete multiple entities by ID. */\n async batchDelete(entityIds: string[]): Promise<Record<string, unknown>> {\n return this._jsonPost(\n '/ngsi-ld/v1/entityOperations/delete',\n entityIds,\n 'Batch delete failed',\n );\n }\n\n // --- Internal helpers ---\n\n /** GET request that returns parsed JSON or throws. */\n private async _jsonGet<T>(path: string, fallbackError: string): Promise<T> {\n const res = await this._auth.request('GET', path);\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n throw createErrorFromResponse(res.status, e, fallbackError);\n }\n return (await res.json()) as T;\n }\n\n /** POST request that returns parsed JSON or throws. */\n private async _jsonPost<T>(\n path: string,\n body: unknown,\n fallbackError: string,\n ): Promise<T> {\n const res = await this._auth.request('POST', path, body);\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n throw createErrorFromResponse(res.status, e, fallbackError);\n }\n if (res.status === 204) return {} as T;\n return (await res.json()) as T;\n }\n\n // --- Generic Request ---\n\n /**\n * Make an authenticated API request.\n * Automatically checks response status, parses JSON, and throws on error.\n *\n * Empty bodies (`Content-Length: 0` \u542B\u3080) \u306F Content-Type \u306B\u95A2\u4FC2\u306A\u304F `null`\n * \u3092\u8FD4\u3059 (#1145)\u3002NGSI-LD \u306E POST / DELETE / PATCH \u7CFB\u306F\u4ED5\u69D8\u4E0A\u30DC\u30C7\u30A3\u304C\n * \u7A7A\u3060\u304C\u3001\u30B5\u30FC\u30D0\u5B9F\u88C5\u304C `Content-Type: application/ld+json` \u3092\u4ED8\u3051\u3066\u8FD4\u3059\n * \u30B1\u30FC\u30B9\u304C\u3042\u308A\u3001\u305D\u3053\u3067 `res.json()` \u304C SyntaxError \u3092\u6295\u3052\u3066\u6B62\u307E\u3063\u3066\u3044\u305F\u3002\n */\n async request(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<unknown> {\n const res = await this._auth.request(method, path, body);\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n throw createErrorFromResponse(res.status, e, 'Request failed: ' + res.status);\n }\n if (res.status === 204) return null;\n if (res.headers.get('Content-Length') === '0') return null;\n const ct = res.headers.get('Content-Type') || '';\n if (!ct) return null;\n if (ct.indexOf('json') !== -1) {\n // \u30DC\u30C7\u30A3\u304C\u7A7A\u306E\u3068\u304D\u306B res.json() \u306F SyntaxError \u3092\u6295\u3052\u308B\u305F\u3081\u3001\u307E\u305A text() \u3067\u8AAD\u307F\u3001\n // \u7A7A\u6587\u5B57\u306A\u3089 null \u3092\u8FD4\u3059\u3002\n const text = await res.text();\n if (!text) return null;\n return JSON.parse(text) as unknown;\n }\n return res.text();\n }\n\n /**\n * Make an authenticated API request and return the raw Response object.\n * Use this when you need access to response headers (e.g. NGSILD-Results-Count).\n */\n async requestRaw(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<Response> {\n return this._auth.request(method, path, body);\n }\n\n // --- WebSocket ---\n\n /** Establish WebSocket connection (authentication is automatic). */\n async connect(): Promise<void> {\n return this._ws.connect();\n }\n\n /** Force-reconnect (closes existing connection, preserves subscriptions). */\n async reconnect(): Promise<void> {\n return this._ws.reconnect();\n }\n\n /** Disconnect WebSocket (does not fire 'disconnected' event). */\n disconnect(): void {\n this._ws.disconnect();\n }\n\n /** Check if WebSocket is currently open. */\n isConnected(): boolean {\n return this._ws.isConnected();\n }\n\n /** Subscribe to entity events (can be called before or after connect). */\n subscribe(options?: SubscribeOptions): void {\n this._ws.subscribe(options);\n }\n\n // --- Web Push (#3014 / #3060) ---\n\n /**\n * Fetch the deployment's VAPID public key for Web Push subscriptions.\n *\n * Hits the unauthenticated discovery endpoint\n * `GET /.well-known/webpush-vapid-key` directly (not via `request()`) \u2014\n * it needs no login/API key and predates any session setup. Throws\n * `GeonicDBError` with `statusCode: 503` if the deployment has no VAPID\n * keys configured (503 has no dedicated error class \u2014 see\n * `createErrorFromResponse()` in `./errors`).\n */\n async getWebPushVapidKey(): Promise<string> {\n const res = await fetch(this._auth.baseUrl + WEBPUSH_VAPID_KEY_PATH);\n if (!res.ok) {\n const e = (await res.json().catch(() => ({}))) as Record<string, string>;\n throw createErrorFromResponse(\n res.status,\n e,\n 'Failed to fetch Web Push VAPID key: ' + res.status,\n );\n }\n const body = (await res.json()) as { publicKey: string };\n return body.publicKey;\n }\n\n /**\n * Subscribe the current browser to Web Push notifications (#3014).\n *\n * Reuses an existing subscription on the service worker registration if\n * one is already active *and* still matches the resolved VAPID public key\n * \u2014 otherwise (e.g. after the deployment rotated its VAPID keys) it\n * unsubscribes the stale one and re-subscribes with the current key.\n * Fetches the deployment's VAPID public key (unless `applicationServerKey`\n * is given) and calls `PushManager.subscribe()`. Convert the result with\n * `toNgsiv2WebPushNotification()` / `toNgsiLdWebPushEndpoint()` and create\n * the NGSI subscription with `request('POST', ...)`.\n *\n * Browser-only \u2014 throws in environments without Service Worker + Push API\n * support (e.g. Node.js).\n *\n * @example\n * ```ts\n * await navigator.serviceWorker.register('/sw.js');\n * const subscription = await db.subscribeWebPush();\n * await db.request('POST', '/v2/subscriptions', {\n * description: 'Web Push notification',\n * subject: { entities: [{ type: 'Sensor' }], condition: { attrs: ['value'] } },\n * notification: { webpush: toNgsiv2WebPushNotification(subscription) },\n * });\n * ```\n */\n async subscribeWebPush(\n options?: WebPushSubscribeOptions,\n ): Promise<PushSubscription> {\n /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */\n if (\n typeof navigator === 'undefined' ||\n !navigator.serviceWorker ||\n typeof window === 'undefined' ||\n !window.PushManager\n ) {\n throw new Error(\n 'subscribeWebPush() requires a browser with Service Worker + Push API support',\n );\n }\n const registration: ServiceWorkerRegistration =\n options?.serviceWorkerRegistration ?? (await navigator.serviceWorker.ready);\n const publicKey = options?.applicationServerKey ?? (await this.getWebPushVapidKey());\n const applicationServerKey = urlBase64ToUint8Array(publicKey);\n const existing: PushSubscription | null = await registration.pushManager.getSubscription();\n if (existing) {\n if (matchesApplicationServerKey(existing, applicationServerKey)) return existing;\n await existing.unsubscribe();\n }\n return registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey });\n /* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */\n }\n\n /**\n * Unsubscribe the current browser from Web Push notifications, if\n * subscribed. Returns `false` (no-op) when there is no active\n * subscription or the environment has no Service Worker support.\n */\n async unsubscribeWebPush(registration?: ServiceWorkerRegistration): Promise<boolean> {\n /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */\n if (typeof navigator === 'undefined' || !navigator.serviceWorker) return false;\n const reg = registration ?? (await navigator.serviceWorker.ready);\n const existing: PushSubscription | null = await reg.pushManager.getSubscription();\n if (!existing) return false;\n return existing.unsubscribe();\n /* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */\n }\n}\n\n// Default export for convenient importing\nexport default GeonicDB;\n\n// UMD-style global assignment for browser script tag usage\n/* istanbul ignore next: jsdom defines `window` but bundles run under real\n browsers where this attaches the SDK to the global. Covered by build\n artifact + manual browser tests. */\nif (typeof window !== 'undefined') {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n window.GeonicDB = GeonicDB;\n}\n\n/**\n * Build the NGSI-LD entities query path from `GetEntitiesParams`.\n *\n * Shared by `getEntities()` / `countEntities()` / `poll()` so all stay in sync\n * as NGSI-LD \u30AF\u30A8\u30EA\u30D1\u30E9\u30E1\u30FC\u30BF\u304C\u8FFD\u52A0\u3055\u308C\u308B (review #1043: query-string duplication)\u3002\n *\n * #1132 \u3067 `URLSearchParams` \u30D9\u30FC\u30B9\u306B refactor\u3002\u578B (`GetEntitiesParams`) \u306B\n * \u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u8FFD\u52A0\u3059\u308B\u3060\u3051\u3067\u81EA\u52D5\u7684\u306B URL \u306B\u4E57\u308B\u305F\u3081\u3001\u5F8C\u8FFD\u3044\u3067\u3053\u306E\u30D3\u30EB\u30C0\u3092\n * \u89E6\u308B\u5FC5\u8981\u304C\u306A\u3044\u3002\n */\nfunction buildEntitiesPath(params?: GetEntitiesParams): string {\n return buildPathWithParams(\n '/ngsi-ld/v1/entities',\n params as Record<string, unknown> | undefined,\n );\n}\n\n/**\n * \u6C4E\u7528\u30AF\u30A8\u30EA\u6587\u5B57\u5217\u30D3\u30EB\u30C0\u3002`undefined` / `null` \u306F\u7701\u7565\u3001`boolean` \u306F\n * `'true'` / `'false'` \u306E\u6587\u5B57\u5217\u3001`number` \u306F `String(n)`\u3001\u305D\u308C\u4EE5\u5916\u306F\u6587\u5B57\u5217\u5316\u3002\n * \u914D\u5217 / \u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u306F\u30B5\u30DD\u30FC\u30C8\u5BFE\u8C61\u5916 (NGSI-LD \u4ED5\u69D8\u5185\u306E\u5168\u30D1\u30E9\u30E1\u30FC\u30BF\u306F\n * primitive \u3067\u8868\u73FE\u3067\u304D\u308B\u305F\u3081\u4E0D\u8981)\u3002\n */\nexport function buildPathWithParams(\n path: string,\n params?: Record<string, unknown>,\n): string {\n if (!params) return path;\n const sp = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue;\n if (typeof value === 'boolean') {\n sp.append(key, value ? 'true' : 'false');\n } else if (typeof value === 'number') {\n sp.append(key, String(value));\n } else if (typeof value === 'string') {\n if (value === '') continue;\n sp.append(key, value);\n } else {\n // \u60F3\u5B9A\u5916\u306E\u578B\u306F\u7121\u8996 (\u5C06\u6765 array/object \u7CFB\u3092\u6271\u3046\u5834\u5408\u306F\u547C\u51FA\u5074\u3067\u6587\u5B57\u5217\u5316)\n continue;\n }\n }\n const qs = sp.toString();\n return qs ? `${path}?${qs}` : path;\n}\n", "/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\nimport type { GeonicDBEventMap } from './types';\n\ntype Listener<T> = (data: T) => void;\n\n/**\n * Minimal typed event emitter for GeonicDB SDK.\n */\nexport class EventEmitter {\n private _listeners: Record<string, Listener<unknown>[]> =\n Object.create(null) as Record<string, Listener<unknown>[]>;\n\n on<K extends keyof GeonicDBEventMap>(\n event: K,\n callback: Listener<GeonicDBEventMap[K]>,\n ): this;\n on(event: string, callback: Listener<unknown>): this;\n on(event: string, callback: Listener<unknown>): this {\n if (!this._listeners[event]) {\n this._listeners[event] = [];\n }\n this._listeners[event].push(callback);\n return this;\n }\n\n off<K extends keyof GeonicDBEventMap>(\n event: K,\n callback?: Listener<GeonicDBEventMap[K]>,\n ): this;\n off(event: string, callback?: Listener<unknown>): this;\n off(event: string, callback?: Listener<unknown>): this {\n if (!this._listeners[event]) return this;\n if (!callback) {\n delete this._listeners[event];\n } else {\n this._listeners[event] = this._listeners[event].filter(\n (cb) => cb !== callback,\n );\n }\n return this;\n }\n\n protected emit<K extends keyof GeonicDBEventMap>(\n event: K,\n data?: GeonicDBEventMap[K],\n ): void;\n protected emit(event: string, data?: unknown): void;\n protected emit(event: string, data?: unknown): void {\n const cbs = this._listeners[event];\n if (!cbs) return;\n const snapshot = cbs.slice();\n for (const cb of snapshot) {\n try {\n cb(data);\n } catch {\n // listener error \u2014 swallow silently\n }\n }\n }\n}\n", "/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\n/** Default token TTL in seconds when expiresIn is not specified in setCredentials(). */\nexport const DEFAULT_TOKEN_TTL_SEC = 3_600;\n\n/** Token refresh leeway in ms \u2014 refresh 2 minutes before expiry. */\nexport const TOKEN_REFRESH_LEEWAY_MS = 120_000;\n\n/** Minimum wait time in ms before refreshing a token. */\nexport const TOKEN_REFRESH_MIN_MS = 10_000;\n\n/** Maximum WebSocket reconnection attempts. */\nexport const RECONNECT_MAX_ATTEMPTS = 10;\n\n/** Base delay in ms for exponential backoff reconnection. */\nexport const RECONNECT_BASE_MS = 1_000;\n\n/** Maximum delay in ms for exponential backoff reconnection. */\nexport const RECONNECT_MAX_DELAY_MS = 30_000;\n\n/** WebSocket sub-protocol name for browser authentication. */\nexport const SUB_PROTOCOL = 'access_token';\n\n/**\n * NGSI-LD core @context. Injected into `application/ld+json` request bodies that\n * omit `@context` so writes stay spec-compliant (#1583): the broker requires an\n * inline `@context` for `application/ld+json` (ETSI GS CIM 009 clause 6.3.5).\n */\nexport const NGSI_LD_CORE_CONTEXT =\n 'https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.9.jsonld';\n\n// ============================================================\n// SDK Client Cache (#1043 Phase A)\n// Mirrors `SDK_CACHE` in src/config/defaults.ts. Update both when changing.\n// ============================================================\n\n/** In-memory cache LRU capacity (entries). */\nexport const SDK_CACHE_MAX_ENTRIES_DEFAULT = 1_000;\n\n/** Default polling interval for `db.poll()` in milliseconds. */\nexport const SDK_POLL_INTERVAL_MS_DEFAULT = 5_000;\n\n// ============================================================\n// DPoP session persistence (#1230)\n// ============================================================\n\n/**\n * IndexedDB database name for persisted DPoP sessions. Origin-scoped, so\n * multiple SDK instances in the same origin share the same database.\n */\nexport const DPOP_IDB_NAME = 'geonicdb-sdk';\n\n/** IndexedDB object store name. Single fixed entry keyed by tenant. */\nexport const DPOP_IDB_STORE = 'dpop-sessions';\n\n/** Bumped when the on-disk session shape changes. */\nexport const DPOP_IDB_VERSION = 1;\n\n// ============================================================\n// Persistent ETag cache (#1262)\n// ============================================================\n//\n// The ETag/If-None-Match cache (`SdkCache`) is in-memory and therefore lost on\n// page reload. To keep the `304` revalidation path working after a reload, the\n// cache is mirrored into IndexedDB and rehydrated when a remembered session is\n// restored. A separate database from the DPoP store avoids version-coordination\n// between the two persistence concerns.\n\n/**\n * IndexedDB database name for the persisted SDK response cache. Origin-scoped,\n * so multiple SDK instances in the same origin share the same database.\n */\nexport const SDK_CACHE_IDB_NAME = 'geonicdb-sdk-cache';\n\n/** IndexedDB object store name. Records keyed by `${scope}\\u0000${cacheKey}`. */\nexport const SDK_CACHE_IDB_STORE = 'responses';\n\n/** Bumped when the on-disk cache record shape changes. */\nexport const SDK_CACHE_IDB_VERSION = 1;\n\n// ============================================================\n// Web Push (#3014 / #3060)\n// ============================================================\n\n/**\n * Discovery endpoint for the deployment's VAPID public key.\n *\n * SDK \u306F npm \u5411\u3051\u306B\u5358\u72EC\u30D3\u30EB\u30C9\u3055\u308C\u672C\u4F53\u306E `@config/defaults` \u3092 import \u3057\u306A\u3044\u305F\u3081\u3001\n * \u3053\u3053\u3067\u30EA\u30C6\u30E9\u30EB\u3068\u3057\u3066\u6301\u3064\u3002**\u672C\u4F53\u306E `DISCOVERY.WEBPUSH_VAPID_KEY_PATH`\n * (`src/config/defaults.ts`) \u3092\u5909\u3048\u305F\u3089\u3053\u306E\u5024\u3082\u540C\u671F\u3059\u308B\u3053\u3068** (SDK Consistency)\u3002\n */\nexport const WEBPUSH_VAPID_KEY_PATH = '/.well-known/webpush-vapid-key';\n", "/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\nimport { DPOP_IDB_NAME, DPOP_IDB_STORE, DPOP_IDB_VERSION } from './constants';\nimport type { DPoPKeyPair, PersistedDPoPSession } from './types';\n\n/** Whether the Web Crypto DPoP API is available in the current environment. */\nexport const dpopSupported =\n typeof crypto !== 'undefined' &&\n !!crypto.subtle &&\n !!crypto.subtle.generateKey;\n\n/**\n * Whether IndexedDB is available. We require it for DPoP session persistence\n * because \u2014 unlike localStorage \u2014 the structured clone algorithm can store a\n * non-extractable `CryptoKey` object as-is, preserving `extractable: false`\n * across page reloads (#1230).\n */\nexport const dpopPersistenceSupported =\n typeof (globalThis as { indexedDB?: unknown }).indexedDB !== 'undefined' &&\n typeof structuredClone !== 'undefined';\n\n/* istanbul ignore next: b64url is exercised only via createDPoPProof which\n itself requires a real Web Crypto subtle.sign \u2014 bundled and E2E only. */\nfunction b64url(buf: ArrayBuffer | Uint8Array): string {\n const arr = buf instanceof ArrayBuffer ? new Uint8Array(buf) : buf;\n let str = '';\n for (let i = 0; i < arr.length; i++) {\n str += String.fromCharCode(arr[i]);\n }\n return btoa(str).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/* istanbul ignore next: helper used only by createDPoPProof; covered in E2E. */\nfunction strToUint8(s: string): Uint8Array {\n return new TextEncoder().encode(s);\n}\n\n/* istanbul ignore next: requires a real Web Crypto subtle.generateKey;\n covered by E2E auth/WS flow. */\n/** Generate an ECDSA P-256 key pair for DPoP proof creation. */\nexport async function generateDPoPKeyPair(): Promise<DPoPKeyPair | null> {\n if (!dpopSupported) return null;\n const kp = await crypto.subtle.generateKey(\n { name: 'ECDSA', namedCurve: 'P-256' },\n false,\n ['sign'],\n );\n const pubJwk = await crypto.subtle.exportKey(\n 'jwk',\n kp.publicKey,\n );\n return {\n privateKey: kp.privateKey,\n publicJwk: {\n kty: pubJwk.kty!,\n crv: pubJwk.crv!,\n x: pubJwk.x!,\n y: pubJwk.y!,\n },\n };\n}\n\n/* istanbul ignore next: ECDSA signing requires real Web Crypto; E2E-only. */\n/**\n * Create a DPoP proof JWT (RFC 9449).\n *\n * @param keyPair - The DPoP key pair\n * @param htm - HTTP method\n * @param htu - HTTP target URI\n * @param accessToken - Optional access token for ath claim\n * @param nonce - Optional server-provided nonce\n * @returns Signed DPoP proof JWT or null if keyPair is null\n */\nexport async function createDPoPProof(\n keyPair: DPoPKeyPair | null,\n htm: string,\n htu: string,\n accessToken?: string | null,\n nonce?: string | null,\n): Promise<string | null> {\n if (!keyPair) return null;\n\n const header = { alg: 'ES256', typ: 'dpop+jwt', jwk: keyPair.publicJwk };\n const payload: Record<string, unknown> = {\n jti:\n typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID()\n : Date.now().toString(36) + Math.random().toString(36).substr(2),\n htm,\n htu,\n iat: Math.floor(Date.now() / 1000),\n };\n if (nonce) payload.nonce = nonce;\n\n if (accessToken) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\n const hash = await crypto.subtle.digest('SHA-256', strToUint8(accessToken) as unknown as ArrayBuffer);\n payload.ath = b64url(hash);\n }\n\n const headerB64 = b64url(strToUint8(JSON.stringify(header)));\n const payloadB64 = b64url(strToUint8(JSON.stringify(payload)));\n const signingInput = headerB64 + '.' + payloadB64;\n\n /* eslint-disable @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unnecessary-type-assertion */\n const sigBuf = await crypto.subtle.sign(\n { name: 'ECDSA', hash: 'SHA-256' },\n keyPair.privateKey,\n strToUint8(signingInput) as unknown as ArrayBuffer,\n );\n /* eslint-enable @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unnecessary-type-assertion */\n // WebCrypto ECDSA returns IEEE P1363 r||s format (64 bytes for P-256)\n return signingInput + '.' + b64url(new Uint8Array(sigBuf));\n}\n\n// ============================================================\n// DPoP session persistence \u2014 IndexedDB (#1230)\n// ============================================================\n//\n// localStorage \u306B DPoP \u30BB\u30C3\u30B7\u30E7\u30F3\u3092\u4FDD\u5B58\u3067\u304D\u306A\u3044\u306E\u306F\u3001CryptoKey \u304C\u6587\u5B57\u5217\u306B\n// \u30B7\u30EA\u30A2\u30E9\u30A4\u30BA\u3067\u304D\u306A\u3044\u305F\u3081\u3002JWK \u306B\u30A8\u30AF\u30B9\u30DD\u30FC\u30C8\u3059\u308B\u306B\u306F extractable: true \u3067\n// \u518D\u751F\u6210\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u3001\u305D\u306E\u6642\u70B9\u3067 XSS \u3067\u9375\u304C\u6F0F\u308C\u308B\u3002\n//\n// IndexedDB \u306F\u69CB\u9020\u5316\u8907\u88FD\u30A2\u30EB\u30B4\u30EA\u30BA\u30E0\u3067 CryptoKey \u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3092\u305D\u306E\u307E\u307E\u4FDD\u5B58\n// \u3067\u304D\u308B\u3002\u9375\u306F extractable: false \u306E\u307E\u307E\u6C38\u7D9A\u5316\u3055\u308C\u3001JavaScript \u304B\u3089\u306F\u4F9D\u7136\u3068\n// \u3057\u3066\u30A8\u30AF\u30B9\u30DD\u30FC\u30C8\u4E0D\u53EF\u3002\u30EA\u30ED\u30FC\u30C9\u6642\u306F\u540C\u3058\u9375\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u304C\u5FA9\u5143\u3055\u308C\u308B\u306E\u3067\n// DPoP \u30D7\u30EB\u30FC\u30D5\u7F72\u540D\u3092\u518D\u958B\u3067\u304D\u308B\u3002\u3053\u308C\u304C Web \u306E DPoP \u5B9F\u88C5\u306E\u696D\u754C\u6A19\u6E96\u3002\n//\n// \u30B9\u30C8\u30A2\u306F tenant \u3092\u30AD\u30FC\u306B\u3059\u308B\uFF1A\u540C\u3058 origin \u3067\u8907\u6570\u30C6\u30CA\u30F3\u30C8\u3092\u4F7F\u3044\u5206\u3051\u308B\n// \u30A2\u30D7\u30EA\u3067\u3082\u3001\u5404\u30C6\u30CA\u30F3\u30C8\u306E DPoP \u30BB\u30C3\u30B7\u30E7\u30F3\u304C\u72EC\u7ACB\u3057\u3066\u4FDD\u305F\u308C\u308B\u3002\n//\n// IndexedDB \u306E\u578B\u306F DOM lib \u306B\u3057\u304B\u5B58\u5728\u3057\u306A\u3044\u306E\u3067\u3001SDK \u30D3\u30EB\u30C9\u306F\n// `lib: [\"DOM\"]` \u3067\u901A\u308B\u304C\u3001\u30EA\u30DD\u30B8\u30C8\u30EA\u5168\u4F53\u306E ESLint \u304C\u898B\u308B tsconfig \u306F\n// Node \u7528\u306E lib \u306E\u307F\u3002SDK \u30D5\u30A1\u30A4\u30EB\u3060\u3051\u5225 lint config \u306B\u5207\u308A\u51FA\u3059\u3088\u308A\u3001\n// IndexedDB \u3092\u89E6\u308B\u95A2\u6570\u7FA4\u3092\u5C40\u6240\u7684\u306B eslint-disable \u3059\u308B\u65B9\u304C\u5909\u66F4\u7BC4\u56F2\u304C\u5C0F\u3055\u3044\u3002\n\n/* eslint-disable @typescript-eslint/no-unsafe-call,\n @typescript-eslint/no-unsafe-member-access,\n @typescript-eslint/prefer-promise-reject-errors,\n @typescript-eslint/no-unsafe-assignment,\n @typescript-eslint/no-unsafe-return,\n @typescript-eslint/no-unsafe-argument,\n @typescript-eslint/no-redundant-type-constituents */\n\n/* istanbul ignore next: trivial wrapper, exercised in E2E and in the\n fake-indexeddb-backed unit tests. */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction openSessionDB(): Promise<any> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return new Promise<any>((resolve, reject) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const req: any = (globalThis as any).indexedDB.open(DPOP_IDB_NAME, DPOP_IDB_VERSION);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains(DPOP_IDB_STORE)) {\n db.createObjectStore(DPOP_IDB_STORE);\n }\n };\n req.onsuccess = () => { resolve(req.result); };\n req.onerror = () => { reject(req.error ?? new Error('IndexedDB open failed')); };\n });\n}\n\n/**\n * Persist a DPoP session to IndexedDB, keyed by tenant.\n *\n * Safe to call repeatedly: each call replaces the prior entry for the same\n * tenant. Returns false (without throwing) when IndexedDB is unavailable or\n * the write fails \u2014 callers should treat persistence as best-effort and not\n * abort the login flow.\n */\nexport async function saveDPoPSession(\n session: PersistedDPoPSession,\n): Promise<boolean> {\n if (!dpopPersistenceSupported) return false;\n // IndexedDB \u306E\u578B\u306F DOM lib \u306B\u3057\u304B\u306A\u3044\u3002SDK \u30D3\u30EB\u30C9\u306F lib: DOM \u3067\u901A\u308B\u304C\u3001\n // \u30EA\u30DD\u30B8\u30C8\u30EA\u5168\u4F53\u306E lint context \u306B DOM lib \u3092\u6F0F\u6D29\u3055\u305B\u305F\u304F\u306A\u3044\u306E\u3067\u5C40\u6240\u7684\u306B\n // any \u3068\u3057\u3066\u6271\u3046 (\u3053\u306E\u30BB\u30AF\u30B7\u30E7\u30F3\u5192\u982D\u306E eslint-disable \u3067\u8A31\u5BB9)\u3002\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let db: any = null;\n try {\n db = await openSessionDB();\n await new Promise<void>((resolve, reject) => {\n const tx = db!.transaction(DPOP_IDB_STORE, 'readwrite');\n tx.objectStore(DPOP_IDB_STORE).put(session, session.tenant);\n tx.oncomplete = () => { resolve(); };\n tx.onerror = () => { reject(tx.error ?? new Error('IndexedDB put failed')); };\n tx.onabort = () => { reject(tx.error ?? new Error('IndexedDB transaction aborted')); };\n });\n return true;\n } catch {\n /* istanbul ignore next: write-path catch is reachable only when IDB\n itself faults (quota exceeded, blocked tab, ...) \u2014 best-effort path,\n covered via E2E rather than unit tests. */\n return false;\n } finally {\n db?.close();\n }\n}\n\n/**\n * Load a previously persisted DPoP session for the given tenant.\n *\n * Returns null when no session is stored, when persistence is unavailable,\n * or when the stored session is shaped unexpectedly (schema drift across\n * versions). Never throws.\n */\nexport async function loadDPoPSession(\n tenant: string,\n): Promise<PersistedDPoPSession | null> {\n if (!dpopPersistenceSupported) return null;\n // IndexedDB \u306E\u578B\u306F DOM lib \u306B\u3057\u304B\u306A\u3044\u3002SDK \u30D3\u30EB\u30C9\u306F lib: DOM \u3067\u901A\u308B\u304C\u3001\n // \u30EA\u30DD\u30B8\u30C8\u30EA\u5168\u4F53\u306E lint context \u306B DOM lib \u3092\u6F0F\u6D29\u3055\u305B\u305F\u304F\u306A\u3044\u306E\u3067\u5C40\u6240\u7684\u306B\n // any \u3068\u3057\u3066\u6271\u3046 (\u3053\u306E\u30BB\u30AF\u30B7\u30E7\u30F3\u5192\u982D\u306E eslint-disable \u3067\u8A31\u5BB9)\u3002\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let db: any = null;\n try {\n db = await openSessionDB();\n const data = await new Promise<unknown>((resolve, reject) => {\n const tx = db!.transaction(DPOP_IDB_STORE, 'readonly');\n const req = tx.objectStore(DPOP_IDB_STORE).get(tenant);\n req.onsuccess = () => { resolve(req.result); };\n req.onerror = () => { reject(req.error ?? new Error('IndexedDB get failed')); };\n });\n return validatePersistedSession(data);\n } catch {\n /* istanbul ignore next: read-path catch is reachable only when IDB\n itself faults (db deletion mid-read, blocked tab, ...) \u2014 covered\n via E2E rather than unit tests. */\n return null;\n } finally {\n db?.close();\n }\n}\n\n/**\n * Remove the persisted DPoP session for the given tenant. Always resolves \u2014\n * idempotent and never throws, so logout() flows can call it unconditionally.\n */\nexport async function clearDPoPSession(tenant: string): Promise<void> {\n if (!dpopPersistenceSupported) return;\n // IndexedDB \u306E\u578B\u306F DOM lib \u306B\u3057\u304B\u306A\u3044\u3002SDK \u30D3\u30EB\u30C9\u306F lib: DOM \u3067\u901A\u308B\u304C\u3001\n // \u30EA\u30DD\u30B8\u30C8\u30EA\u5168\u4F53\u306E lint context \u306B DOM lib \u3092\u6F0F\u6D29\u3055\u305B\u305F\u304F\u306A\u3044\u306E\u3067\u5C40\u6240\u7684\u306B\n // any \u3068\u3057\u3066\u6271\u3046 (\u3053\u306E\u30BB\u30AF\u30B7\u30E7\u30F3\u5192\u982D\u306E eslint-disable \u3067\u8A31\u5BB9)\u3002\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let db: any = null;\n try {\n db = await openSessionDB();\n await new Promise<void>((resolve, reject) => {\n const tx = db!.transaction(DPOP_IDB_STORE, 'readwrite');\n tx.objectStore(DPOP_IDB_STORE).delete(tenant);\n tx.oncomplete = () => { resolve(); };\n tx.onerror = () => { reject(tx.error ?? new Error('IndexedDB delete failed')); };\n tx.onabort = () => { reject(tx.error ?? new Error('IndexedDB transaction aborted')); };\n });\n } catch {\n // swallow \u2014 clearing is best-effort\n } finally {\n db?.close();\n }\n}\n\nfunction validatePersistedSession(data: unknown): PersistedDPoPSession | null {\n if (!data || typeof data !== 'object') return null;\n const s = data as Record<string, unknown>;\n const kp = s.keyPair as Record<string, unknown> | undefined;\n if (\n typeof s.tenant !== 'string' ||\n typeof s.accessToken !== 'string' ||\n typeof s.refreshToken !== 'string' ||\n typeof s.expiresAt !== 'number' ||\n !kp ||\n !kp.privateKey ||\n !kp.publicJwk\n ) {\n return null;\n }\n return data as PersistedDPoPSession;\n}\n\n/* eslint-enable @typescript-eslint/no-unsafe-call,\n @typescript-eslint/no-unsafe-member-access,\n @typescript-eslint/prefer-promise-reject-errors,\n @typescript-eslint/no-unsafe-assignment,\n @typescript-eslint/no-unsafe-return,\n @typescript-eslint/no-unsafe-argument,\n @typescript-eslint/no-redundant-type-constituents */\n", "/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\nconst BATCH_SIZE = 1000;\nconst MAX_ITERATIONS = 1_000_000;\n\n/** Check whether the first `bits` bits of a hash are zero. */\nfunction checkZeros(hash: Uint8Array, bits: number): boolean {\n const fullBytes = Math.floor(bits / 8);\n const remainBits = bits % 8;\n for (let k = 0; k < fullBytes; k++) {\n if (hash[k] !== 0) return false;\n }\n if (remainBits > 0) {\n const mask = 0xff << (8 - remainBits);\n if ((hash[fullBytes] & mask) !== 0) return false;\n }\n return true;\n}\n\n/**\n * Solve a Proof of Work challenge asynchronously using Web Crypto.\n *\n * Hashes `challenge + nonce` (SHA-256) in batches and checks for\n * the required number of leading zero bits.\n *\n * @param challenge - The challenge string from the server\n * @param difficulty - Number of leading zero bits required\n * @returns The nonce that satisfies the difficulty\n */\nexport async function solvePoW(\n challenge: string,\n difficulty: number,\n): Promise<number> {\n if (!Number.isInteger(difficulty) || difficulty < 0 || difficulty > 256) {\n throw new Error('difficulty must be an integer between 0 and 256');\n }\n const encoder = new TextEncoder();\n let i = 0;\n\n async function tryBatch(): Promise<number> {\n const batchEnd = Math.min(i + BATCH_SIZE, MAX_ITERATIONS);\n const promises: Promise<{ idx: number; hash: Uint8Array }>[] = [];\n\n for (; i < batchEnd; i++) {\n const idx = i;\n promises.push(\n crypto.subtle\n .digest('SHA-256', encoder.encode(challenge + String(idx)))\n .then((buf) => ({ idx, hash: new Uint8Array(buf) })),\n );\n }\n\n const results = await Promise.all(promises);\n for (const result of results) {\n if (checkZeros(result.hash, difficulty)) {\n return result.idx;\n }\n }\n /* istanbul ignore next: hitting MAX_ITERATIONS (1e6) without a solution is\n practically unreachable for realistic difficulties (<= ~20 bits) and\n would make tests run for many seconds. Covered indirectly by tryBatch's\n recursion via the difficulty-8 test. */\n if (i >= MAX_ITERATIONS) throw new Error('PoW solution not found');\n /* istanbul ignore next: recursion path triggers only when the first batch\n of 1000 hashes finds no solution. With difficulty <= 16 this rarely\n happens within unit-test timeouts. */\n return tryBatch();\n }\n\n return tryBatch();\n}\n", "/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\n/**\n * Which request produced an error.\n *\n * `login()` performs two requests \u2014 `POST /auth/login` and then\n * `POST /auth/dpop-bind` \u2014 and both surface the server's `detail` /\n * `description` verbatim as an `AuthenticationError`. Without this, a rejected\n * credential and a rejected DPoP bind are indistinguishable from the error\n * alone, so callers (and anyone reading a bug report) cannot tell which step\n * actually failed.\n */\nexport interface RequestContext {\n method: string;\n /**\n * The endpoint path, without the base URL \u2014 e.g. `/auth/dpop-bind`. When the\n * base URL itself carries a path prefix (`https://host/broker`), the path on\n * the wire includes that prefix; this field names the endpoint, not the full\n * request path.\n */\n path: string;\n}\n\n/**\n * Base error class for GeonicDB SDK.\n * All SDK errors extend this class, allowing `instanceof GeonicDBError` checks.\n */\nexport class GeonicDBError extends Error {\n /** HTTP status code (if applicable) */\n readonly statusCode: number;\n\n /** The request that failed, when the SDK knows it. */\n readonly request?: RequestContext;\n\n constructor(message: string, statusCode = 0) {\n super(message);\n this.name = 'GeonicDBError';\n this.statusCode = statusCode;\n }\n}\n\n/**\n * Record which request an error came from: sets `request` and prefixes the\n * message with `[METHOD path]`. Returns the same instance so it can wrap a\n * `throw` expression directly, and preserves the concrete error class.\n */\nexport function withRequest<E extends GeonicDBError>(error: E, request: RequestContext): E {\n (error as { request?: RequestContext }).request = request;\n error.message = `[${request.method} ${request.path}] ${error.message}`;\n return error;\n}\n\n/**\n * Authentication failed (invalid credentials, expired token, or an HTTP\n * failure on an auth endpoint). Defaults to 401; when thrown from an HTTP\n * response, pass the real status so callers see what the server returned\n * (#2649). Subclasses override with 409 / 403 for control-flow errors.\n */\nexport class AuthenticationError extends GeonicDBError {\n constructor(message = 'Authentication failed', statusCode = 401) {\n super(message, statusCode);\n this.name = 'AuthenticationError';\n }\n}\n\n/** 403 \u2014 Authorization denied (insufficient permissions). */\nexport class AuthorizationError extends GeonicDBError {\n constructor(message = 'Access denied') {\n super(message, 403);\n this.name = 'AuthorizationError';\n }\n}\n\n/** 404 \u2014 Resource not found. */\nexport class NotFoundError extends GeonicDBError {\n constructor(message = 'Not found') {\n super(message, 404);\n this.name = 'NotFoundError';\n }\n}\n\n/** 409 \u2014 Conflict (e.g. entity already exists). */\nexport class ConflictError extends GeonicDBError {\n constructor(message = 'Conflict') {\n super(message, 409);\n this.name = 'ConflictError';\n }\n}\n\n/**\n * 409 \u2014 \u4E00\u6642\u30D1\u30B9\u30EF\u30FC\u30C9\u3067\u306E\u521D\u56DE\u30ED\u30B0\u30A4\u30F3\u3067\u3001\u65B0\u30D1\u30B9\u30EF\u30FC\u30C9\u306E\u8A2D\u5B9A\u304C\u5FC5\u8981 (#1532)\u3002\n * `login(email, tempPassword)` \u304C\u3053\u308C\u3092\u6295\u3052\u305F\u3089\u3001\u65B0\u30D1\u30B9\u30EF\u30FC\u30C9\u3092\u96C6\u3081\u3066\n * `login(email, tempPassword, { newPassword })` \u3092\u547C\u3073\u76F4\u3059\u3002\n */\nexport class PasswordResetRequiredError extends AuthenticationError {\n constructor(message = 'Password reset required: set a new password to complete login') {\n super(message, 409);\n this.name = 'PasswordResetRequiredError';\n }\n}\n\n/**\n * 403 \u2014 \u4E00\u6642\u30D1\u30B9\u30EF\u30FC\u30C9\u306E\u6709\u52B9\u671F\u9650\u5207\u308C (#1532)\u3002\u7BA1\u7406\u8005\u306B\u518D\u767A\u884C\u3092\u4F9D\u983C\u3059\u308B\u3002\n */\nexport class TemporaryPasswordExpiredError extends AuthenticationError {\n constructor(message = 'Temporary password expired: ask an administrator to re-issue it') {\n super(message, 403);\n this.name = 'TemporaryPasswordExpiredError';\n }\n}\n\n/** 422 \u2014 Validation error (bad request payload). */\nexport class ValidationError extends GeonicDBError {\n constructor(message = 'Validation failed') {\n super(message, 422);\n this.name = 'ValidationError';\n }\n}\n\n/** 429 \u2014 Rate limited. */\nexport class RateLimitError extends GeonicDBError {\n /** Seconds to wait before retrying (from Retry-After header) */\n readonly retryAfter: number;\n\n constructor(message = 'Rate limit exceeded', retryAfter = 1) {\n super(message, 429);\n this.name = 'RateLimitError';\n this.retryAfter = retryAfter;\n }\n}\n\n/** Network error (fetch failed, DNS resolution, timeout). */\nexport class NetworkError extends GeonicDBError {\n constructor(message = 'Network error') {\n super(message, 0);\n this.name = 'NetworkError';\n }\n}\n\n/**\n * Create a typed error from an HTTP response status and error body.\n * Used internally by SDK methods to throw the appropriate error class.\n */\nexport function createErrorFromResponse(\n status: number,\n body: Record<string, string>,\n fallbackMessage: string,\n): GeonicDBError {\n const message = body.detail || body.description || fallbackMessage;\n switch (status) {\n case 401:\n return new AuthenticationError(message);\n case 403:\n return new AuthorizationError(message);\n case 404:\n return new NotFoundError(message);\n case 409:\n return new ConflictError(message);\n case 422:\n return new ValidationError(message);\n case 429:\n return new RateLimitError(message);\n default:\n return new GeonicDBError(message, status);\n }\n}\n", "/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\nimport { SDK_CACHE_MAX_ENTRIES_DEFAULT } from './constants';\nimport type { CachePersistence } from './cache-persistence';\n\n/**\n * In-memory client-side cache for the GeonicDB SDK (#991 Phase A).\n *\n * Stores ETag / Last-Modified / response body / response headers per\n * `${method}:${path}` key. The cache layer is opt-out (enabled by default) and\n * driven by the SDK's `request()` method:\n *\n * 1. On a cacheable GET, the SDK looks up the cache and adds `If-None-Match` /\n * `If-Modified-Since` to the outgoing request.\n * 2. If the server returns `304 Not Modified`, the cached body is returned to\n * the caller (transparent to the consumer).\n * 3. On `200 OK`, the cache entry is replaced with the fresh body and ETag.\n * 4. WebSocket entity events do **not** auto-invalidate the cache. Because\n * data endpoints respond with `Cache-Control: private, no-cache`, every\n * cached read already revalidates with the origin via `If-None-Match`,\n * so the server returns `304` if data is unchanged or `200` with the\n * fresh body if it changed. Keeping the cache entries during WebSocket\n * activity preserves the bandwidth-saving `304` path; deleting them\n * forces a full `200` body on the next read with no benefit.\n * 5. Use `clearCache()` for explicit, full invalidation (e.g. on auth change).\n *\n * Persistence (#1262): when a persistence backend is attached and enabled\n * (browser + remembered session), `set`/`delete` write through to IndexedDB\n * so cached validators survive a page reload. `hydrate()` reloads them after a\n * session is restored, so the first request after a reload can send\n * `If-None-Match` instead of cold-starting into a full `200`. In non-browser\n * runtimes (no IndexedDB) the cache stays purely in-memory.\n *\n * Eviction is LRU based on `maxEntries`. There is no TTL \u2014 freshness is\n * delegated entirely to the server's RFC 7234 `Cache-Control` directives,\n * which the SDK respects via revalidation (the server returns\n * `private, no-cache` for data endpoints \u2014 `private` blocks shared / CDN\n * caches and `no-cache` forces revalidation from the SDK's private cache,\n * so every cached lookup re-validates with the origin).\n */\n\nexport interface CacheEntry {\n /** ETag captured from the previous response. */\n etag?: string;\n /** Last-Modified captured from the previous response (RFC 1123 HTTP-date). */\n lastModified?: string;\n /**\n * Cached response body. Stored as the parsed JSON value (or string for\n * non-JSON responses) so subsequent 304 hits can hand the same shape back to\n * the caller without re-parsing.\n */\n data: unknown;\n /**\n * Selected response headers worth replaying on a 304 hit (e.g.\n * `NGSILD-Results-Count`, `Fiware-Total-Count`, `Link`). Stored as a plain\n * record because `Headers` instances are not serialisable.\n */\n headers: Record<string, string>;\n /** Cache insertion timestamp (epoch ms). Used for LRU only. */\n cachedAt: number;\n}\n\n/**\n * Minimal LRU cache for SDK responses.\n *\n * The implementation relies on `Map`'s insertion-order property (ES2015+):\n * deleting and re-inserting an entry moves it to the most-recent position, so\n * eviction simply removes the head of the iterator when capacity is exceeded.\n */\nexport class SdkCache {\n private readonly _store = new Map<string, CacheEntry>();\n private readonly _maxEntries: number;\n\n /**\n * Optional persistent backing store (IndexedDB) so cached validators survive\n * a page reload (#1262). `null` in non-browser runtimes \u2014 the cache then\n * behaves as a pure in-memory LRU.\n */\n private _persistence: CachePersistence | null = null;\n /**\n * Auth-scope namespace for persisted entries. The auth layer sets this to a\n * tenant + authenticated-principal identifier so that entries written by one\n * principal are never read back by another sharing the same origin/tenant\n * (different tabs / SDK instances each scope to their own principal).\n */\n private _scope = '';\n /**\n * Whether write-through persistence is currently active. Driven by the auth\n * layer: enabled only for remembered sessions, so persisted entries belong to\n * exactly one principal and are dropped on every credential change.\n */\n private _persistEnabled = false;\n /**\n * Serialises persistence writes so they complete in call order (#1262\n * review). `set`/`delete`/`clearPersisted` are otherwise independent\n * fire-and-forget ops; if a late `put()` resolved after a `remove()` /\n * `clearScope()`, an evicted/deleted/logged-out entry could reappear in\n * IndexedDB. Chaining them keeps the persisted store consistent with the\n * in-memory state and with the auth-boundary flush.\n */\n private _persistQueue: Promise<void> = Promise.resolve();\n\n constructor(maxEntries: number = SDK_CACHE_MAX_ENTRIES_DEFAULT) {\n if (!Number.isInteger(maxEntries) || maxEntries <= 0) {\n throw new Error(`SdkCache maxEntries must be a positive integer, got ${String(maxEntries)}`);\n }\n this._maxEntries = maxEntries;\n }\n\n /** Wire a persistence backend (called once at construction when available). */\n attachPersistence(persistence: CachePersistence): void {\n this._persistence = persistence;\n }\n\n /** Whether a persistence backend is attached. */\n hasPersistence(): boolean {\n return this._persistence !== null;\n }\n\n /** Set the auth-scope namespace used for persisted reads/writes. */\n setScope(scope: string): void {\n this._scope = scope;\n }\n\n /** Enable or disable write-through persistence (driven by `rememberSession`). */\n setPersistEnabled(enabled: boolean): void {\n this._persistEnabled = enabled && this._persistence !== null;\n }\n\n /** True when set/delete should propagate to the persistent store. */\n private get _persisting(): boolean {\n return this._persistEnabled && this._persistence !== null;\n }\n\n /**\n * Append a persistence operation to the serialised queue. The backend and\n * scope are captured at enqueue time so a later scope change cannot retarget\n * an already-queued op. A failing op is swallowed (persistence is\n * best-effort) and does not stall the chain. Returns the tail so callers that\n * need ordering (`clearPersisted`) can await it.\n */\n private _enqueuePersist(\n op: (persistence: CachePersistence, scope: string) => Promise<void>,\n ): Promise<void> {\n const persistence = this._persistence;\n if (!persistence) return this._persistQueue;\n const scope = this._scope;\n this._persistQueue = this._persistQueue\n .catch(() => undefined)\n .then(() => op(persistence, scope))\n .catch(() => undefined);\n return this._persistQueue;\n }\n\n /**\n * Load persisted entries for the current scope into memory, oldest first so\n * the in-memory LRU recency order is preserved. Best-effort and idempotent \u2014\n * a no-op when no backend is attached. Called after a remembered session is\n * restored so the very first request after a reload can send `If-None-Match`.\n */\n async hydrate(): Promise<void> {\n if (!this._persistence) return;\n const entries = await this._persistence.loadScope(this._scope, this._maxEntries);\n for (const { key, entry } of entries) {\n if (this._store.has(key)) this._store.delete(key);\n this._store.set(key, entry);\n }\n // Defensive trim \u2014 loadScope already caps to maxEntries, but a pre-populated\n // store could push us over.\n while (this._store.size > this._maxEntries) {\n const oldest = this._store.keys().next().value;\n /* istanbul ignore if -- size > maxEntries guarantees a first key */\n if (oldest === undefined) break;\n this._store.delete(oldest);\n }\n }\n\n /** Drop every persisted entry for the current scope (auth change / flush). */\n async clearPersisted(): Promise<void> {\n if (!this._persistence) return;\n await this._enqueuePersist((persistence, scope) => persistence.clearScope(scope));\n }\n\n /** Build a stable cache key. Method is upper-cased so casing differences do not produce duplicate entries. */\n static keyFor(method: string, path: string): string {\n return `${method.toUpperCase()}:${path}`;\n }\n\n /** Returns the entry and bumps it to most-recent position, or `undefined`. */\n get(key: string): CacheEntry | undefined {\n const entry = this._store.get(key);\n if (!entry) return undefined;\n // Bump LRU position\n this._store.delete(key);\n this._store.set(key, entry);\n return entry;\n }\n\n /** Insert or replace an entry. Evicts the LRU entry when over capacity. */\n set(key: string, entry: CacheEntry): void {\n if (this._store.has(key)) {\n this._store.delete(key);\n }\n this._store.set(key, entry);\n const evicted: string[] = [];\n while (this._store.size > this._maxEntries) {\n const oldest = this._store.keys().next().value;\n /* istanbul ignore if -- size > maxEntries guarantees a first key */\n if (oldest === undefined) break;\n this._store.delete(oldest);\n evicted.push(oldest);\n }\n // Mirror the mutation into the persistent store (best-effort, fire-and-forget)\n // so the entry survives a reload. Evicted keys are dropped to keep the\n // persisted set bounded by the same LRU capacity (#1262).\n if (this._persisting) {\n void this._enqueuePersist(async (persistence, scope) => {\n await persistence.put(scope, key, entry);\n if (evicted.length > 0) await persistence.remove(scope, evicted);\n });\n }\n }\n\n /** Delete a specific entry. Returns whether anything was removed. */\n delete(key: string): boolean {\n const removed = this._store.delete(key);\n if (removed && this._persisting) {\n void this._enqueuePersist((persistence, scope) => persistence.remove(scope, [key]));\n }\n return removed;\n }\n\n /**\n * Delete every entry whose key matches a predicate. Returns the array of\n * removed keys so callers can emit `cacheInvalidated` events.\n */\n deleteWhere(predicate: (key: string, entry: CacheEntry) => boolean): string[] {\n const removed: string[] = [];\n for (const [key, entry] of this._store) {\n if (predicate(key, entry)) {\n this._store.delete(key);\n removed.push(key);\n }\n }\n return removed;\n }\n\n /** Drop everything. */\n clear(): void {\n this._store.clear();\n }\n\n /** Current entry count. Useful for tests / metrics. */\n size(): number {\n return this._store.size;\n }\n}\n\n/**\n * Pull a small set of response headers worth retaining on a 304 replay.\n * Stripping unknown headers keeps memory bounded and avoids leaking transport\n * details (e.g. `set-cookie`) into the SDK consumer.\n */\nconst HEADERS_TO_PERSIST = [\n 'content-type',\n 'etag',\n 'last-modified',\n 'cache-control',\n 'vary',\n 'link',\n 'ngsild-results-count',\n 'fiware-total-count',\n 'x-total-count',\n 'ngsild-next',\n 'fiware-next-token',\n] as const;\n\nexport function snapshotHeaders(headers: Headers): Record<string, string> {\n const out: Record<string, string> = {};\n for (const name of HEADERS_TO_PERSIST) {\n const value = headers.get(name);\n if (value !== null) out[name] = value;\n }\n return out;\n}\n\n/** Whether a request method is cacheable per RFC 7234 \u00A74.2.1 (safe + idempotent + no body changes). */\nexport function isCacheableMethod(method: string): boolean {\n const upper = method.toUpperCase();\n return upper === 'GET' || upper === 'HEAD';\n}\n", "/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\nimport {\n clearDPoPSession,\n createDPoPProof,\n dpopSupported,\n generateDPoPKeyPair,\n loadDPoPSession,\n saveDPoPSession,\n} from './dpop';\nimport { solvePoW } from './pow';\nimport { DEFAULT_TOKEN_TTL_SEC, NGSI_LD_CORE_CONTEXT } from './constants';\nimport {\n AuthenticationError,\n PasswordResetRequiredError,\n TemporaryPasswordExpiredError,\n withRequest,\n} from './errors';\nimport { SdkCache, snapshotHeaders, isCacheableMethod } from './cache';\nimport type {\n CacheEvent,\n CredentialsOptions,\n DPoPKeyPair,\n LoginOptions,\n LoginResponse,\n RefreshedCredentials,\n} from './types';\n\ntype CacheEventName = 'cacheHit' | 'cacheMiss' | 'cacheInvalidated';\n\n/** Internal authentication state for the GeonicDB SDK. */\nexport class AuthManager {\n private _baseUrl: string;\n\n /** The base URL requests are sent against (trailing slash already stripped). */\n get baseUrl(): string {\n return this._baseUrl;\n }\n\n private _apiKey: string;\n private _tenant: string;\n _debug: boolean;\n /**\n * #1105: anonymous \u30E2\u30FC\u30C9\u3002true \u306E\u5834\u5408\u3001\u30C8\u30FC\u30AF\u30F3\u3092\u53D6\u5F97\u305B\u305A Authorization\n * \u30D8\u30C3\u30C0\u7121\u3057\u3067\u9001\u4FE1\u3059\u308B\u3002\u30B5\u30FC\u30D0\u30FC\u5074 (`optionalAuth`) \u3067 role='anonymous' \u3068\u3057\u3066\n * \u901A\u308A\u3001XACML \u304C\u8A8D\u53EF\u5224\u5B9A\u3059\u308B\u3002\n *\n * `_initialAnonymous` \u306F\u30B3\u30F3\u30B9\u30C8\u30E9\u30AF\u30BF\u3067\u6307\u5B9A\u3055\u308C\u305F\u5E0C\u671B\u30E2\u30FC\u30C9 (immutable)\u3002\n * `_anonymous` \u306F\u73FE\u5728\u306E\u30E2\u30FC\u30C9\u72B6\u614B\u3067\u3001`login()` / `setCredentials()` \u3067 false\n * \u306B\u964D\u308A\u3001`logout()` \u3067 `_initialAnonymous` \u306E\u5024\u306B\u623B\u308B\u3002\n *\n * \u30C8\u30FC\u30AF\u30F3\u6709\u7121 (`_token === null`) \u304B\u3089\u306F\u63A8\u8AD6\u3057\u306A\u3044\u3002`_token` \u306F refresh\n * \u5931\u6557\u6642\u306B null \u3078\u843D\u3061\u308B\u304C\u3001\u30E6\u30FC\u30B6\u304C\u660E\u793A\u7684\u306B `logout()` \u3057\u3066\u3044\u306A\u3044\u4EE5\u4E0A\u3001\n * \u6B21\u306E\u30EA\u30AF\u30A8\u30B9\u30C8\u3092\u52DD\u624B\u306B Authorization \u30D8\u30C3\u30C0\u7121\u3057\u3067\u9001\u3063\u3066\u306F\u306A\u3089\u306A\u3044\n * (#1113 review)\u3002\n */\n private readonly _initialAnonymous: boolean;\n private _anonymous: boolean;\n\n _token: string | null = null;\n _tokenExpiry = 0;\n _tokenType: 'Bearer' | 'DPoP' = 'Bearer';\n _refreshToken: string | null = null;\n _dpopKeyPair: DPoPKeyPair | null = null;\n _dpopReady: Promise<void> | null = null;\n _dpopNonce: string | null = null;\n /**\n * When true, every DPoP token mutation (login bind, refresh) is mirrored to\n * IndexedDB so the session survives a page reload (#1230). Toggled by\n * `login(email, password, { rememberSession: true })` and\n * `restoreSession()` (#1244).\n */\n _rememberSession = false;\n /** Email captured at login, persisted for UI display. */\n _email: string | null = null;\n private _tokenPromise: Promise<string> | null = null;\n\n /** Callback to emit tokenRefresh events. Set by GeonicDB class. */\n onTokenRefresh: ((creds: RefreshedCredentials) => void) | null = null;\n\n /**\n * SDK-level cache (#991 Phase A). When set, cacheable GET requests are\n * served via ETag/304 with automatic If-None-Match negotiation, and\n * concurrent requests to the same path are deduplicated.\n */\n private _cache: SdkCache | null = null;\n\n /** Emitter for cacheHit / cacheMiss / cacheInvalidated events. */\n private _emitCacheEvent: ((name: CacheEventName, payload: CacheEvent) => void) | null = null;\n\n /** In-flight request map keyed by `${METHOD}:${path}` for request deduplication. */\n private readonly _inFlight = new Map<string, Promise<Response>>();\n\n constructor(\n baseUrl: string,\n apiKey: string,\n tenant: string,\n debug = false,\n anonymous = false,\n ) {\n if (anonymous && apiKey) {\n throw new Error(\n 'GeonicDB SDK: `anonymous: true` cannot be combined with `apiKey`. ' +\n 'Anonymous mode skips token acquisition entirely.',\n );\n }\n this._baseUrl = baseUrl;\n this._apiKey = apiKey;\n this._tenant = tenant;\n this._debug = debug;\n this._initialAnonymous = anonymous;\n this._anonymous = anonymous;\n\n // anonymous \u30E2\u30FC\u30C9\u3067\u306F DPoP \u9375\u30DA\u30A2\u3092\u751F\u6210\u3057\u306A\u3044\uFF08\u4F7F\u308F\u308C\u306A\u3044\uFF09\u3002\n /* istanbul ignore next: dpopSupported is false in jsdom (no\n crypto.subtle.generateKey) \u2014 covered by E2E. */\n if (!anonymous && dpopSupported) {\n this._dpopReady = generateDPoPKeyPair()\n .then((kp) => {\n // #1230 review: restoreSession() \u304C\u30B3\u30F3\u30B9\u30C8\u30E9\u30AF\u30BF\u5B8C\u4E86\u524D\u306B\u547C\u3070\u308C\u3066\n // \u9375\u3092 IndexedDB \u304B\u3089\u5FA9\u5143\u3057\u305F\u5834\u5408\u3001\u3053\u3053\u3067\u9045\u5EF6\u3057\u3066 resolve \u3059\u308B\u9375\u3067\n // \u4E0A\u66F8\u304D\u3059\u308B\u3068\u5FA9\u5143\u3057\u305F\u30C8\u30FC\u30AF\u30F3\u3068\u306E\u7D10\u4ED8\u3051\u304C\u58CA\u308C\u308B\u3002\u65E2\u306B\u9375\u304C\u5165\u3063\u3066\n // \u3044\u308C\u3070\u89E6\u3089\u306A\u3044\u3002\n if (!this._dpopKeyPair) this._dpopKeyPair = kp;\n })\n .catch(() => {\n if (!this._dpopKeyPair) this._dpopKeyPair = null;\n });\n }\n }\n\n /**\n * True when running unauthenticated.\n *\n * Returns the explicit mode flag, NOT derived from `_token === null`. After\n * a refresh failure clears `_token`, the SDK must NOT silently switch to\n * anonymous; the caller must explicitly `logout()` to revert (#1113 review).\n */\n isAnonymous(): boolean {\n return this._anonymous;\n }\n\n private _log(...args: unknown[]): void {\n if (this._debug) console.log('[GeonicDB]', ...args);\n }\n\n /** Wire an SdkCache instance (called by GeonicDB constructor when caching is enabled). */\n setCache(cache: SdkCache | null): void {\n this._cache = cache;\n // #1262: namespace persisted cache entries by tenant + principal so a reload\n // only rehydrates entries belonging to the current authenticated subject.\n cache?.setScope(this._cacheScope());\n }\n\n /**\n * #1262: persistence scope for the SDK cache \u2014 tenant + authenticated\n * principal (email when available). Keying on the principal, not just the\n * tenant, prevents a different subject sharing the same origin/tenant (another\n * tab, another SDK instance, a later login) from reading entries written by a\n * previous principal. The principal segment is empty for sessions without a\n * known email; such sessions never persist (persistence is gated on a\n * remembered DPoP session that always carries an email), so the empty segment\n * is not reachable as a write scope in practice.\n */\n private _cacheScope(): string {\n return `${this._tenant}\\u0001${this._email ?? ''}`;\n }\n\n /**\n * #1262: enable write-through persistence for the SDK cache so it survives a\n * reload. Always re-scopes to the current tenant + principal first.\n *\n * `clearFirst` drops any persisted entries left by a previous session of the\n * same principal \u2014 used on a fresh `login()`. It is `false` on\n * `restoreSession()`, where the persisted entries belong to the very session\n * being restored and are about to be rehydrated.\n */\n private async _enablePersistentCache(clearFirst: boolean): Promise<void> {\n if (!this._cache) return;\n this._cache.setScope(this._cacheScope());\n if (clearFirst) await this._cache.clearPersisted();\n this._cache.setPersistEnabled(true);\n }\n\n /**\n * #1262: disable write-through persistence and drop the persisted cache for\n * the current principal. Called on credential changes that do not persist a\n * session (`setCredentials()`, non-remembered `login()`, `logout()`),\n * mirroring the in-memory invalidation so a stale body can never outlive its\n * principal. Callers that null out `_email` must invoke this **before** doing\n * so, otherwise the scope no longer resolves to the principal being dropped.\n */\n private _disablePersistentCache(): void {\n if (!this._cache) return;\n this._cache.setScope(this._cacheScope());\n this._cache.setPersistEnabled(false);\n void this._cache.clearPersisted();\n }\n\n /** Provide the cache event emitter (forwarded to the GeonicDB EventEmitter). */\n setCacheEventEmitter(\n emitter: ((name: CacheEventName, payload: CacheEvent) => void) | null,\n ): void {\n this._emitCacheEvent = emitter;\n }\n\n /** Expose the cache for invalidation (e.g. WebSocket entity events). */\n getCache(): SdkCache | null {\n return this._cache;\n }\n\n /** Emit a cache event if a listener is wired. */\n emitCacheEvent(name: CacheEventName, payload: CacheEvent): void {\n this._emitCacheEvent?.(name, payload);\n }\n\n /**\n * Login with email and password.\n *\n * #1244: DPoP (RFC 9449) is transparent and always-on \u2014 there is no `dpop`\n * option to set. After the Bearer login succeeds, the SDK automatically\n * exchanges it for a DPoP sender-constrained session so a token leaked from\n * storage (e.g. localStorage XSS) cannot be replayed from another origin.\n *\n * When the runtime lacks Web Crypto (`crypto.subtle.generateKey`, e.g. a\n * non-HTTPS context), DPoP cannot be used and the SDK silently keeps the\n * Bearer session \u2014 login still succeeds. Callers never deal with DPoP\n * directly; pass `{ rememberSession: true }` only to keep the user signed in\n * across reloads.\n */\n async login(\n email: string,\n password: string,\n options?: LoginOptions,\n ): Promise<LoginResponse> {\n this._log('login', email);\n\n const wantsRemember = options?.rememberSession === true;\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (this._tenant) headers['NGSILD-Tenant'] = this._tenant;\n\n const loginBody: Record<string, string> = { email, password };\n // #1532: \u4E00\u6642\u30D1\u30B9\u30EF\u30FC\u30C9\u3067\u306E\u521D\u56DE\u30ED\u30B0\u30A4\u30F3\u6642\u306B\u65B0\u30D1\u30B9\u30EF\u30FC\u30C9\u3092\u540C\u68B1\u3059\u308B (\u5358\u767A\u578B)\u3002\n if (options?.newPassword !== undefined) loginBody.newPassword = options.newPassword;\n\n const res = await fetch(this._baseUrl + '/auth/login', {\n method: 'POST',\n headers,\n body: JSON.stringify(loginBody),\n });\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n const message = e.detail || e.description || 'Login failed: ' + res.status;\n // #1532: \u5F37\u5236\u30D1\u30B9\u30EF\u30FC\u30C9\u5909\u66F4\u30D5\u30ED\u30FC\u3092\u6A5F\u68B0\u53EF\u8AAD\u30AD\u30FC\u3067\u5224\u5225\u3057\u3001\u5C02\u7528\u30A8\u30E9\u30FC\u3092\u6295\u3052\u308B\u3002\n // \u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u306F PasswordResetRequiredError \u3092\u6355\u6349\u3057\u3066\u65B0\u30D1\u30B9\u30EF\u30FC\u30C9\u3092\u96C6\u3081\u3001\n // login(email, tempPassword, { newPassword }) \u3092\u547C\u3073\u76F4\u3059\u3002\n const code = e.error || e.type || e.title || '';\n if (res.status === 409 && code.includes('PasswordResetRequired')) {\n throw withRequest(new PasswordResetRequiredError(message), {\n method: 'POST',\n path: '/auth/login',\n });\n }\n if (res.status === 403 && code.includes('TemporaryPasswordExpired')) {\n throw withRequest(new TemporaryPasswordExpiredError(message), {\n method: 'POST',\n path: '/auth/login',\n });\n }\n throw withRequest(new AuthenticationError(message, res.status), {\n method: 'POST',\n path: '/auth/login',\n });\n }\n const data = (await res.json()) as LoginResponse;\n // #1113 review: \u8A8D\u8A3C\u6210\u529F\u3067\u660E\u793A\u7684\u306B anonymous \u30E2\u30FC\u30C9\u3092\u629C\u3051\u308B\u3002\n this._anonymous = false;\n this._token = data.accessToken;\n this._tokenExpiry = Date.now() + (data.expiresIn - 60) * 1000;\n this._tokenType = 'Bearer';\n this._refreshToken = data.refreshToken;\n this._email = email;\n // #1230 review: \u524D\u56DE login \u306E _rememberSession \u304C\u6B8B\u3089\u306A\u3044\u3088\u3046\u3001\u6BCE\u56DE\u660E\u793A\u7684\u306B\n // \u4E0A\u66F8\u304D\u3059\u308B (false\u2192true / true\u2192false \u3069\u3061\u3089\u306E\u9077\u79FB\u306B\u3082\u8FFD\u968F)\u3002\n this._rememberSession = wantsRemember;\n // #1043 review (Critical): drop cache + dedup state from any previous identity.\n this._invalidateAuthScopedState();\n // #1262: pause persistent cache writes during the credential transition.\n // The terminal branch below either re-enables it (remembered session) or\n // disables + flushes it (everything else).\n this._cache?.setPersistEnabled(false);\n\n // #1244 review: rememberSession \u3092\u660E\u793A\u7684\u306B\u30AA\u30D5 (\u7701\u7565\u542B\u3080) \u3067\u30ED\u30B0\u30A4\u30F3\u3057\u305F\u5834\u5408\u3001\n // \u904E\u53BB\u306B\u6C38\u7D9A\u5316\u3055\u308C\u305F\u540C tenant \u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u3092\u7834\u68C4\u3059\u308B\u3002\u6B8B\u3059\u3068 restoreSession()\n // \u3067\u53E4\u3044\u30BB\u30C3\u30B7\u30E7\u30F3\u304C\u5FA9\u6D3B\u3057\u3001\u5171\u6709\u7AEF\u672B\u3067\u610F\u56F3\u3057\u306A\u3044\u518D\u30ED\u30B0\u30A4\u30F3\u3092\u62DB\u304F\u3002\n if (!wantsRemember && this._tenant) {\n void clearDPoPSession(this._tenant);\n }\n\n // #1244: DPoP \u3092\u5E38\u6642\u30AA\u30F3\u3067\u900F\u904E\u7684\u306B\u9069\u7528\u3059\u308B\u3002\u9375\u751F\u6210\u304C\u5B8C\u4E86\u3057\u3066\u3044\u3066\u9375\u30DA\u30A2\u304C\n // \u3042\u308C\u3070\u5FC5\u305A DPoP-bound \u5316\u3059\u308B\u3002Web Crypto \u975E\u5BFE\u5FDC\u74B0\u5883 (crypto.subtle \u7121\u3057)\n // \u3067\u306F\u9375\u30DA\u30A2\u304C\u751F\u6210\u3055\u308C\u306A\u3044\u305F\u3081\u3001\u9ED9\u3063\u3066 Bearer \u306E\u307E\u307E\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u3059\u308B\n // \u2014 login \u81EA\u4F53\u306F\u6210\u529F\u3059\u308B\u3002\u958B\u767A\u8005\u306F DPoP \u3092\u610F\u8B58\u3057\u306A\u3044\u3002\n if (this._dpopReady) await this._dpopReady;\n if (this._dpopKeyPair) {\n try {\n const bound = await this._bindDpopSession();\n // \u9375\u3092 IndexedDB \u306B\u6C38\u7D9A\u5316\u3057\u3001\u30EA\u30ED\u30FC\u30C9\u5F8C\u3082 restoreSession() \u3067\u540C\u3058 DPoP\n // \u30BB\u30C3\u30B7\u30E7\u30F3\u3092\u7D99\u7D9A\u3067\u304D\u308B\u3088\u3046\u306B\u3059\u308B\u3002\u6C38\u7D9A\u5316\u5931\u6557 (IndexedDB \u5229\u7528\u4E0D\u53EF\u306A\u3069)\n // \u306F\u30ED\u30B0\u30A4\u30F3\u81EA\u4F53\u3092\u59A8\u3052\u306A\u3044 \u2014 \u30EA\u30ED\u30FC\u30C9\u6642\u306B\u518D\u30ED\u30B0\u30A4\u30F3\u304C\u5FC5\u8981\u306B\u306A\u308B\u3060\u3051\u3002\n if (this._rememberSession) {\n await this._persistDpopSession();\n // #1262: persist the ETag cache for this remembered session so the\n // 304 path survives a reload. Drop any entries left by a previous\n // principal on this tenant before enabling write-through.\n await this._enablePersistentCache(true);\n } else {\n // #1262: non-remembered session keeps the cache in-memory only; drop\n // any persisted entries so they cannot leak into a later session.\n this._disablePersistentCache();\n }\n return bound;\n } catch (err) {\n // #1244 review: \u9375\u30DA\u30A2\u304C\u3042\u3063\u3066 DPoP \u30D0\u30A4\u30F3\u30C9\u3092\u300C\u8A66\u307F\u3066\u5931\u6557\u300D\u3057\u305F\u5834\u5408\u306F\n // fail-CLOSED \u306B\u3059\u308B\u3002Bearer \u8CC7\u683C\u60C5\u5831\u3092 state \u306B\u6B8B\u3059\u3068\u5F8C\u7D9A\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\n // Bearer \u3067\u6210\u7ACB\u3059\u308B\u30C0\u30A6\u30F3\u30B0\u30EC\u30FC\u30C9\u306B\u306A\u308B\u305F\u3081\u3001\u4ECA\u56DE\u306E login \u3067\u8A2D\u5B9A\u3057\u305F\n // \u8A8D\u8A3C state \u3092\u7834\u68C4\u3057\u3066\u304B\u3089\u518D throw \u3059\u308B\u3002crypto \u975E\u5BFE\u5FDC\u3067\u300C\u305D\u3082\u305D\u3082\n // \u8A66\u307F\u306A\u3044\u300D\u30B0\u30EC\u30FC\u30B9\u30D5\u30EB\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF (\u4E0B\u306E return data) \u3068\u306F\u533A\u5225\u3059\u308B\u3002\n this._token = null;\n this._tokenExpiry = 0;\n this._tokenType = 'Bearer';\n this._refreshToken = null;\n this._tokenPromise = null;\n this._invalidateAuthScopedState();\n // #1262: drop the persisted cache while `_email` still resolves to the\n // failed-login principal, then clear the remembered-session state.\n this._disablePersistentCache();\n this._rememberSession = false;\n this._email = null;\n throw err;\n }\n }\n // crypto.subtle \u4E0D\u5728 \u2192 DPoP \u4E0D\u53EF\u3002Bearer \u30BB\u30C3\u30B7\u30E7\u30F3\u306F sender-constrained \u306B\n // \u3067\u304D\u306A\u3044\u305F\u3081\u6C38\u7D9A\u5316\u3082\u3057\u306A\u3044 (rememberSession \u306F no-op)\u3002\n // #1244 review (#1262): rememberSession \u3092\u660E\u793A\u8981\u6C42\u3057\u3066\u3082 DPoP \u9375\u304C\u7121\u3044\u305F\u3081\n // \u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u6C38\u7D9A\u5316\u3067\u304D\u306A\u3044\u3002\u53E4\u3044\u6C38\u7D9A DPoP \u30BB\u30C3\u30B7\u30E7\u30F3\u304C\u6B8B\u3063\u3066\u3044\u308B\u3068\u5F8C\u7D9A\u306E\n // restoreSession() \u3067\u5225 principal \u304C\u5FA9\u6D3B\u3057\u5F97\u308B\u305F\u3081\u3001\u7121\u6761\u4EF6\u3067\u7834\u68C4\u3059\u308B\u3002\n this._rememberSession = false;\n if (this._tenant) {\n void clearDPoPSession(this._tenant);\n }\n // #1262: \u540C\u69D8\u306B\u30AD\u30E3\u30C3\u30B7\u30E5\u6C38\u7D9A\u5316\u3082\u7121\u52B9\u5316\u3057\u3001\u904E\u53BB\u306E\u6C38\u7D9A\u30A8\u30F3\u30C8\u30EA\u3092\u7834\u68C4\u3059\u308B\u3002\n this._disablePersistentCache();\n return data;\n }\n\n /**\n * Exchange the current Bearer session for a DPoP-bound session via\n * `POST /auth/dpop-bind` (RFC 9449). The server re-issues access and\n * refresh tokens with `cnf.jkt` matching this SDK's DPoP key pair.\n *\n * Assumes `_token` / `_refreshToken` are already populated by a prior\n * `login()` call (callers should not invoke this directly).\n *\n * @internal\n */\n private async _bindDpopSession(): Promise<LoginResponse> {\n if (!this._token) {\n throw new AuthenticationError(\n 'Cannot bind DPoP session: no Bearer token. login() must succeed first.',\n );\n }\n if (this._dpopReady) await this._dpopReady;\n if (!this._dpopKeyPair) {\n throw new AuthenticationError(\n 'DPoP binding requires a generated DPoP key pair, but key generation ' +\n 'was unavailable or failed. DPoP requires Web Crypto subtle.generateKey.',\n );\n }\n\n const bindUrl = this._baseUrl + '/auth/dpop-bind';\n const accessToken = this._token;\n const data = await this._doDpopBind(bindUrl, accessToken, this._dpopNonce, 0);\n\n // \u6210\u529F: \u30C8\u30FC\u30AF\u30F3\u3092 DPoP-bound \u7248\u306B\u5DEE\u3057\u66FF\u3048\u308B\u3002tokenType \u30B5\u30FC\u30D0\u5FDC\u7B54\n // (`'DPoP'`) \u3092\u4FE1\u983C\u3057\u3064\u3064\u3001\u30B5\u30FC\u30D0\u304C\u53E4\u304F\u3066\u672A\u8A2D\u5B9A\u3060\u3063\u305F\u5834\u5408\u306B\u3082\n // \u52D5\u4F5C\u3059\u308B\u3088\u3046\u660E\u793A\u7684\u306B 'DPoP' \u3067\u4E0A\u66F8\u304D\u3059\u308B\u3002\n this._token = data.accessToken;\n this._tokenExpiry = Date.now() + (data.expiresIn - 60) * 1000;\n this._tokenType = 'DPoP';\n this._refreshToken = data.refreshToken;\n // #1043 review: identity \u304C\u540C\u4E00\u3067\u3082 tokenType \u304C\u5909\u308F\u308B\u305F\u3081\u3001Bearer \u6642\u4EE3\u306E\n // \u30AD\u30E3\u30C3\u30B7\u30E5\u30FBdedup \u306F\u7834\u68C4\u3059\u308B\u3002\n this._invalidateAuthScopedState();\n return { ...data, tokenType: 'DPoP' };\n }\n\n private async _doDpopBind(\n bindUrl: string,\n accessToken: string,\n dpopNonce: string | null,\n retryCount: number,\n ): Promise<LoginResponse> {\n const dpopProof = await createDPoPProof(\n this._dpopKeyPair,\n 'POST',\n bindUrl,\n accessToken,\n dpopNonce,\n );\n if (!dpopProof) {\n // _dpopKeyPair is guaranteed non-null by the caller, but TS narrowing\n // does not propagate through createDPoPProof's signature.\n throw new AuthenticationError('DPoP proof generation failed');\n }\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Authorization: 'Bearer ' + accessToken,\n DPoP: dpopProof,\n };\n if (this._tenant) headers['NGSILD-Tenant'] = this._tenant;\n\n const res = await fetch(bindUrl, { method: 'POST', headers });\n\n const serverNonce = res.headers.get('DPoP-Nonce');\n if (serverNonce) this._dpopNonce = serverNonce;\n\n // RFC 9449 \u00A78: nonce \u5FC5\u9808 \u2192 DPoP-Nonce \u30D8\u30C3\u30C0\u30FC\u3092\u53D6\u3063\u3066\u518D\u8A66\u884C\n if (\n res.status === 401 &&\n serverNonce &&\n serverNonce !== dpopNonce &&\n retryCount < AuthManager.MAX_DPOP_RETRIES\n ) {\n return this._doDpopBind(bindUrl, accessToken, serverNonce, retryCount + 1);\n }\n\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n throw withRequest(\n new AuthenticationError(\n e.detail ||\n e.description ||\n 'DPoP bind failed: ' + res.status,\n res.status,\n ),\n { method: 'POST', path: '/auth/dpop-bind' },\n );\n }\n\n return (await res.json()) as LoginResponse;\n }\n\n /**\n * Set credentials externally (e.g. from a login API response).\n * When tokenType is 'Bearer' and refreshToken is provided, all subsequent\n * API calls and connect() bypass DPoP/PoW entirely.\n */\n setCredentials(opts: CredentialsOptions): void {\n if (!opts || !opts.token) throw new Error('token is required');\n // #1113 review: \u533F\u540D\u69CB\u7BC9\u30A4\u30F3\u30B9\u30BF\u30F3\u30B9\u306F DPoP \u9375\u30DA\u30A2\u751F\u6210\u81EA\u4F53\u3092 skip \u3059\u308B\u306E\u3067\n // `tokenType: 'DPoP'` \u3092\u53D7\u3051\u3066\u3082 proof \u7121\u3057 Bearer \u9001\u4FE1\u306B\u6210\u308A\u4E0B\u304C\u308B\u3002\n // \u5B89\u5168\u5074\u3067\u660E\u793A\u62D2\u5426\u3059\u308B\u3002\n //\n // \u6CE8: `_dpopKeyPair` \u306E\u6709\u7121\u3067\u5224\u5B9A\u3059\u308B\u3068\u3001\u975E\u533F\u540D\u30A4\u30F3\u30B9\u30BF\u30F3\u30B9\u3067\u3082\u69CB\u7BC9\u76F4\u5F8C\u306E\n // \u9375\u751F\u6210\u5B8C\u4E86\u524D (`_dpopReady` \u672A\u89E3\u6C7A) \u306B\u547C\u3070\u308C\u305F\u5834\u5408\u306B\u8AA4\u3063\u3066\u5F3E\u3044\u3066\u3057\u307E\u3046\u3002\n // \u3053\u3053\u306F\u30B3\u30F3\u30B9\u30C8\u30E9\u30AF\u30BF\u3067\u306E\u5E0C\u671B\u30E2\u30FC\u30C9 `_initialAnonymous` \u3067\u5224\u5B9A\u3059\u308B\n // (#1113 review-2)\u3002\n if (this._initialAnonymous && opts.tokenType === 'DPoP') {\n throw new Error(\n 'DPoP credentials cannot be set on an SDK instance constructed with ' +\n '`anonymous: true` (no DPoP key pair is generated for anonymous instances). ' +\n 'Use Bearer credentials, or construct the SDK in non-anonymous mode.',\n );\n }\n // #1113 review: \u8A8D\u8A3C\u60C5\u5831\u6CE8\u5165\u3067\u660E\u793A\u7684\u306B anonymous \u30E2\u30FC\u30C9\u3092\u629C\u3051\u308B\u3002\n this._anonymous = false;\n this._token = opts.token;\n this._tokenType = opts.tokenType || 'Bearer';\n this._tokenExpiry = opts.expiresIn != null\n ? Date.now() + (opts.expiresIn - 60) * 1000\n : Date.now() + DEFAULT_TOKEN_TTL_SEC * 1000;\n this._refreshToken = opts.refreshToken || null;\n this._tokenPromise = null;\n // #1043 review (Critical): cache + in-flight dedup is keyed only by\n // METHOD:path, so credentials changes must invalidate them to prevent\n // cross-user response reuse.\n this._invalidateAuthScopedState();\n // #1262: externally-injected credentials are not a persisted DPoP session,\n // so keep the cache in-memory only and drop the previous principal's\n // persisted entries \u2014 done while `_email` still resolves to that principal.\n this._disablePersistentCache();\n // #1262 review: also reset the remembered-session state. Otherwise a stale\n // `_rememberSession === true` / `_email` from a prior `login()` would make\n // the next token refresh persist these injected credentials into a DPoP\n // session (see `_refreshBearerToken`), and `restoreSession()` could later\n // resurrect a principal the caller never intended to persist.\n this._rememberSession = false;\n this._email = null;\n if (this._tenant) {\n void clearDPoPSession(this._tenant);\n }\n }\n\n /** Clear all credentials. */\n logout(): void {\n this._token = null;\n this._tokenExpiry = 0;\n this._refreshToken = null;\n this._tokenPromise = null;\n // #1262: flush the persisted ETag cache while `_email` still resolves to the\n // principal being logged out (the scope is tenant + principal), then null\n // it. Done before `_invalidateAuthScopedState()` ordering does not matter\n // for the in-memory side, but the persisted scope must be computed first.\n this._disablePersistentCache();\n this._email = null;\n // #1230 / #1244 review: \u6C38\u7D9A\u5316\u3055\u308C\u305F\u30BB\u30C3\u30B7\u30E7\u30F3\u3092 **\u7121\u6761\u4EF6\u3067** \u7834\u68C4\u3059\u308B\u3002\n // `_rememberSession` \u6761\u4EF6\u3092\u4ED8\u3051\u308B\u3068\u3001\u5225\u306E login \u3067 rememberSession \u3092\n // \u30AA\u30D5\u306B\u3057\u305F\u5F8C (= \u30D5\u30E9\u30B0\u304C false) \u3067\u3082\u904E\u53BB\u306E\u6C38\u7D9A\u30A8\u30F3\u30C8\u30EA\u304C\u6B8B\u308A\u3001\n // restoreSession() \u3067\u5FA9\u6D3B\u3059\u308B\u4F59\u5730\u304C\u751F\u307E\u308C\u308B\u3002clearDPoPSession \u306F\u51AA\u7B49\u3067\n // \u30A8\u30F3\u30C8\u30EA\u7121\u3057\u3067\u3082 no-op \u306A\u306E\u3067\u3001tenant \u304C\u3042\u308C\u3070\u5E38\u306B\u6D88\u3059\u3002\u5931\u6557\u3057\u3066\u3082\n // logout \u81EA\u4F53\u306F\u5B8C\u4E86\u6271\u3044 (await \u3057\u306A\u3044)\u3002\n if (this._tenant) {\n void clearDPoPSession(this._tenant);\n }\n this._rememberSession = false;\n // #1113 review: \u30B3\u30F3\u30B9\u30C8\u30E9\u30AF\u30BF\u3067 anonymous: true \u6307\u5B9A\u306E\u30A4\u30F3\u30B9\u30BF\u30F3\u30B9\u306E\u307F\n // logout() \u3067 anonymous \u30E2\u30FC\u30C9\u306B\u5FA9\u5E30\u3059\u308B\u3002\u305D\u308C\u4EE5\u5916\u306F false \u306E\u307E\u307E\u3002\n this._anonymous = this._initialAnonymous;\n this._invalidateAuthScopedState();\n }\n\n /**\n * Restore a session previously persisted via\n * `login(email, password, { rememberSession: true })`. Returns true if a\n * usable session was found and loaded into the\n * SDK, false otherwise (no entry, IndexedDB unavailable, or the entry was\n * for a different tenant).\n *\n * After a successful restore the SDK behaves as if `login()` had just\n * succeeded: tokens are set, the DPoP key pair is rehydrated as a\n * non-extractable `CryptoKey`, and subsequent requests automatically\n * carry a fresh DPoP proof. Token refresh continues to flow through\n * `/auth/refresh` and emits `tokenRefresh` events as usual.\n *\n * Token expiry is NOT validated here \u2014 the next `ensureToken()` call will\n * trigger a refresh if the access token has expired (#1230). An expired\n * refresh token will manifest as an `AuthenticationError` from that\n * refresh attempt; consumers should treat such errors as \"session ended,\n * fall back to login()\".\n */\n async restoreSession(): Promise<boolean> {\n if (this._initialAnonymous) return false;\n if (!this._tenant) return false;\n const persisted = await loadDPoPSession(this._tenant);\n if (!persisted) return false;\n\n // \u5225 tenant \u306E entry \u3092\u9593\u9055\u3063\u3066\u8AAD\u307F\u51FA\u3059\u3053\u3068\u306F getter \u3067\u9632\u3044\u3067\u3044\u308B\u304C\u3001\n // \u5FF5\u306E\u305F\u3081\u4E00\u81F4\u78BA\u8A8D\u3002\n if (persisted.tenant !== this._tenant) return false;\n\n this._anonymous = false;\n this._token = persisted.accessToken;\n this._refreshToken = persisted.refreshToken;\n this._tokenExpiry = persisted.expiresAt;\n this._tokenType = 'DPoP';\n this._dpopKeyPair = persisted.keyPair;\n // \u9375\u751F\u6210 Promise \u306F\u3082\u3046\u5F85\u3064\u5FC5\u8981\u304C\u306A\u3044 (\u9375\u306F IDB \u304B\u3089\u5FA9\u5143\u6E08\u307F)\u3002\n this._dpopReady = Promise.resolve();\n this._dpopNonce = null;\n this._rememberSession = true;\n this._email = persisted.email ?? null;\n this._tokenPromise = null;\n this._invalidateAuthScopedState();\n // #1262: the restored session is the same principal that persisted the\n // cache, so re-enable write-through (without clearing) and rehydrate the\n // entries \u2014 the first request after a reload can now send If-None-Match.\n await this._enablePersistentCache(false);\n await this._cache?.hydrate();\n return true;\n }\n\n /**\n * Whether this AuthManager currently keeps the session persisted across\n * reloads (`rememberSession`). Exposed for the `GeonicDB` wrapper; internal\n * consumers should read `_rememberSession` directly.\n */\n isSessionRemembered(): boolean {\n return this._rememberSession;\n }\n\n /**\n * Persist the current DPoP session to IndexedDB. Best-effort: a failed\n * write is logged in debug mode but never propagated, since persistence\n * is purely a UX optimization.\n *\n * @internal\n */\n private async _persistDpopSession(): Promise<void> {\n if (\n !this._rememberSession ||\n !this._tenant ||\n !this._dpopKeyPair ||\n !this._token ||\n !this._refreshToken\n ) {\n return;\n }\n const ok = await saveDPoPSession({\n tenant: this._tenant,\n accessToken: this._token,\n refreshToken: this._refreshToken,\n expiresAt: this._tokenExpiry,\n email: this._email ?? undefined,\n keyPair: this._dpopKeyPair,\n });\n if (!ok) this._log('DPoP session persistence failed (IndexedDB unavailable?)');\n }\n\n /**\n * Drop everything that may have been associated with the previous auth\n * context. Called on `login()` (after token assignment), `setCredentials()`,\n * and `logout()`. Without this, a cached body or in-flight Response from\n * user A could be returned to user B after a credentials swap.\n */\n private _invalidateAuthScopedState(): void {\n this._cache?.clear();\n this._inFlight.clear();\n }\n\n /**\n * Ensure a valid token is available, refreshing or acquiring as needed.\n *\n * #1105: In anonymous mode (no apiKey, no Bearer credentials) this throws\n * `AuthenticationError`. Callers that may run anonymously should branch on\n * `isAnonymous()` first instead of calling `ensureToken()` blindly.\n */\n async ensureToken(): Promise<string> {\n if (this._token && Date.now() < this._tokenExpiry) {\n return this._token;\n }\n\n // Deduplicate concurrent calls\n if (this._tokenPromise) {\n return this._tokenPromise;\n }\n\n // Bearer JWT refresh path \u2014 skip DPoP/PoW\n if (this._refreshToken) {\n this._tokenPromise = this._refreshBearerToken();\n return this._tokenPromise;\n }\n\n // anonymous \u30E2\u30FC\u30C9\u3067\u30C8\u30FC\u30AF\u30F3\u7121\u3057 \u2192 \u53D6\u5F97\u7D4C\u8DEF\u3092\u6301\u305F\u306A\u3044\n if (this._anonymous) {\n throw new AuthenticationError(\n 'Anonymous mode: no token available. Call login() or setCredentials() to authenticate.',\n );\n }\n\n // #1113 review: refresh \u5931\u6557\u3067 `_refreshToken` \u304C null \u306B\u306A\u3063\u305F Bearer-only\n // \u30A4\u30F3\u30B9\u30BF\u30F3\u30B9\u3084\u3001apiKey \u304C\u7A7A\u306E\u307E\u307E PoW \u30D5\u30ED\u30FC\u3078\u843D\u3061\u308B\u3053\u3068\u3092\u9632\u3050\u3002\n // \u3053\u3053\u3067\u660E\u793A\u7684\u306B\u5931\u6557\u3055\u305B\u306A\u3044\u3068 `_acquireTokenViaPow()` \u304C `api_key: ''` \u3067\n // `/auth/nonce` \u3092\u53E9\u3044\u3066\u3057\u307E\u3046\u3002\n if (!this._apiKey) {\n throw new AuthenticationError(\n 'No authentication method available. Call login() / setCredentials() to authenticate, or construct the SDK with an apiKey.',\n );\n }\n\n // API key + DPoP/PoW path\n /* istanbul ignore next: full PoW + DPoP token-exchange path requires the\n live /auth/nonce + /oauth/token endpoints \u2014 covered by E2E. */\n this._tokenPromise = this._acquireTokenViaPow();\n /* istanbul ignore next */\n return this._tokenPromise;\n }\n\n private async _refreshBearerToken(): Promise<string> {\n this._log('refreshing token (type=%s)', this._tokenType);\n try {\n const isDPoP = this._tokenType === 'DPoP';\n const refreshUrl = this._baseUrl + '/auth/refresh';\n const body = JSON.stringify({ refreshToken: this._refreshToken });\n\n // #1221: DPoP-bound refresh \u306F proof \u5FC5\u9808 (RFC 9449)\u3002tokenType \u306F\n // \u524D\u56DE issue \u6642\u306B\u30B5\u30FC\u30D0\u5FDC\u7B54\u3067\u78BA\u5B9A\u6E08\u307F\u306A\u306E\u3067\u3001\u305D\u308C\u3092\u4FE1\u983C\u3059\u308B\u3002\n const res = isDPoP\n ? await this._doDpopRefresh(refreshUrl, body, this._dpopNonce, 0)\n : await fetch(refreshUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n });\n\n if (!res.ok) {\n this._refreshToken = null;\n this._token = null;\n this._tokenPromise = null;\n throw new AuthenticationError('Token refresh failed: ' + res.status, res.status);\n }\n const data = (await res.json()) as LoginResponse;\n this._token = data.accessToken;\n this._tokenExpiry = Date.now() + (data.expiresIn - 60) * 1000;\n // \u30B5\u30FC\u30D0\u5FDC\u7B54\u306E tokenType \u3092\u4FE1\u983C\u3059\u308B\u3002Bearer\u2192DPoP \u306E\u7247\u9053\u79FB\u884C\u306E\u307F\u3067\u3001\n // DPoP-bound refresh \u304C Bearer \u306B\u964D\u683C\u3059\u308B\u3053\u3068\u306F\u306A\u3044 (\u672C\u4F53\u5074\u306F\n // boundJkt \u304C\u3042\u308C\u3070\u5E38\u306B 'DPoP' \u3092\u8FD4\u3059)\u3002\n this._tokenType = data.tokenType ?? (isDPoP ? 'DPoP' : 'Bearer');\n this._refreshToken = data.refreshToken;\n this._tokenPromise = null;\n this.onTokenRefresh?.({\n token: this._token,\n tokenType: this._tokenType,\n expiresIn: data.expiresIn,\n refreshToken: this._refreshToken,\n });\n // #1230: DPoP \u30BB\u30C3\u30B7\u30E7\u30F3\u3092\u6C38\u7D9A\u5316\u3057\u3066\u3044\u308B\u5834\u5408\u306F\u3001\u65B0\u3057\u3044\u30C8\u30FC\u30AF\u30F3\u3092\n // IDB \u306B\u66F8\u304D\u623B\u3059\u3002\u66F8\u304D\u8FBC\u307F\u306F\u975E\u540C\u671F\u3060\u304C await \u3057\u306A\u3044 (\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\n // \u7D4C\u8DEF\u306E\u6240\u8981\u6642\u9593\u306B\u3076\u3089\u4E0B\u3052\u306A\u3044)\u3002\u5931\u6557\u306F best-effort\u3002\n if (this._rememberSession) void this._persistDpopSession();\n return this._token;\n } catch (err) {\n this._tokenPromise = null;\n throw err;\n }\n }\n\n /**\n * Perform a DPoP-bound `/auth/refresh` call with the standard RFC 9449 \u00A78\n * `use_dpop_nonce` retry handshake.\n *\n * @internal\n */\n private async _doDpopRefresh(\n refreshUrl: string,\n body: string,\n dpopNonce: string | null,\n retryCount: number,\n ): Promise<Response> {\n if (this._dpopReady) await this._dpopReady;\n if (!this._dpopKeyPair) {\n throw new AuthenticationError(\n 'DPoP-bound refresh requires a DPoP key pair, but key generation was unavailable.',\n );\n }\n const proof = await createDPoPProof(\n this._dpopKeyPair,\n 'POST',\n refreshUrl,\n null,\n dpopNonce,\n );\n if (!proof) {\n throw new AuthenticationError('DPoP proof generation failed');\n }\n const res = await fetch(refreshUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', DPoP: proof },\n body,\n });\n const serverNonce = res.headers.get('DPoP-Nonce');\n if (serverNonce) this._dpopNonce = serverNonce;\n if (\n res.status === 401 &&\n serverNonce &&\n serverNonce !== dpopNonce &&\n retryCount < AuthManager.MAX_DPOP_RETRIES\n ) {\n return this._doDpopRefresh(refreshUrl, body, serverNonce, retryCount + 1);\n }\n return res;\n }\n\n /* istanbul ignore next: PoW + DPoP token-exchange flow is exercised by E2E\n against a live server. Unit-mocking each fetch + crypto step would not\n verify the actual nonce/proof exchange. */\n private async _acquireTokenViaPow(): Promise<string> {\n try {\n if (this._dpopReady) await this._dpopReady;\n\n // Step 1: Get nonce + PoW challenge\n const nonceRes = await fetch(this._baseUrl + '/auth/nonce', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ api_key: this._apiKey }),\n });\n if (!nonceRes.ok)\n throw new AuthenticationError('Nonce request failed: ' + nonceRes.status, nonceRes.status);\n const nonceData = (await nonceRes.json()) as {\n nonce: string;\n challenge: string;\n difficulty: number;\n dpop_nonce?: string;\n };\n\n // RFC 9449 \u00A78: store pre-issued DPoP-Nonce\n if (nonceData.dpop_nonce) this._dpopNonce = nonceData.dpop_nonce;\n\n // Step 2: Solve PoW\n const proof = await solvePoW(nonceData.challenge, nonceData.difficulty);\n\n // Step 3: Exchange for token\n const tokenUrl = this._baseUrl + '/oauth/token';\n const tokenBody = JSON.stringify({\n grant_type: 'api_key',\n api_key: this._apiKey,\n nonce: nonceData.nonce,\n proof: String(proof),\n });\n\n const res = await this._doTokenExchange(tokenUrl, tokenBody, this._dpopNonce);\n\n if (!res.ok)\n throw new AuthenticationError('Token request failed: ' + res.status, res.status);\n const newNonce = res.headers.get('DPoP-Nonce');\n if (newNonce) this._dpopNonce = newNonce;\n\n const data = (await res.json()) as {\n access_token: string;\n expires_in: number;\n token_type?: string;\n };\n this._token = data.access_token;\n this._tokenExpiry = Date.now() + (data.expires_in - 60) * 1000;\n this._tokenType = (data.token_type as 'Bearer' | 'DPoP') || 'Bearer';\n this._tokenPromise = null;\n return this._token;\n } catch (err) {\n this._tokenPromise = null;\n throw err;\n }\n }\n\n private static readonly MAX_DPOP_RETRIES = 3;\n\n /* istanbul ignore next: DPoP nonce-retry handshake (RFC 9449 \u00A78) \u2014 requires\n a real server emitting `use_dpop_nonce` 400s. Exercised by E2E. */\n private async _doTokenExchange(\n tokenUrl: string,\n tokenBody: string,\n dpopNonce: string | null,\n retryCount = 0,\n ): Promise<Response> {\n const reqHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n const dpopProof = await createDPoPProof(\n this._dpopKeyPair,\n 'POST',\n tokenUrl,\n null,\n dpopNonce,\n );\n if (dpopProof) reqHeaders['DPoP'] = dpopProof;\n\n const res = await fetch(tokenUrl, {\n method: 'POST',\n headers: reqHeaders,\n body: tokenBody,\n });\n\n // RFC 9449 \u00A78: use_dpop_nonce \u2192 retry with server nonce\n if (res.status === 400 && this._dpopKeyPair) {\n const errBody = await res.json().catch(() => ({})) as Record<string, string>;\n if (errBody.error === 'use_dpop_nonce') {\n const serverNonce = res.headers.get('DPoP-Nonce');\n if (serverNonce && serverNonce !== dpopNonce && retryCount < AuthManager.MAX_DPOP_RETRIES) {\n this._dpopNonce = serverNonce;\n return this._doTokenExchange(tokenUrl, tokenBody, serverNonce, retryCount + 1);\n }\n }\n throw new AuthenticationError(\n 'Token request failed: ' +\n (errBody.error_description || errBody.error || '400'),\n 400,\n );\n }\n\n return res;\n }\n\n /**\n * Make an authenticated HTTP request with automatic token refresh and DPoP.\n *\n * When the SDK cache is enabled and the request is cacheable\n * (GET / HEAD without a body), this method:\n * - Sends `If-None-Match` / `If-Modified-Since` derived from a previously\n * cached entry, if any.\n * - Returns the cached body wrapped in a synthesized `200` Response when\n * the server replies `304 Not Modified`.\n * - Persists fresh `200` responses (with their ETag/Last-Modified) into\n * the cache.\n * - Deduplicates concurrent in-flight requests to the same path.\n */\n async request(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<Response> {\n this._log(method, path);\n\n if (!this._cache || !isCacheableMethod(method) || body !== undefined) {\n const token = this.isAnonymous() ? null : await this.ensureToken();\n const res = await this._doAuthenticatedRequest(method, path, body, token);\n this._log(method, path, '\u2192', res.status);\n return res;\n }\n\n const key = SdkCache.keyFor(method, path);\n\n // Request deduplication: if the same path is already in flight,\n // return the same Response (cloned) so concurrent callers share the result.\n const inFlight = this._inFlight.get(key);\n if (inFlight) {\n return inFlight.then((res) => res.clone());\n }\n\n const promise = this._cachedRequest(method, path, key);\n this._inFlight.set(key, promise);\n try {\n return await promise;\n } finally {\n this._inFlight.delete(key);\n }\n }\n\n private async _cachedRequest(\n method: string,\n path: string,\n key: string,\n ): Promise<Response> {\n const cache = this._cache;\n /* istanbul ignore if: defensive guard \u2014 request() already gates on this._cache. */\n if (!cache) {\n // Defensive: caller already checked, but TS narrowing prefers an explicit guard.\n const token = this.isAnonymous() ? null : await this.ensureToken();\n return this._doAuthenticatedRequest(method, path, undefined, token);\n }\n const cached = cache.get(key);\n const conditional: Record<string, string> = {};\n if (cached?.etag) conditional['If-None-Match'] = cached.etag;\n if (cached?.lastModified) conditional['If-Modified-Since'] = cached.lastModified;\n\n const token = this.isAnonymous() ? null : await this.ensureToken();\n const res = await this._doAuthenticatedRequest(method, path, undefined, token, 0, conditional);\n this._log(method, path, '\u2192', res.status);\n\n // 304 Not Modified \u2014 replay the cached body as a synthetic 200.\n // The SDK consumer should be unaware of conditional negotiation.\n //\n // The 304 response itself may carry refreshed validators (ETag /\n // Last-Modified rotation, updated Cache-Control etc.). Merge them into\n // the cached entry so the next request sends the latest If-None-Match.\n if (res.status === 304 && cached) {\n const refreshedHeaders = { ...cached.headers, ...snapshotHeaders(res.headers) };\n const refreshedEtag = res.headers.get('etag') ?? cached.etag;\n const refreshedLastModified = res.headers.get('last-modified') ?? cached.lastModified;\n cache.set(key, {\n ...cached,\n etag: refreshedEtag,\n lastModified: refreshedLastModified,\n headers: refreshedHeaders,\n cachedAt: Date.now(),\n });\n this.emitCacheEvent('cacheHit', { key, path });\n const body = typeof cached.data === 'string' ? cached.data : JSON.stringify(cached.data);\n return new Response(body, { status: 200, headers: refreshedHeaders });\n }\n\n // Persist fresh 200 responses that carry a validator.\n if (res.status === 200) {\n const etag = res.headers.get('etag') ?? undefined;\n const lastModified = res.headers.get('last-modified') ?? undefined;\n if (etag || lastModified) {\n const text = await res.clone().text();\n let data: unknown = text;\n try {\n data = JSON.parse(text);\n } catch {\n // not JSON \u2014 keep the raw text\n }\n cache.set(key, {\n etag,\n lastModified,\n data,\n headers: snapshotHeaders(res.headers),\n cachedAt: Date.now(),\n });\n this.emitCacheEvent('cacheMiss', { key, path });\n }\n }\n\n return res;\n }\n\n private static readonly MAX_REQUEST_RETRIES = 3;\n\n private async _doAuthenticatedRequest(\n method: string,\n path: string,\n body: unknown,\n token: string | null,\n retryCount = 0,\n extraHeaders: Record<string, string> = {},\n ): Promise<Response> {\n // #1113 review-3: \u975E\u533F\u540D\u30A4\u30F3\u30B9\u30BF\u30F3\u30B9\u3067 `setCredentials({ tokenType: 'DPoP' })`\n // \u3092\u53D7\u3051\u305F\u76F4\u5F8C\u306B\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u98DB\u3076\u3068\u3001\u30B3\u30F3\u30B9\u30C8\u30E9\u30AF\u30BF\u3067\u958B\u59CB\u3057\u305F\u9375\u751F\u6210\u304C\u307E\u3060\n // \u5B8C\u4E86\u3057\u3066\u3044\u306A\u3044 (`_dpopKeyPair === null`) \u53EF\u80FD\u6027\u304C\u3042\u308B\u3002`_doAuthenticatedRequest`\n // \u5185\u3067 `_dpopReady` \u3092 await \u3057\u306A\u3044\u3068\u3001DPoP \u306E\u306F\u305A\u304C proof \u7121\u3057\u306E\n // `Authorization: Bearer ...` \u3067\u9001\u3089\u308C\u3066\u3057\u307E\u3046\u3002\u9375\u751F\u6210\u304C\u5931\u6557\u3057\u3066\u3044\u305F\n // \u5834\u5408\u306F AuthenticationError \u3067 fail-closed \u3055\u305B\u308B\u3002\n /* istanbul ignore if: DPoP path requires a real Web Crypto key pair \u2014\n jsdom skips key generation (dpopSupported=false). Exercised by E2E. */\n if (this._tokenType === 'DPoP' && token !== null) {\n if (this._dpopReady) await this._dpopReady;\n if (!this._dpopKeyPair) {\n throw new AuthenticationError(\n 'DPoP credentials require a generated DPoP key pair, but key generation ' +\n 'was unavailable or failed.',\n );\n }\n }\n\n const url = this._baseUrl + path;\n /* istanbul ignore next: DPoP token type is unreachable in jsdom tests. */\n const isDPoP = this._tokenType === 'DPoP' && !!this._dpopKeyPair && token !== null;\n\n // #1583 / #1599 / #2069: every NGSI-LD write body must carry an inline @context under\n // application/ld+json (which the SDK always sends). Inject the core context when a body\n // omits it so existing callers keep working; a caller-supplied @context is preserved.\n //\n // Scope = the NGSI-LD API (`/ngsi-ld/v1/**`) \u2014 exactly where the broker applies ETSI\n // GS CIM 009 clause 6.3.5 (`assertContextSourceCompliance`, the NGSI-LD router entry).\n // It must NOT touch other endpoints (auth/admin/rules/snapshots reached via the generic\n // request() escape hatch): many use `.strict()` schemas that would reject the injected\n // @context with a 400. `/ngsi-ld/v1/jsonldContexts` is excluded too \u2014 its body *is* the\n // context document being registered.\n //\n // Array bodies (`/entityOperations/*`) get the context **per element**: each element is\n // its own JSON-LD document (clause 5.6.7.3), and the broker reports a missing one as a\n // per-entity BadRequestData inside a 207. Non-object elements are left alone \u2014\n // `entityOperations/delete` takes Entity ID strings (clause 5.6.10.3) which cannot carry\n // an @context.\n const pathname = path.split('?')[0];\n const isNgsiLdWrite =\n pathname.startsWith('/ngsi-ld/v1/') && pathname !== '/ngsi-ld/v1/jsonldContexts';\n const withCoreContext = (value: unknown): unknown =>\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n !('@context' in (value as Record<string, unknown>))\n ? { ...(value as Record<string, unknown>), '@context': NGSI_LD_CORE_CONTEXT }\n : value;\n let effectiveBody = body;\n if (isNgsiLdWrite && effectiveBody !== undefined) {\n effectiveBody = Array.isArray(effectiveBody)\n ? effectiveBody.map(withCoreContext)\n : withCoreContext(effectiveBody);\n }\n const bodyStr = effectiveBody !== undefined ? JSON.stringify(effectiveBody) : undefined;\n\n const doRequest = async (\n reqToken: string | null,\n dpopNonce: string | null,\n ): Promise<Response> => {\n const reqIsDPoP =\n this._tokenType === 'DPoP' && !!this._dpopKeyPair && reqToken !== null;\n /* istanbul ignore next: DPoP proof creation requires a real Web Crypto key. */\n const dpopProof = reqIsDPoP\n ? await createDPoPProof(\n this._dpopKeyPair,\n method,\n url,\n reqToken,\n dpopNonce,\n )\n : null;\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/ld+json',\n Accept: 'application/ld+json',\n ...extraHeaders,\n };\n // #1105: anonymous \u30E2\u30FC\u30C9\u3067\u306F Authorization \u3092\u4ED8\u3051\u306A\u3044\u3002\n if (reqToken !== null) {\n /* istanbul ignore next: DPoP authorization scheme path \u2014 covered by E2E. */\n headers['Authorization'] = (reqIsDPoP ? 'DPoP ' : 'Bearer ') + reqToken;\n }\n /* istanbul ignore next: DPoP proof header \u2014 covered by E2E. */\n if (dpopProof) headers['DPoP'] = dpopProof;\n if (this._tenant) headers['Fiware-Service'] = this._tenant;\n\n return fetch(url, { method, headers, body: bodyStr });\n };\n\n let currentToken = token;\n let res = await doRequest(currentToken, this._dpopNonce);\n const previousNonce = this._dpopNonce;\n const newNonce = res.headers.get('DPoP-Nonce');\n /* istanbul ignore if: DPoP-Nonce header only emitted by live DPoP server. */\n if (newNonce) this._dpopNonce = newNonce;\n\n // anonymous \u30EA\u30AF\u30A8\u30B9\u30C8\u306E 401 \u306F\u518D\u8A66\u884C\u3057\u306A\u3044 (XACML Deny \u2192 \u305D\u306E\u307E\u307E\u8FD4\u3059)\n const canRetry401 = currentToken !== null;\n\n // RFC 9449 \u00A78: use_dpop_nonce \u2192 retry with server nonce\n /* istanbul ignore if: DPoP nonce-rotation 401 retry requires a live DPoP\n server. The Bearer 401 retry below is unit-tested; this branch is\n covered by E2E. */\n if (res.status === 401 && isDPoP && newNonce && newNonce !== previousNonce) {\n res = await doRequest(currentToken, newNonce);\n const rn = res.headers.get('DPoP-Nonce');\n if (rn) this._dpopNonce = rn;\n if (res.status === 401 && canRetry401) {\n this._token = null;\n this._tokenPromise = null;\n currentToken = await this.ensureToken();\n res = await doRequest(currentToken, this._dpopNonce);\n const fn = res.headers.get('DPoP-Nonce');\n if (fn) this._dpopNonce = fn;\n }\n } else if (res.status === 401 && canRetry401) {\n this._token = null;\n this._tokenPromise = null;\n currentToken = await this.ensureToken();\n res = await doRequest(currentToken, this._dpopNonce);\n const rn = res.headers.get('DPoP-Nonce');\n /* istanbul ignore if: DPoP nonce header only emitted by live DPoP server. */\n if (rn) this._dpopNonce = rn;\n }\n\n if (res.status === 429 && retryCount < AuthManager.MAX_REQUEST_RETRIES) {\n const retryNonce = res.headers.get('DPoP-Nonce');\n /* istanbul ignore if: DPoP nonce header only emitted by live DPoP server. */\n if (retryNonce) this._dpopNonce = retryNonce;\n const delay =\n parseInt(res.headers.get('Retry-After') || '1', 10) * 1000;\n await new Promise<void>((resolve) => setTimeout(resolve, delay));\n return this._doAuthenticatedRequest(method, path, body, currentToken, retryCount + 1, extraHeaders);\n }\n\n return res;\n }\n}\n", "/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\nimport { createDPoPProof } from './dpop';\nimport type { AuthManager } from './auth';\nimport {\n TOKEN_REFRESH_LEEWAY_MS,\n TOKEN_REFRESH_MIN_MS,\n RECONNECT_MAX_ATTEMPTS,\n RECONNECT_BASE_MS,\n RECONNECT_MAX_DELAY_MS,\n SUB_PROTOCOL,\n} from './constants';\nimport type { SubscriptionMessage } from './types';\n\ntype EmitFn = (event: string, data?: unknown) => void;\n\n/* istanbul ignore file: WebSocketManager wraps a real browser WebSocket and\n only meaningfully runs against a live server (E2E coverage via\n tests/e2e/features/...). Unit-testing the timer-driven token refresh,\n exponential reconnect backoff, DPoP bind, and onmessage/onclose state\n machine in jsdom would require fragile mocks that do not exercise the\n actual transport. The pure subscribe/disconnect bookkeeping is covered\n incidentally via SDK index tests. */\n\n/**\n * WebSocket connection manager for the GeonicDB SDK.\n *\n * Handles connection lifecycle, DPoP binding, token refresh,\n * automatic reconnection with exponential backoff, and subscriptions.\n */\nexport class WebSocketManager {\n private _auth: AuthManager;\n private _tenant: string;\n private _wsEndpointOverride: string | null;\n private _baseUrl: string;\n private _emit: EmitFn;\n\n private _ws: WebSocket | null = null;\n private _wsEndpoint: string | null = null;\n private _deploymentHostname: string | null = null;\n private _wsIntentionalClose = false;\n private _reconnectAttempts = 0;\n private _tokenRefreshTimer: ReturnType<typeof setTimeout> | null = null;\n private _reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private _subscription: SubscriptionMessage | null = null;\n private _pendingSubscription = false;\n\n constructor(\n auth: AuthManager,\n baseUrl: string,\n tenant: string,\n emit: EmitFn,\n wsEndpointOverride?: string,\n ) {\n this._auth = auth;\n this._baseUrl = baseUrl;\n this._tenant = tenant;\n this._emit = emit;\n this._wsEndpointOverride = wsEndpointOverride || null;\n }\n\n private _log(...args: unknown[]): void {\n if (this._auth._debug) console.log('[GeonicDB:WS]', ...args);\n }\n\n /** Establish WebSocket connection (authentication is automatic). */\n async connect(): Promise<void> {\n // #1105: anonymous \u30E2\u30FC\u30C9\u3067\u306F WS \u63A5\u7D9A\u4E0D\u53EF (\u30B5\u30FC\u30D0\u30FC local-ws-server.ts \u306F\n // token \u5FC5\u9808)\u3002HTTP \u306E\u307F anonymous \u3092\u30B5\u30DD\u30FC\u30C8\u3059\u308B\u3002\n if (this._auth.isAnonymous()) {\n const err = new Error(\n 'WebSocket connect() is not supported in anonymous mode. ' +\n 'Authenticate via login() or setCredentials() before connect().',\n );\n this._emit('error', err);\n throw err;\n }\n if (this._reconnectTimer) {\n clearTimeout(this._reconnectTimer);\n this._reconnectTimer = null;\n }\n if (\n this._ws &&\n (this._ws.readyState === WebSocket.OPEN ||\n this._ws.readyState === WebSocket.CONNECTING)\n ) {\n return;\n }\n this._wsIntentionalClose = false;\n this._reconnectAttempts = 0;\n\n try {\n const token = await this._auth.ensureToken();\n await this._openWebSocket(token);\n } catch (err) {\n this._emit('error', err);\n }\n }\n\n /** Force-reconnect (closes existing connection, preserves subscriptions). */\n async reconnect(): Promise<void> {\n this._wsIntentionalClose = true;\n this._clearTimers();\n if (this._ws) {\n this._ws.close(1000, 'Client reconnect');\n this._ws = null;\n }\n return this.connect();\n }\n\n /** Disconnect WebSocket (does not fire 'disconnected' event). */\n disconnect(): void {\n this._wsIntentionalClose = true;\n this._clearTimers();\n this._subscription = null;\n this._pendingSubscription = false;\n if (this._ws) {\n this._ws.close(1000, 'Client disconnect');\n this._ws = null;\n }\n }\n\n /** Check if WebSocket is currently open. */\n isConnected(): boolean {\n return !!(this._ws && this._ws.readyState === WebSocket.OPEN);\n }\n\n /** Subscribe to entity events (can be called before or after connect). */\n subscribe(options?: {\n entityTypes?: string[];\n idPattern?: string;\n scopeQ?: string;\n /** #2026: \u914D\u4FE1\u30A4\u30D9\u30F3\u30C8\u306E\u30EC\u30F3\u30C0\u30EA\u30F3\u30B0\u306B\u4F7F\u3046 `@context` (URL / URL \u914D\u5217)\u3002 */\n '@context'?: string | string[];\n }): void {\n const msg: SubscriptionMessage = { action: 'subscribe' };\n if (options) {\n if (options.entityTypes) msg.entityTypes = options.entityTypes;\n if (options.idPattern) msg.idPattern = options.idPattern;\n // #1132: scopeQ \u3092\u30B5\u30FC\u30D0\u5074\u306B\u4F1D\u3048\u308B\u3002\u30B5\u30FC\u30D0\u304C\u672A\u5BFE\u5FDC\u306E\u5834\u5408\u306F\u9ED9\u3063\u3066\u7121\u8996\u3055\u308C\u308B\n if (options.scopeQ) msg.scopeQ = options.scopeQ;\n // #2026: \u914D\u4FE1\u7269\u306E\u578B\u540D\u30FB\u5C5E\u6027\u540D\u3092 compact \u3059\u308B\u8A9E\u5F59\u3002\u672A\u5BFE\u5FDC\u30B5\u30FC\u30D0\u3067\u306F\u9ED9\u3063\u3066\u7121\u8996\u3055\u308C\u308B\n if (options['@context']) msg['@context'] = options['@context'];\n }\n this._subscription = msg;\n // If DPoP pending verification, store subscription but don't send yet\n if (this._pendingSubscription) return;\n if (this._ws && this._ws.readyState === WebSocket.OPEN) {\n this._ws.send(JSON.stringify(msg));\n }\n }\n\n private async _discoverWsEndpoint(): Promise<string> {\n if (this._wsEndpointOverride) return this._wsEndpointOverride;\n if (this._wsEndpoint) return this._wsEndpoint;\n\n // Fetch endpoint from server discovery API\n try {\n const res = await fetch(this._baseUrl + '/sdk/v1/streaming');\n if (res.ok) {\n const info = (await res.json()) as {\n enabled: boolean;\n endpoint: string | null;\n deployment?: string;\n };\n if (info.enabled && info.endpoint) {\n this._wsEndpoint = info.endpoint;\n if (info.deployment) {\n this._deploymentHostname = info.deployment;\n }\n return this._wsEndpoint;\n }\n }\n } catch {\n // Discovery failed \u2014 fall back to URL scheme conversion\n }\n\n // Fallback: convert http(s) baseUrl to ws(s)\n this._wsEndpoint = this._baseUrl.replace(/^http/, 'ws');\n return this._wsEndpoint;\n }\n\n private async _openWebSocket(token: string): Promise<void> {\n const endpoint = await this._discoverWsEndpoint();\n return new Promise<void>((resolve, reject) => {\n const params = new URLSearchParams({ tenant: this._tenant });\n if (this._deploymentHostname) {\n params.set('deployment', this._deploymentHostname);\n }\n const wsUrl =\n endpoint +\n (endpoint.indexOf('?') === -1 ? '?' : '&') +\n params.toString();\n\n this._log('connecting', wsUrl);\n const ws = new WebSocket(wsUrl, [SUB_PROTOCOL, token]);\n this._ws = ws;\n\n ws.onopen = (): void => {\n if (this._ws !== ws) return;\n this._log('connected');\n this._reconnectAttempts = 0;\n\n const isDPoP =\n this._auth._tokenType === 'DPoP' && !!this._auth._dpopKeyPair;\n\n const bindPromise = isDPoP\n ? createDPoPProof(this._auth._dpopKeyPair, 'GET', wsUrl, null).then(\n (proof) => {\n if (proof && ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ action: 'dpop_bind', proof }));\n }\n },\n )\n : Promise.resolve();\n\n bindPromise\n .then(() => {\n this._scheduleTokenRefresh();\n\n if (isDPoP) {\n this._pendingSubscription = true;\n } else if (this._subscription) {\n ws.send(JSON.stringify(this._subscription));\n }\n\n this._emit('open');\n this._emit('connected');\n resolve();\n })\n .catch((err) => {\n this._emit('error', err);\n resolve();\n });\n };\n\n ws.onmessage = (event: MessageEvent): void => {\n if (this._ws !== ws) return;\n let msg: Record<string, unknown>;\n try {\n msg = JSON.parse(event.data as string) as Record<string, unknown>;\n } catch {\n return;\n }\n\n if (msg.type === 'pong') return;\n\n // #2055: \u8CFC\u8AAD\u5B8C\u4E86\u306E ack\u3002**\u3053\u3053\u3067\u63E1\u3089\u306A\u3044\u3068\u4E0B\u306E fallthrough \u304C\n // \u300C\u672A\u77E5\u306E type = \u30A8\u30F3\u30C6\u30A3\u30C6\u30A3\u30A4\u30D9\u30F3\u30C8\u300D\u3068\u3057\u3066\u6271\u3044\u3001\u8CFC\u8AAD\u76F4\u5F8C\u306B\u507D\u306E\u30A4\u30D9\u30F3\u30C8\u3092\n // 1 \u4EF6\u914D\u3063\u3066\u3057\u307E\u3046\u3002** \u30B5\u30FC\u30D0\u306F `subscribe` \u306E\u30D5\u30A3\u30EB\u30BF\u9069\u7528\u5F8C\u306B\u3053\u308C\u3092\u9001\u308B\n // (`@context` \u306E\u89E3\u6C7A\u304C\u975E\u540C\u671F\u306A\u306E\u3067\u3001\u53D7\u7406\u3068\u6709\u52B9\u5316\u306E\u9593\u306B\u7A93\u304C\u3042\u308B)\u3002\n if (msg.type === 'subscribed') {\n this._emit('subscribed', undefined);\n return;\n }\n\n if (msg.type === 'dpop_verified') {\n this._pendingSubscription = false;\n if (this._subscription) {\n ws.send(JSON.stringify(this._subscription));\n }\n return;\n }\n\n if (msg.type === 'error') {\n this._emit('error', new Error(msg.message as string));\n return;\n }\n\n this._log('event', msg.type, msg.entityId || '');\n\n // Entity events: build a complete NGSI-LD entity from id + type + data\n if (msg.entityId && msg.data && typeof msg.data === 'object') {\n msg.entity = {\n id: msg.entityId,\n type: msg.entityType,\n ...(msg.data as Record<string, unknown>),\n };\n }\n\n this._emit(msg.type as string, msg);\n this._emit('message', msg);\n };\n\n ws.onclose = (event: CloseEvent): void => {\n if (this._ws !== ws) return;\n this._clearTimers();\n\n if (this._wsIntentionalClose) {\n this._emit('close', event);\n return;\n }\n\n this._emit('close', event);\n this._emit('disconnected');\n this._autoReconnect();\n };\n\n ws.onerror = (err: Event): void => {\n if (this._ws !== ws) return;\n this._emit('error', err);\n if (ws.readyState !== WebSocket.OPEN) {\n reject(new Error('WebSocket connection failed'));\n }\n };\n });\n }\n\n private _scheduleTokenRefresh(): void {\n if (this._tokenRefreshTimer) clearTimeout(this._tokenRefreshTimer);\n\n const remaining = this._auth._tokenExpiry - Date.now();\n const refreshIn = Math.max(\n remaining - TOKEN_REFRESH_LEEWAY_MS,\n TOKEN_REFRESH_MIN_MS,\n );\n\n this._tokenRefreshTimer = setTimeout(() => {\n const oldWs = this._ws;\n this._auth._token = null;\n this._auth\n .ensureToken()\n .then((newToken) => {\n if (!oldWs || oldWs.readyState !== WebSocket.OPEN) return;\n\n this._wsIntentionalClose = true;\n oldWs.close(1000, 'Token refresh');\n\n this._wsIntentionalClose = false;\n return this._openWebSocket(newToken);\n })\n .catch((err) => {\n this._emit('error', err);\n this._autoReconnect();\n });\n }, refreshIn);\n }\n\n private _autoReconnect(): void {\n if (this._wsIntentionalClose) return;\n if (\n this._ws &&\n (this._ws.readyState === WebSocket.OPEN ||\n this._ws.readyState === WebSocket.CONNECTING)\n ) {\n return;\n }\n\n if (this._reconnectAttempts >= RECONNECT_MAX_ATTEMPTS) {\n this._emit('error', new Error('Max reconnection attempts reached'));\n return;\n }\n\n const delay = Math.min(\n RECONNECT_BASE_MS * Math.pow(2, this._reconnectAttempts),\n RECONNECT_MAX_DELAY_MS,\n );\n this._reconnectAttempts++;\n this._emit('reconnecting', {\n attempt: this._reconnectAttempts,\n delay,\n });\n\n this._reconnectTimer = setTimeout(() => {\n this._auth._token = null;\n this._auth\n .ensureToken()\n .then((token) => this._openWebSocket(token))\n .catch(() => this._autoReconnect());\n }, delay);\n }\n\n private _clearTimers(): void {\n if (this._tokenRefreshTimer) {\n clearTimeout(this._tokenRefreshTimer);\n this._tokenRefreshTimer = null;\n }\n if (this._reconnectTimer) {\n clearTimeout(this._reconnectTimer);\n this._reconnectTimer = null;\n }\n }\n}\n", "/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\nimport {\n SDK_CACHE_IDB_NAME,\n SDK_CACHE_IDB_STORE,\n SDK_CACHE_IDB_VERSION,\n} from './constants';\nimport type { CacheEntry } from './cache';\n\n/**\n * Persistent backing store for the SDK's ETag/If-None-Match cache (#1262).\n *\n * `SdkCache` is an in-memory LRU and is therefore lost on page reload, which\n * kills the `304` revalidation path for SPAs: a reloaded page has no cached\n * validators, cannot send `If-None-Match`, and the server always responds\n * `200` with a full body. This module mirrors cache entries into IndexedDB so\n * they survive a reload and the validators can be replayed.\n *\n * Persistence is wired only in environments that expose IndexedDB (browsers).\n * Node-based consumers (CLI, server-side) keep the pure in-memory behaviour \u2014\n * `createCachePersistence()` returns `null` there and `SdkCache` runs unchanged.\n *\n * Auth boundary: entries are namespaced by a `scope` string (the tenant) and\n * the persisted store is dropped on every credential change (`login()`,\n * `setCredentials()`, `logout()`). Persisted entries are only ever read back\n * when a *remembered* session is restored \u2014 i.e. the very same principal that\n * wrote them \u2014 so a cached body can never be replayed across users.\n */\nexport interface CachePersistence {\n /**\n * Load up to `limit` persisted entries for a scope, ordered oldest \u2192\n * newest by `cachedAt` so the caller can replay them into an LRU in the\n * correct recency order. Never throws \u2014 resolves `[]` on any failure.\n */\n loadScope(\n scope: string,\n limit: number,\n ): Promise<Array<{ key: string; entry: CacheEntry }>>;\n /** Insert or replace a single entry. Best-effort; never throws. */\n put(scope: string, key: string, entry: CacheEntry): Promise<void>;\n /** Remove specific entries within a scope. Best-effort; never throws. */\n remove(scope: string, keys: string[]): Promise<void>;\n /** Drop every entry for a scope (auth change / explicit flush). Never throws. */\n clearScope(scope: string): Promise<void>;\n}\n\n/**\n * Whether IndexedDB is available for cache persistence. Mirrors the DPoP\n * persistence probe in `dpop.ts`: both `indexedDB` and `structuredClone`\n * (used by IndexedDB internally for the value clone) must exist.\n */\nexport const cachePersistenceSupported =\n typeof (globalThis as { indexedDB?: unknown }).indexedDB !== 'undefined' &&\n typeof structuredClone !== 'undefined';\n\n/** NUL separates the scope from the cache key in a composite record id. */\nconst SCOPE_SEP = '\\u0000';\n\n/** Highest code point \u2014 upper bound for a scope-prefixed key range. */\nconst SCOPE_RANGE_END = '\uFFFF';\n\ninterface PersistedCacheRecord {\n /** `${scope}\\u0000${key}` \u2014 primary key. */\n id: string;\n scope: string;\n key: string;\n entry: CacheEntry;\n}\n\n// ============================================================\n// IndexedDB-backed implementation (#1262)\n// ============================================================\n//\n// The IndexedDB DOM types live only in `lib: [\"DOM\"]`, but the repo-wide\n// tsconfig/ESLint context uses the Node libs. Rather than split the SDK into a\n// separate lint config, the IDB-touching code is treated as `any` under a\n// local eslint-disable \u2014 the same trade-off already made in `dpop.ts`.\n\n/* eslint-disable @typescript-eslint/no-unsafe-call,\n @typescript-eslint/no-unsafe-member-access,\n @typescript-eslint/prefer-promise-reject-errors,\n @typescript-eslint/no-unsafe-assignment,\n @typescript-eslint/no-unsafe-argument,\n @typescript-eslint/no-explicit-any */\n\n/* istanbul ignore next: trivial wrapper, exercised via the\n fake-indexeddb-backed unit tests rather than statement-counted here. */\nfunction openCacheDB(): Promise<any> {\n return new Promise<any>((resolve, reject) => {\n const req: any = (globalThis as any).indexedDB.open(\n SDK_CACHE_IDB_NAME,\n SDK_CACHE_IDB_VERSION,\n );\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains(SDK_CACHE_IDB_STORE)) {\n db.createObjectStore(SDK_CACHE_IDB_STORE, { keyPath: 'id' });\n }\n };\n req.onsuccess = () => { resolve(req.result); };\n req.onerror = () => { reject(req.error ?? new Error('IndexedDB open failed')); };\n // A version upgrade blocked by another open tab would otherwise leave the\n // open request pending forever, so the per-op `catch` never runs. Reject to\n // keep persistence best-effort and fail fast.\n req.onblocked = () => { reject(req.error ?? new Error('IndexedDB open blocked')); };\n });\n}\n\nfunction recordId(scope: string, key: string): string {\n return scope + SCOPE_SEP + key;\n}\n\n/** IndexedDB implementation of {@link CachePersistence}. Stateless \u2014 opens a\n * fresh connection per operation, like the DPoP persistence helpers. */\nexport class IdbCachePersistence implements CachePersistence {\n async loadScope(\n scope: string,\n limit: number,\n ): Promise<Array<{ key: string; entry: CacheEntry }>> {\n let db: any = null;\n try {\n db = await openCacheDB();\n const records = await new Promise<PersistedCacheRecord[]>((resolve, reject) => {\n const tx = db!.transaction(SDK_CACHE_IDB_STORE, 'readonly');\n const range = (globalThis as any).IDBKeyRange.bound(\n scope + SCOPE_SEP,\n scope + SCOPE_SEP + SCOPE_RANGE_END,\n );\n const req = tx.objectStore(SDK_CACHE_IDB_STORE).getAll(range);\n req.onsuccess = () => { resolve(req.result ?? []); };\n req.onerror = () => { reject(req.error ?? new Error('IndexedDB getAll failed')); };\n });\n return records\n .filter((r) => r && r.scope === scope && typeof r.key === 'string' && r.entry)\n .sort((a, b) => (a.entry.cachedAt ?? 0) - (b.entry.cachedAt ?? 0))\n .slice(-limit)\n .map((r) => ({ key: r.key, entry: r.entry }));\n } catch {\n /* istanbul ignore next: reachable only on IDB faults (db deletion\n mid-read, blocked tab); persistence is best-effort. */\n return [];\n } finally {\n db?.close();\n }\n }\n\n async put(scope: string, key: string, entry: CacheEntry): Promise<void> {\n let db: any = null;\n try {\n db = await openCacheDB();\n await new Promise<void>((resolve, reject) => {\n const tx = db!.transaction(SDK_CACHE_IDB_STORE, 'readwrite');\n const record: PersistedCacheRecord = { id: recordId(scope, key), scope, key, entry };\n tx.objectStore(SDK_CACHE_IDB_STORE).put(record);\n tx.oncomplete = () => { resolve(); };\n tx.onerror = () => { reject(tx.error ?? new Error('IndexedDB put failed')); };\n tx.onabort = () => { reject(tx.error ?? new Error('IndexedDB transaction aborted')); };\n });\n } catch {\n /* istanbul ignore next: write-path catch is reachable only on IDB faults\n (quota exceeded, blocked tab); best-effort, covered via E2E. */\n // swallow \u2014 persistence is best-effort\n } finally {\n db?.close();\n }\n }\n\n async remove(scope: string, keys: string[]): Promise<void> {\n if (keys.length === 0) return;\n let db: any = null;\n try {\n db = await openCacheDB();\n await new Promise<void>((resolve, reject) => {\n const tx = db!.transaction(SDK_CACHE_IDB_STORE, 'readwrite');\n const store = tx.objectStore(SDK_CACHE_IDB_STORE);\n for (const key of keys) store.delete(recordId(scope, key));\n tx.oncomplete = () => { resolve(); };\n tx.onerror = () => { reject(tx.error ?? new Error('IndexedDB delete failed')); };\n tx.onabort = () => { reject(tx.error ?? new Error('IndexedDB transaction aborted')); };\n });\n } catch {\n // swallow \u2014 best-effort\n } finally {\n db?.close();\n }\n }\n\n async clearScope(scope: string): Promise<void> {\n let db: any = null;\n try {\n db = await openCacheDB();\n await new Promise<void>((resolve, reject) => {\n const tx = db!.transaction(SDK_CACHE_IDB_STORE, 'readwrite');\n const range = (globalThis as any).IDBKeyRange.bound(\n scope + SCOPE_SEP,\n scope + SCOPE_SEP + SCOPE_RANGE_END,\n );\n tx.objectStore(SDK_CACHE_IDB_STORE).delete(range);\n tx.oncomplete = () => { resolve(); };\n tx.onerror = () => { reject(tx.error ?? new Error('IndexedDB clear failed')); };\n tx.onabort = () => { reject(tx.error ?? new Error('IndexedDB transaction aborted')); };\n });\n } catch {\n // swallow \u2014 best-effort\n } finally {\n db?.close();\n }\n }\n}\n\n/* eslint-enable @typescript-eslint/no-unsafe-call,\n @typescript-eslint/no-unsafe-member-access,\n @typescript-eslint/prefer-promise-reject-errors,\n @typescript-eslint/no-unsafe-assignment,\n @typescript-eslint/no-unsafe-argument,\n @typescript-eslint/no-explicit-any */\n\n/**\n * Construct a cache persistence backend for the current runtime, or `null`\n * when IndexedDB is unavailable (Node, non-secure contexts) \u2014 callers then\n * keep the pure in-memory cache.\n */\nexport function createCachePersistence(): CachePersistence | null {\n return cachePersistenceSupported ? new IdbCachePersistence() : null;\n}\n", "/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\n/**\n * Web Push (VAPID) notification helpers (#3014 / #3060).\n *\n * Pure conversion functions between the browser Push API and the\n * NGSIv2 `notification.webpush` / NGSI-LD `endpoint.webpush` shapes\n * documented in docs/SUBSCRIPTIONS.md. These do not touch the network \u2014\n * see `GeonicDB.getWebPushVapidKey()` / `subscribeWebPush()` /\n * `unsubscribeWebPush()` in `./index` for the browser-facing API.\n */\n\n/** `PushSubscription.toJSON().keys` shape (RFC 8291). */\nexport interface WebPushKeys {\n p256dh: string;\n auth: string;\n}\n\n/** RFC 8030 delivery options, shared by both NGSI protocol shapes. */\nexport interface WebPushDeliveryOptions {\n /** Push message TTL in seconds. */\n ttl?: number;\n urgency?: 'very-low' | 'low' | 'normal' | 'high';\n topic?: string;\n}\n\n/** Options for `GeonicDB.subscribeWebPush()`. */\nexport interface WebPushSubscribeOptions {\n /**\n * VAPID public key (base64url). Auto-fetched from\n * `GET /.well-known/webpush-vapid-key` when omitted.\n */\n applicationServerKey?: string;\n /**\n * Service worker registration to subscribe through. Defaults to\n * `navigator.serviceWorker.ready` \u2014 the caller must have already\n * registered a service worker (e.g. `navigator.serviceWorker.register('/sw.js')`).\n */\n serviceWorkerRegistration?: ServiceWorkerRegistration;\n}\n\n/**\n * Accepted by all conversion functions: either a `PushSubscription.toJSON()`\n * plain-object shape (`{ endpoint, keys: { p256dh, auth } }`), or a native\n * `PushSubscription` instance, which has no `keys` property and exposes the\n * raw key material via `getKey()` instead (RFC 8291).\n */\nexport interface WebPushSubscriptionLike {\n endpoint?: string;\n keys?: Partial<WebPushKeys>;\n getKey?: (name: 'p256dh' | 'auth') => ArrayBuffer | null;\n}\n\nfunction arrayBufferToBase64Url(buf: ArrayBuffer): string {\n const bytes = new Uint8Array(buf);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nfunction extractKeys(subscription: WebPushSubscriptionLike): {\n endpoint: string;\n keys: WebPushKeys;\n} {\n const endpoint = subscription.endpoint;\n let p256dh = subscription.keys?.p256dh;\n let auth = subscription.keys?.auth;\n // Native PushSubscription instances (e.g. returned by\n // GeonicDB.subscribeWebPush()) have no `keys` property \u2014 only\n // `getKey(name)`, returning the raw key as an ArrayBuffer.\n if ((!p256dh || !auth) && typeof subscription.getKey === 'function') {\n const p256dhBuf = subscription.getKey('p256dh');\n const authBuf = subscription.getKey('auth');\n if (!p256dh && p256dhBuf) p256dh = arrayBufferToBase64Url(p256dhBuf);\n if (!auth && authBuf) auth = arrayBufferToBase64Url(authBuf);\n }\n if (!endpoint || !p256dh || !auth) {\n throw new Error(\n 'Invalid PushSubscription: endpoint, keys.p256dh, and keys.auth are all required',\n );\n }\n return { endpoint, keys: { p256dh, auth } };\n}\n\n/**\n * Convert a browser `PushSubscription` (or its `.toJSON()` output) into the\n * NGSIv2 `notification.webpush` shape (#3014).\n *\n * @example\n * ```ts\n * const subscription = await db.subscribeWebPush();\n * const webpush = toNgsiv2WebPushNotification(subscription, { ttl: 86400 });\n * await db.request('POST', '/v2/subscriptions', {\n * description: 'Web Push notification',\n * subject: { entities: [{ type: 'Sensor' }], condition: { attrs: ['value'] } },\n * notification: { webpush },\n * });\n * ```\n */\nexport function toNgsiv2WebPushNotification(\n subscription: WebPushSubscriptionLike,\n options?: WebPushDeliveryOptions,\n): { url: string; keys: WebPushKeys } & WebPushDeliveryOptions {\n const { endpoint, keys } = extractKeys(subscription);\n return { url: endpoint, keys, ...options };\n}\n\n/**\n * Convert a browser `PushSubscription` (or its `.toJSON()` output) into the\n * NGSI-LD `endpoint.webpush` shape (#3014, GeonicDB extension). The\n * returned object is meant to be spread into `notification.endpoint`\n * alongside `uri`.\n *\n * @example\n * ```ts\n * const subscription = await db.subscribeWebPush();\n * const { uri, webpush } = toNgsiLdWebPushEndpoint(subscription);\n * await db.request('POST', '/ngsi-ld/v1/subscriptions', {\n * type: 'Subscription',\n * entities: [{ type: 'Sensor' }],\n * notification: { endpoint: { uri, accept: 'application/json', webpush } },\n * });\n * ```\n */\nexport function toNgsiLdWebPushEndpoint(\n subscription: WebPushSubscriptionLike,\n options?: WebPushDeliveryOptions,\n): { uri: string; webpush: { keys: WebPushKeys } & WebPushDeliveryOptions } {\n const { endpoint, keys } = extractKeys(subscription);\n return { uri: endpoint, webpush: { keys, ...options } };\n}\n\n/**\n * Convert a base64url-encoded VAPID public key (as returned by\n * `GET /.well-known/webpush-vapid-key`) into the `Uint8Array` expected by\n * `PushManager.subscribe({ applicationServerKey })`.\n */\nexport function urlBase64ToUint8Array(base64Url: string): Uint8Array<ArrayBuffer> {\n const padding = '='.repeat((4 - (base64Url.length % 4)) % 4);\n const base64 = (base64Url + padding).replace(/-/g, '+').replace(/_/g, '/');\n const raw = atob(base64);\n const output = new Uint8Array(raw.length);\n for (let i = 0; i < raw.length; i++) {\n output[i] = raw.charCodeAt(i);\n }\n return output;\n}\n\n/**\n * Whether an existing `PushSubscription` was created with the given VAPID\n * public key. Used by `GeonicDB.subscribeWebPush()` to detect a stale\n * subscription left over from before a VAPID key rotation \u2014 reusing it would\n * otherwise fail silently (the push service rejects deliveries signed with\n * the new key, with nothing surfaced to the SDK caller).\n *\n * Returns `true` (assume match, keep the existing subscription) when the key\n * can't be read \u2014 some older Push API implementations don't expose\n * `PushSubscription.options`.\n */\nexport function matchesApplicationServerKey(\n subscription: { options?: { applicationServerKey?: ArrayBuffer | null } | null },\n desiredKey: Uint8Array,\n): boolean {\n const existingKey = subscription.options?.applicationServerKey;\n if (!existingKey) return true;\n const existingBytes = new Uint8Array(existingKey);\n if (existingBytes.length !== desiredKey.length) return false;\n for (let i = 0; i < existingBytes.length; i++) {\n if (existingBytes[i] !== desiredKey[i]) return false;\n }\n return true;\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\n/**\n * GeonicDB JavaScript SDK\n *\n * NGSI-LD Context Broker client for browser and Node.js.\n * Handles API key auth (DPoP + PoW), Bearer JWT, entity CRUD,\n * WebSocket event streaming, and automatic reconnection.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\ndeclare const document: any;\ndeclare const window: any;\ndeclare const navigator: any;\ndeclare const localStorage: Storage;\n\nimport { EventEmitter } from './events';\nimport { AuthManager } from './auth';\nimport { WebSocketManager } from './websocket';\nimport { createErrorFromResponse, NotFoundError, GeonicDBError } from './errors';\nimport { SdkCache } from './cache';\nimport { createCachePersistence } from './cache-persistence';\nimport {\n SDK_CACHE_MAX_ENTRIES_DEFAULT,\n SDK_POLL_INTERVAL_MS_DEFAULT,\n WEBPUSH_VAPID_KEY_PATH,\n} from './constants';\nimport {\n urlBase64ToUint8Array,\n matchesApplicationServerKey,\n toNgsiv2WebPushNotification,\n toNgsiLdWebPushEndpoint,\n buildWebPushSubscriptionStorageKey,\n} from './webpush';\nimport type {\n WebPushSubscribeOptions,\n RegisterWebPushSubscriptionOptions,\n RegisterWebPushSubscriptionResult,\n WebPushRegisterProtocol,\n WebPushDeliveryOptions,\n} from './webpush';\nimport type {\n GeonicDBOptions,\n CredentialsOptions,\n CountEntitiesParams,\n GetEntitiesParams,\n GetTemporalEntitiesParams,\n EntityTypeList,\n AttributeList,\n LoginOptions,\n PollHandle,\n PollOptions,\n SubscribeOptions,\n LoginResponse,\n RefreshedCredentials,\n} from './types';\n\n// Re-export types for consumers\nexport type {\n GeonicDBOptions,\n CredentialsOptions,\n RefreshedCredentials,\n CountEntitiesParams,\n GetEntitiesParams,\n GetTemporalEntitiesParams,\n EntityTypeList,\n AttributeList,\n LoginOptions,\n SubscribeOptions,\n EntityEvent,\n ReconnectingEvent,\n LoginResponse,\n GeonicDBEventMap,\n CacheEvent,\n PollOptions,\n PollHandle,\n} from './types';\n\n// Re-export Web Push helpers/types (#3014 / #3060 / #3092)\nexport {\n urlBase64ToUint8Array,\n toNgsiv2WebPushNotification,\n toNgsiLdWebPushEndpoint,\n buildWebPushSubscriptionStorageKey,\n} from './webpush';\nexport type {\n WebPushKeys,\n WebPushDeliveryOptions,\n WebPushSubscribeOptions,\n WebPushRegisterProtocol,\n WebPushRegisterEntitySelector,\n WebPushRegisterNgsiv2Subject,\n RegisterWebPushSubscriptionOptions,\n RegisterWebPushSubscriptionResult,\n} from './webpush';\n\n// Re-export error classes for consumers\nexport {\n GeonicDBError,\n AuthenticationError,\n AuthorizationError,\n NotFoundError,\n ConflictError,\n ValidationError,\n RateLimitError,\n QuotaExceededError,\n NetworkError,\n PasswordResetRequiredError,\n TemporaryPasswordExpiredError,\n} from './errors';\nexport type { RequestContext } from './errors';\n\n/**\n * GeonicDB SDK client.\n *\n * Handles API key auth (DPoP + PoW), Bearer JWT, entity CRUD,\n * WebSocket event streaming, and automatic reconnection.\n *\n * @example\n * ```typescript\n * import GeonicDB from '@geolonia/geonicdb-sdk';\n *\n * const db = new GeonicDB({\n * apiKey: 'your-api-key',\n * tenant: 'your-tenant',\n * baseUrl: 'https://your-geonicdb.example.com',\n * });\n *\n * const entities = await db.getEntities({ type: 'Room' });\n * ```\n */\nexport class GeonicDB extends EventEmitter {\n private _auth: AuthManager;\n private _ws: WebSocketManager;\n /**\n * In-flight `registerWebPushSubscription()` calls keyed by storageKey (#3092).\n * Concurrent callers with the same key share one Promise so a race cannot\n * POST two subscriptions before the first id is persisted.\n */\n private _webPushRegisterInflight = new Map<\n string,\n Promise<RegisterWebPushSubscriptionResult>\n >();\n\n /**\n * Legacy callback for token refresh events.\n * Prefer `db.on('tokenRefresh', callback)` for new code.\n * @deprecated Use `db.on('tokenRefresh', callback)` instead.\n */\n onTokenRefresh: ((creds: RefreshedCredentials) => void) | null = null;\n\n constructor(options?: GeonicDBOptions) {\n super();\n const opts = options || {};\n\n // Auto-detect from script tag in browser\n let apiKey = opts.apiKey || '';\n let tenant = opts.tenant || '';\n let baseUrl = opts.baseUrl || '';\n\n /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */\n /* istanbul ignore next: document.currentScript is only populated when the\n SDK bundle is loaded via a <script> tag in a real browser; jsdom does\n not expose it for the test runner. Covered by E2E / manual browser tests. */\n if (typeof document !== 'undefined' && document.currentScript) {\n const script = document.currentScript;\n if (!apiKey) apiKey = script?.getAttribute?.('data-api-key') || '';\n if (!tenant) tenant = script?.getAttribute?.('data-tenant') || '';\n if (!baseUrl) baseUrl = script?.getAttribute?.('data-base-url') || '';\n }\n /* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */\n\n // Request paths are concatenated onto the base URL verbatim, so a trailing\n // slash would produce `//auth/login`. The server routes that outside the\n // auth-exempt prefix, so the policy engine answers\n // `403 Access denied: no applicable policy` \u2014 a permissions error for what\n // is only a malformed URL. `geonic config` stores the base URL with a\n // trailing slash, which makes this easy to hit when copying that value.\n baseUrl = baseUrl.replace(/\\/+$/, '');\n\n this._auth = new AuthManager(baseUrl, apiKey, tenant, opts.debug, opts.anonymous);\n this._auth.onTokenRefresh = (creds): void => {\n this.onTokenRefresh?.(creds);\n this.emit('tokenRefresh', creds);\n };\n\n // #1043 Phase A: client-side cache. Enabled by default. Pass `cache: false` to opt out.\n if (opts.cache !== false) {\n const cache = new SdkCache(opts.cacheMaxEntries ?? SDK_CACHE_MAX_ENTRIES_DEFAULT);\n // #1262: persist cached validators across reloads in browsers (IndexedDB).\n // Returns null in Node-like runtimes \u2014 the cache then stays in-memory.\n const persistence = createCachePersistence();\n if (persistence) cache.attachPersistence(persistence);\n this._auth.setCache(cache);\n this._auth.setCacheEventEmitter((name, payload) => this.emit(name, payload));\n }\n\n this._ws = new WebSocketManager(\n this._auth,\n baseUrl,\n tenant,\n (event, data) => {\n this.emit(event, data);\n // \u6CE8: \u65E7\u5B9F\u88C5\u3067\u306F entityCreated / entityUpdated / entityDeleted \u3067\n // \u30AD\u30E3\u30C3\u30B7\u30E5\u30A8\u30F3\u30C8\u30EA\u3092\u524A\u9664\u3057\u3066\u3044\u305F\u304C\u3001`Cache-Control: private, no-cache`\n // \u306B\u3088\u308A\u6BCE\u8AAD\u307F\u53D6\u308A\u3067 `If-None-Match` revalidation \u304C\u8D70\u308B\u8A2D\u8A08\u306E\u305F\u3081\u3001\n // \u524A\u9664\u3059\u308B\u3068 ETag \u3092\u5931\u3063\u3066\u6BCE\u56DE full 200 \u306B\u306A\u308A 304 \u5E2F\u57DF\u7BC0\u7D04\u304C\u7121\u52B9\u5316\u3055\u308C\u3066\u3044\u305F\n // (incident 2026-04-28 \u3067 pulse \u306E\u4F53\u611F\u901F\u5EA6\u52A3\u5316\u3068\u3057\u3066\u9855\u5728\u5316)\u3002\n // \u30AD\u30E3\u30C3\u30B7\u30E5\u30A8\u30F3\u30C8\u30EA\u306F\u4FDD\u6301\u3057\u3001\u30B5\u30FC\u30D0\u5074\u306E ETag \u30DE\u30C3\u30C1\u3067\u81EA\u52D5\u7684\u306B\n // 304 (=fresh cache hit) / 200 (=\u672C\u5F53\u306B\u5909\u66F4\u3042\u308A\u306E\u307F full body) \u304C\n // \u632F\u308A\u5206\u3051\u3089\u308C\u308B\u3002\u610F\u56F3\u7684\u306A\u5168\u6D88\u53BB\u306F `clearCache()` \u3092\u4F7F\u3046\u3053\u3068\u3002\n },\n opts.wsEndpoint,\n );\n }\n\n /**\n * Drop every cached response. Useful in tests and on manual auth changes.\n *\n * Emits a `cacheInvalidated` event for each removed entry so listeners can\n * track explicit flushes (the WebSocket-driven auto-invalidation was removed\n * in #1060 to preserve the ETag/304 revalidation path; `clearCache()` is now\n * the only path that emits this event).\n */\n clearCache(): void {\n const cache = this._auth.getCache();\n if (!cache) return;\n const removed = cache.deleteWhere(() => true);\n for (const key of removed) {\n this._auth.emitCacheEvent('cacheInvalidated', { key, path: key.split(':').slice(1).join(':') });\n }\n // #1262: also flush the persisted mirror so a reload does not resurrect\n // entries the consumer explicitly dropped. Best-effort, fire-and-forget.\n void cache.clearPersisted();\n }\n\n // --- Authentication ---\n\n /**\n * Login with email and password.\n *\n * The session is sender-constrained with DPoP (RFC 9449) automatically and\n * transparently \u2014 there is no DPoP option to set. A token leaked from\n * storage (e.g. localStorage XSS) is therefore unusable from another origin.\n * On runtimes without Web Crypto, the SDK silently keeps a plain Bearer\n * session and login still succeeds.\n *\n * @example\n * ```ts\n * // Standard login \u2014 DPoP applied transparently when supported.\n * await db.login('user@example.com', 'password');\n *\n * // Keep the user signed in across page reloads (browser SPA UX).\n * await db.login('user@example.com', 'password', { rememberSession: true });\n * ```\n */\n async login(\n email: string,\n password: string,\n options?: LoginOptions,\n ): Promise<LoginResponse> {\n return this._auth.login(email, password, options);\n }\n\n /**\n * Rehydrate a session previously persisted via\n * `login(email, password, { rememberSession: true })`.\n *\n * Returns true if a session for this tenant was found and loaded; false\n * if there was no entry, IndexedDB is unavailable, or the persisted\n * session was malformed. Call this at app startup before deciding\n * whether to render the login form.\n *\n * @example\n * ```ts\n * const db = new GeonicDB({ baseUrl, tenant });\n * if (await db.restoreSession()) {\n * // Session (tokens + non-extractable key) recovered \u2014 boot the app.\n * } else {\n * // No session; show the login form.\n * }\n * ```\n */\n async restoreSession(): Promise<boolean> {\n return this._auth.restoreSession();\n }\n\n /**\n * Whether the SDK is currently operating in anonymous mode (no token held).\n * Returns true only when `anonymous: true` was passed to the constructor\n * AND no credentials have been set via `login()` / `setCredentials()`.\n */\n isAnonymous(): boolean {\n return this._auth.isAnonymous();\n }\n\n /**\n * Set credentials externally (e.g. from a login API response).\n * When tokenType is 'Bearer' with a refreshToken, DPoP/PoW is bypassed entirely.\n */\n setCredentials(opts: CredentialsOptions): this {\n this._auth.setCredentials(opts);\n return this;\n }\n\n /** Clear all credentials and disconnect. */\n logout(): void {\n this._ws.disconnect();\n this._auth.logout();\n }\n\n // --- Entity CRUD (NGSI-LD) ---\n\n /** Create a new entity. */\n async createEntity(\n entity: Record<string, unknown>,\n ): Promise<{ created: true }> {\n const res = await this._auth.request('POST', '/ngsi-ld/v1/entities', entity);\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n throw createErrorFromResponse(res.status, e, 'Create failed');\n }\n return { created: true };\n }\n\n /** Get a single entity by ID. */\n async getEntity(entityId: string): Promise<Record<string, unknown>> {\n const res = await this._auth.request(\n 'GET',\n '/ngsi-ld/v1/entities/' + encodeURIComponent(entityId),\n );\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n throw createErrorFromResponse(res.status, e, 'Not found');\n }\n return (await res.json()) as Record<string, unknown>;\n }\n\n /** Query entities with optional filters. */\n async getEntities(\n params?: GetEntitiesParams,\n ): Promise<Record<string, unknown>[]> {\n const res = await this._auth.request('GET', buildEntitiesPath(params));\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n throw createErrorFromResponse(res.status, e, 'Query failed');\n }\n return (await res.json()) as Record<string, unknown>[];\n }\n\n /**\n * Poll for entity list changes using ETag-based revalidation (#991 Phase A).\n *\n * The handle's `stop()` ends the loop. Each tick performs a normal\n * `getEntities()` call; the SDK's cache layer issues `If-None-Match`\n * automatically. The poll detects \"no change\" by comparing the previous\n * ETag with the response's ETag (the cache exposes the cached ETag on 304\n * replays as well, so the comparison stays valid in both 200 and 304\n * paths).\n *\n * @example\n * ```typescript\n * const handle = db.poll({ type: 'Room' }, {\n * interval: 5000,\n * onData: (rooms) => render(rooms),\n * onNoChange: () => {},\n * });\n * // ...later\n * handle.stop();\n * ```\n */\n poll(\n params: GetEntitiesParams | undefined,\n options: PollOptions<Record<string, unknown>[]>,\n ): PollHandle {\n const interval = options.interval ?? SDK_POLL_INTERVAL_MS_DEFAULT;\n if (!Number.isFinite(interval) || interval <= 0) {\n throw new Error(`poll interval must be a positive number (got ${String(interval)})`);\n }\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let prevEtag: string | undefined;\n let prevPath: string | undefined;\n\n const buildPath = (): string => buildEntitiesPath(params);\n\n const tick = async (): Promise<void> => {\n if (stopped) return;\n try {\n const path = (prevPath ??= buildPath());\n const res = await this._auth.request('GET', path);\n if (stopped) return;\n const etag = res.headers.get('etag') ?? undefined;\n if (prevEtag !== undefined && etag !== undefined && prevEtag === etag) {\n options.onNoChange?.();\n } else {\n if (!res.ok) {\n throw createErrorFromResponse(res.status, {}, 'Poll request failed');\n }\n const data = (await res.json()) as Record<string, unknown>[];\n prevEtag = etag;\n options.onData?.(data);\n }\n } catch (err) {\n options.onError?.(err);\n } finally {\n if (!stopped) {\n timer = setTimeout(() => { void tick(); }, interval);\n }\n }\n };\n\n void tick();\n\n return {\n stop(): void {\n stopped = true;\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n },\n };\n }\n\n /** Count entities matching the given filters. */\n async count(\n params?: CountEntitiesParams,\n ): Promise<number> {\n // #1132: \u5171\u901A\u30D3\u30EB\u30C0\u7D4C\u7531\u3067\u3001CountEntitiesParams \u306B\u8FFD\u52A0\u3055\u308C\u305F\u30D5\u30A3\u30FC\u30EB\u30C9\n // (scopeQ \u306A\u3069) \u3092\u81EA\u52D5\u3067 URL \u306B\u4E57\u305B\u308B\n const path = buildPathWithParams('/ngsi-ld/v1/entities', {\n count: true,\n limit: 0,\n type: params?.type,\n q: params?.q,\n scopeQ: params?.scopeQ,\n // #2278: clause 5.7.2.4 \u306E \"too wide query\" \u514D\u9664\u6761\u4EF6 (e) local scope\u3002\n local: params?.local,\n });\n const res = await this._auth.request('GET', path);\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n throw createErrorFromResponse(res.status, e, 'Count failed');\n }\n const countHeader = res.headers.get('NGSILD-Results-Count');\n return countHeader ? parseInt(countHeader, 10) : 0;\n }\n\n /** Update entity attributes (partial). */\n async updateEntity(\n entityId: string,\n attrs: Record<string, unknown>,\n ): Promise<{ updated: true }> {\n const res = await this._auth.request(\n 'PATCH',\n '/ngsi-ld/v1/entities/' + encodeURIComponent(entityId) + '/attrs',\n attrs,\n );\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n throw createErrorFromResponse(res.status, e, 'Update failed');\n }\n return { updated: true };\n }\n\n /** Delete an entity. */\n async deleteEntity(entityId: string): Promise<{ deleted: true }> {\n const res = await this._auth.request(\n 'DELETE',\n '/ngsi-ld/v1/entities/' + encodeURIComponent(entityId),\n );\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n throw createErrorFromResponse(res.status, e, 'Delete failed');\n }\n return { deleted: true };\n }\n\n // --- Types / Attributes Discovery ---\n\n /**\n * List all entity types.\n *\n * `details` \u672A\u6307\u5B9A\u306E `GET /types` \u306F ETSI OpenAPI v1.8.1 \u6E96\u62E0\u306E\n * `EntityTypeList` \u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\uFF08`{ id, type: 'EntityTypeList', typeList }`\uFF09\u3092\u8FD4\u3059\u3002\n * \u578B\u540D\u306E\u4E00\u89A7\u306F `typeList` \u304B\u3089\u53D6\u5F97\u3059\u308B\u3002\n */\n async getTypes(): Promise<EntityTypeList> {\n return this._jsonGet('/ngsi-ld/v1/types', 'Types query failed');\n }\n\n /** Get details for a specific entity type. */\n async getType(typeName: string): Promise<Record<string, unknown>> {\n return this._jsonGet(\n '/ngsi-ld/v1/types/' + encodeURIComponent(typeName),\n 'Type not found',\n );\n }\n\n /**\n * List all attributes.\n *\n * `details` \u672A\u6307\u5B9A\u306E `GET /attributes` \u306F ETSI OpenAPI v1.8.1 \u6E96\u62E0\u306E\n * `AttributeList` \u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\uFF08`{ id, type: 'AttributeList', attributeList }`\uFF09\u3092\u8FD4\u3059\u3002\n * \u5C5E\u6027\u540D\u306E\u4E00\u89A7\u306F `attributeList` \u304B\u3089\u53D6\u5F97\u3059\u308B\u3002\n */\n async getAttributes(): Promise<AttributeList> {\n return this._jsonGet('/ngsi-ld/v1/attributes', 'Attributes query failed');\n }\n\n /** Get details for a specific attribute. */\n async getAttribute(attrName: string): Promise<Record<string, unknown>> {\n return this._jsonGet(\n '/ngsi-ld/v1/attributes/' + encodeURIComponent(attrName),\n 'Attribute not found',\n );\n }\n\n // --- Temporal API ---\n\n /** Query temporal entities. */\n async getTemporalEntities(\n params?: GetTemporalEntitiesParams,\n ): Promise<Record<string, unknown>[]> {\n // #1132: \u5171\u901A\u30D3\u30EB\u30C0\u7D4C\u7531\u3002GetTemporalEntitiesParams \u306B\u8FFD\u52A0\u3055\u308C\u305F\u30D5\u30A3\u30FC\u30EB\u30C9\n // (scopeQ \u306A\u3069) \u3092\u81EA\u52D5\u3067 URL \u306B\u4E57\u305B\u308B\n return this._jsonGet(\n buildPathWithParams(\n '/ngsi-ld/v1/temporal/entities',\n params as Record<string, unknown> | undefined,\n ),\n 'Temporal query failed',\n );\n }\n\n /** Get temporal representation of a single entity. */\n async getTemporalEntity(entityId: string): Promise<Record<string, unknown>> {\n return this._jsonGet(\n '/ngsi-ld/v1/temporal/entities/' + encodeURIComponent(entityId),\n 'Temporal entity not found',\n );\n }\n\n // --- Batch Operations ---\n\n /** Create multiple entities in a single request. */\n async batchCreate(\n entities: Record<string, unknown>[],\n ): Promise<Record<string, unknown>> {\n return this._jsonPost(\n '/ngsi-ld/v1/entityOperations/create',\n entities,\n 'Batch create failed',\n );\n }\n\n /**\n * Upsert multiple entities in a single request.\n *\n * #1742: the server follows ETSI GS CIM 009 clause 5.6.8.5 / Table 6.15.3.1-1:\n * - **201** with a `string[]` of the **created** entity IDs, when at least one entity\n * was newly created and none failed (merely updated entities are not listed);\n * - **204** with no body, when every entity already existed and was updated\n * (`_jsonPost` maps this to `{}`);\n * - **207** with a `BatchOperationResult` (`{ created, updated, errors }`), on partial\n * or total failure.\n *\n * The return type reflects all three shapes. Narrow with `Array.isArray(result)` to\n * detect the \"something was created\" case.\n * https://cim.etsi.org/NGSI-LD/official/clause-6.html\n */\n async batchUpsert(\n entities: Record<string, unknown>[],\n ): Promise<string[] | Record<string, unknown>> {\n return this._jsonPost(\n '/ngsi-ld/v1/entityOperations/upsert',\n entities,\n 'Batch upsert failed',\n );\n }\n\n /** Update multiple entities in a single request. */\n async batchUpdate(\n entities: Record<string, unknown>[],\n ): Promise<Record<string, unknown>> {\n return this._jsonPost(\n '/ngsi-ld/v1/entityOperations/update',\n entities,\n 'Batch update failed',\n );\n }\n\n /** Delete multiple entities by ID. */\n async batchDelete(entityIds: string[]): Promise<Record<string, unknown>> {\n return this._jsonPost(\n '/ngsi-ld/v1/entityOperations/delete',\n entityIds,\n 'Batch delete failed',\n );\n }\n\n // --- Internal helpers ---\n\n /** GET request that returns parsed JSON or throws. */\n private async _jsonGet<T>(path: string, fallbackError: string): Promise<T> {\n const res = await this._auth.request('GET', path);\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n throw createErrorFromResponse(res.status, e, fallbackError);\n }\n return (await res.json()) as T;\n }\n\n /** POST request that returns parsed JSON or throws. */\n private async _jsonPost<T>(\n path: string,\n body: unknown,\n fallbackError: string,\n ): Promise<T> {\n const res = await this._auth.request('POST', path, body);\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n throw createErrorFromResponse(res.status, e, fallbackError);\n }\n if (res.status === 204) return {} as T;\n return (await res.json()) as T;\n }\n\n // --- Generic Request ---\n\n /**\n * Make an authenticated API request.\n * Automatically checks response status, parses JSON, and throws on error.\n *\n * Empty bodies (`Content-Length: 0` \u542B\u3080) \u306F Content-Type \u306B\u95A2\u4FC2\u306A\u304F `null`\n * \u3092\u8FD4\u3059 (#1145)\u3002NGSI-LD \u306E POST / DELETE / PATCH \u7CFB\u306F\u4ED5\u69D8\u4E0A\u30DC\u30C7\u30A3\u304C\n * \u7A7A\u3060\u304C\u3001\u30B5\u30FC\u30D0\u5B9F\u88C5\u304C `Content-Type: application/ld+json` \u3092\u4ED8\u3051\u3066\u8FD4\u3059\n * \u30B1\u30FC\u30B9\u304C\u3042\u308A\u3001\u305D\u3053\u3067 `res.json()` \u304C SyntaxError \u3092\u6295\u3052\u3066\u6B62\u307E\u3063\u3066\u3044\u305F\u3002\n */\n async request(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<unknown> {\n const res = await this._auth.request(method, path, body);\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n throw createErrorFromResponse(res.status, e, 'Request failed: ' + res.status);\n }\n if (res.status === 204) return null;\n if (res.headers.get('Content-Length') === '0') return null;\n const ct = res.headers.get('Content-Type') || '';\n if (!ct) return null;\n if (ct.indexOf('json') !== -1) {\n // \u30DC\u30C7\u30A3\u304C\u7A7A\u306E\u3068\u304D\u306B res.json() \u306F SyntaxError \u3092\u6295\u3052\u308B\u305F\u3081\u3001\u307E\u305A text() \u3067\u8AAD\u307F\u3001\n // \u7A7A\u6587\u5B57\u306A\u3089 null \u3092\u8FD4\u3059\u3002\n const text = await res.text();\n if (!text) return null;\n return JSON.parse(text) as unknown;\n }\n return res.text();\n }\n\n /**\n * Make an authenticated API request and return the raw Response object.\n * Use this when you need access to response headers (e.g. NGSILD-Results-Count).\n */\n async requestRaw(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<Response> {\n return this._auth.request(method, path, body);\n }\n\n // --- WebSocket ---\n\n /** Establish WebSocket connection (authentication is automatic). */\n async connect(): Promise<void> {\n return this._ws.connect();\n }\n\n /** Force-reconnect (closes existing connection, preserves subscriptions). */\n async reconnect(): Promise<void> {\n return this._ws.reconnect();\n }\n\n /** Disconnect WebSocket (does not fire 'disconnected' event). */\n disconnect(): void {\n this._ws.disconnect();\n }\n\n /** Check if WebSocket is currently open. */\n isConnected(): boolean {\n return this._ws.isConnected();\n }\n\n /** Subscribe to entity events (can be called before or after connect). */\n subscribe(options?: SubscribeOptions): void {\n this._ws.subscribe(options);\n }\n\n // --- Web Push (#3014 / #3060) ---\n\n /**\n * Fetch the deployment's VAPID public key for Web Push subscriptions.\n *\n * Hits the unauthenticated discovery endpoint\n * `GET /.well-known/webpush-vapid-key` directly (not via `request()`) \u2014\n * it needs no login/API key and predates any session setup. Throws\n * `GeonicDBError` with `statusCode: 503` if the deployment has no VAPID\n * keys configured (503 has no dedicated error class \u2014 see\n * `createErrorFromResponse()` in `./errors`).\n */\n async getWebPushVapidKey(): Promise<string> {\n const res = await fetch(this._auth.baseUrl + WEBPUSH_VAPID_KEY_PATH);\n if (!res.ok) {\n const e = (await res.json().catch(() => ({}))) as Record<string, string>;\n throw createErrorFromResponse(\n res.status,\n e,\n 'Failed to fetch Web Push VAPID key: ' + res.status,\n );\n }\n const body = (await res.json()) as { publicKey: string };\n return body.publicKey;\n }\n\n /**\n * Subscribe the current browser to Web Push notifications (#3014).\n *\n * Reuses an existing subscription on the service worker registration if\n * one is already active *and* still matches the resolved VAPID public key\n * \u2014 otherwise (e.g. after the deployment rotated its VAPID keys) it\n * unsubscribes the stale one and re-subscribes with the current key.\n * Fetches the deployment's VAPID public key (unless `applicationServerKey`\n * is given) and calls `PushManager.subscribe()`.\n *\n * This method only covers the browser Push API. To also create (or reuse)\n * the GeonicDB subscription idempotently, prefer\n * `registerWebPushSubscription()` (#3092). For a manual flow, convert the\n * result with `toNgsiv2WebPushNotification()` / `toNgsiLdWebPushEndpoint()`\n * and `request('POST', ...)`.\n *\n * Browser-only \u2014 throws in environments without Service Worker + Push API\n * support (e.g. Node.js).\n *\n * @example\n * ```ts\n * await navigator.serviceWorker.register('/sw.js');\n * const { subscriptionId, created } = await db.registerWebPushSubscription({\n * protocol: 'ngsiv2',\n * subject: { entities: [{ type: 'Sensor' }], condition: { attrs: ['value'] } },\n * });\n * ```\n */\n async subscribeWebPush(\n options?: WebPushSubscribeOptions,\n ): Promise<PushSubscription> {\n /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */\n if (\n typeof navigator === 'undefined' ||\n !navigator.serviceWorker ||\n typeof window === 'undefined' ||\n !window.PushManager\n ) {\n throw new Error(\n 'subscribeWebPush() requires a browser with Service Worker + Push API support',\n );\n }\n const registration: ServiceWorkerRegistration =\n options?.serviceWorkerRegistration ?? (await navigator.serviceWorker.ready);\n const publicKey = options?.applicationServerKey ?? (await this.getWebPushVapidKey());\n const applicationServerKey = urlBase64ToUint8Array(publicKey);\n const existing: PushSubscription | null = await registration.pushManager.getSubscription();\n if (existing) {\n if (matchesApplicationServerKey(existing, applicationServerKey)) return existing;\n await existing.unsubscribe();\n }\n return registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey });\n /* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment */\n }\n\n /**\n * Idempotently register a GeonicDB subscription that delivers via Web Push (#3092).\n *\n * 1. Calls `subscribeWebPush()` for the browser PushSubscription.\n * 2. Looks up a previously saved subscription id in `localStorage`.\n * 3. If that id still exists on the broker (`GET` \u2192 200), returns it without\n * `POST`ing. When the push endpoint changed (e.g. VAPID key rotation),\n * `PATCH`es only `notification.webpush` / `endpoint.webpush` so a new\n * subscription is not created (avoids burning the subscription quota).\n * 4. Otherwise `POST`s a new subscription and persists its id.\n *\n * `protocol` is required \u2014 there is no default. A 507 from the broker is\n * thrown as `QuotaExceededError`.\n *\n * @example\n * ```ts\n * import { QuotaExceededError } from '@geolonia/geonicdb-sdk';\n *\n * try {\n * const { subscriptionId, created } = await db.registerWebPushSubscription({\n * protocol: 'ngsiv2',\n * subject: { entities: [{ type: 'Sensor' }], condition: { attrs: ['value'] } },\n * });\n * } catch (err) {\n * if (err instanceof QuotaExceededError) {\n * // plan grace ceiling \u2014 delete unused subscriptions or change plan, then retry\n * }\n * throw err;\n * }\n * ```\n */\n async registerWebPushSubscription(\n options: RegisterWebPushSubscriptionOptions,\n ): Promise<RegisterWebPushSubscriptionResult> {\n if (!options || (options.protocol !== 'ngsiv2' && options.protocol !== 'ngsi-ld')) {\n throw new GeonicDBError(\n \"registerWebPushSubscription() requires protocol: 'ngsiv2' | 'ngsi-ld'\",\n );\n }\n if (options.protocol === 'ngsiv2' && !options.subject?.entities?.length) {\n throw new GeonicDBError(\n \"registerWebPushSubscription({ protocol: 'ngsiv2' }) requires subject.entities\",\n );\n }\n if (options.protocol === 'ngsi-ld' && !options.entities?.length) {\n throw new GeonicDBError(\n \"registerWebPushSubscription({ protocol: 'ngsi-ld' }) requires entities\",\n );\n }\n\n const storageKey =\n options.storageKey ??\n buildWebPushSubscriptionStorageKey(\n this._auth.baseUrl,\n this._auth.tenant,\n options.protocol,\n );\n // Fail before subscribe/POST if we cannot persist the id \u2014 otherwise a\n // successful create followed by a failed write leaves an orphan subscription\n // and the next call POSTs again (#3092 CodeRabbit).\n assertWebPushSubscriptionStorageWritable(storageKey);\n const inflight = this._webPushRegisterInflight.get(storageKey);\n if (inflight) return inflight;\n\n const run = this._registerWebPushSubscriptionBody(options, storageKey).finally(() => {\n if (this._webPushRegisterInflight.get(storageKey) === run) {\n this._webPushRegisterInflight.delete(storageKey);\n }\n });\n this._webPushRegisterInflight.set(storageKey, run);\n return run;\n }\n\n private async _registerWebPushSubscriptionBody(\n options: RegisterWebPushSubscriptionOptions,\n storageKey: string,\n ): Promise<RegisterWebPushSubscriptionResult> {\n const pushSubscription = await this.subscribeWebPush({\n applicationServerKey: options.applicationServerKey,\n serviceWorkerRegistration: options.serviceWorkerRegistration,\n });\n const delivery: WebPushDeliveryOptions = {\n ...(options.ttl !== undefined && { ttl: options.ttl }),\n ...(options.urgency !== undefined && { urgency: options.urgency }),\n ...(options.topic !== undefined && { topic: options.topic }),\n };\n const savedId = readWebPushSubscriptionId(storageKey);\n if (savedId) {\n const collectionPath = webPushSubscriptionCollectionPath(options.protocol);\n const itemPath = `${collectionPath}/${encodeURIComponent(savedId)}`;\n try {\n const existing = (await this.request('GET', itemPath)) as Record<string, unknown>;\n const storedEndpoint = readStoredWebPushEndpoint(options.protocol, existing);\n if (storedEndpoint && storedEndpoint !== pushSubscription.endpoint) {\n await this.request(\n 'PATCH',\n itemPath,\n buildWebPushPatchBody(options.protocol, pushSubscription, delivery),\n );\n }\n return { subscriptionId: savedId, created: false };\n } catch (err) {\n if (!(err instanceof NotFoundError)) throw err;\n clearWebPushSubscriptionId(storageKey);\n }\n }\n\n const createPath = webPushSubscriptionCollectionPath(options.protocol);\n const body = buildWebPushCreateBody(options, pushSubscription, delivery);\n const res = await this.requestRaw('POST', createPath, body);\n if (!res.ok) {\n const e = (await res.json().catch(() => ({}))) as Record<string, string>;\n throw createErrorFromResponse(res.status, e, 'Request failed: ' + res.status);\n }\n const subscriptionId = subscriptionIdFromLocation(\n res.headers.get('Location'),\n options.protocol,\n );\n writeWebPushSubscriptionId(storageKey, subscriptionId);\n return { subscriptionId, created: true };\n }\n\n /**\n * Unsubscribe the current browser from Web Push notifications, if\n * subscribed. Returns `false` (no-op) when there is no active\n * subscription or the environment has no Service Worker support.\n *\n * Does not delete the GeonicDB subscription document \u2014 clear the\n * persisted id (see `buildWebPushSubscriptionStorageKey`) and\n * `DELETE` the subscription separately if needed.\n */\n async unsubscribeWebPush(registration?: ServiceWorkerRegistration): Promise<boolean> {\n /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */\n if (typeof navigator === 'undefined' || !navigator.serviceWorker) return false;\n const reg = registration ?? (await navigator.serviceWorker.ready);\n const existing: PushSubscription | null = await reg.pushManager.getSubscription();\n if (!existing) return false;\n return existing.unsubscribe();\n /* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call */\n }\n}\n\n// Default export for convenient importing\nexport default GeonicDB;\n\n// UMD-style global assignment for browser script tag usage\n/* istanbul ignore next: jsdom defines `window` but bundles run under real\n browsers where this attaches the SDK to the global. Covered by build\n artifact + manual browser tests. */\nif (typeof window !== 'undefined') {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n window.GeonicDB = GeonicDB;\n}\n\n/**\n * Build the NGSI-LD entities query path from `GetEntitiesParams`.\n *\n * Shared by `getEntities()` / `countEntities()` / `poll()` so all stay in sync\n * as NGSI-LD \u30AF\u30A8\u30EA\u30D1\u30E9\u30E1\u30FC\u30BF\u304C\u8FFD\u52A0\u3055\u308C\u308B (review #1043: query-string duplication)\u3002\n *\n * #1132 \u3067 `URLSearchParams` \u30D9\u30FC\u30B9\u306B refactor\u3002\u578B (`GetEntitiesParams`) \u306B\n * \u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u8FFD\u52A0\u3059\u308B\u3060\u3051\u3067\u81EA\u52D5\u7684\u306B URL \u306B\u4E57\u308B\u305F\u3081\u3001\u5F8C\u8FFD\u3044\u3067\u3053\u306E\u30D3\u30EB\u30C0\u3092\n * \u89E6\u308B\u5FC5\u8981\u304C\u306A\u3044\u3002\n */\nfunction buildEntitiesPath(params?: GetEntitiesParams): string {\n return buildPathWithParams(\n '/ngsi-ld/v1/entities',\n params as Record<string, unknown> | undefined,\n );\n}\n\n/**\n * \u6C4E\u7528\u30AF\u30A8\u30EA\u6587\u5B57\u5217\u30D3\u30EB\u30C0\u3002`undefined` / `null` \u306F\u7701\u7565\u3001`boolean` \u306F\n * `'true'` / `'false'` \u306E\u6587\u5B57\u5217\u3001`number` \u306F `String(n)`\u3001\u305D\u308C\u4EE5\u5916\u306F\u6587\u5B57\u5217\u5316\u3002\n * \u914D\u5217 / \u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u306F\u30B5\u30DD\u30FC\u30C8\u5BFE\u8C61\u5916 (NGSI-LD \u4ED5\u69D8\u5185\u306E\u5168\u30D1\u30E9\u30E1\u30FC\u30BF\u306F\n * primitive \u3067\u8868\u73FE\u3067\u304D\u308B\u305F\u3081\u4E0D\u8981)\u3002\n */\nexport function buildPathWithParams(\n path: string,\n params?: Record<string, unknown>,\n): string {\n if (!params) return path;\n const sp = new URLSearchParams();\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue;\n if (typeof value === 'boolean') {\n sp.append(key, value ? 'true' : 'false');\n } else if (typeof value === 'number') {\n sp.append(key, String(value));\n } else if (typeof value === 'string') {\n if (value === '') continue;\n sp.append(key, value);\n } else {\n // \u60F3\u5B9A\u5916\u306E\u578B\u306F\u7121\u8996 (\u5C06\u6765 array/object \u7CFB\u3092\u6271\u3046\u5834\u5408\u306F\u547C\u51FA\u5074\u3067\u6587\u5B57\u5217\u5316)\n continue;\n }\n }\n const qs = sp.toString();\n return qs ? `${path}?${qs}` : path;\n}\n\nfunction webPushSubscriptionCollectionPath(protocol: WebPushRegisterProtocol): string {\n return protocol === 'ngsiv2' ? '/v2/subscriptions' : '/ngsi-ld/v1/subscriptions';\n}\n\nfunction subscriptionIdFromLocation(\n location: string | null,\n protocol: WebPushRegisterProtocol,\n): string {\n if (!location) {\n throw new GeonicDBError('Missing Location header after Web Push subscription create');\n }\n const marker =\n protocol === 'ngsiv2' ? '/v2/subscriptions/' : '/ngsi-ld/v1/subscriptions/';\n const idx = location.indexOf(marker);\n const raw = idx >= 0 ? location.slice(idx + marker.length) : location.split('/').pop() || '';\n // Strip query/fragment if a full URL was returned.\n const id = decodeURIComponent(raw.split('?')[0].split('#')[0]);\n if (!id) {\n throw new GeonicDBError('Could not parse subscription id from Location: ' + location);\n }\n return id;\n}\n\nfunction readStoredWebPushEndpoint(\n protocol: WebPushRegisterProtocol,\n body: Record<string, unknown>,\n): string | undefined {\n const notification = body.notification as Record<string, unknown> | undefined;\n if (!notification) return undefined;\n if (protocol === 'ngsiv2') {\n const webpush = notification.webpush as { url?: string } | undefined;\n return webpush?.url;\n }\n const endpoint = notification.endpoint as { uri?: string } | undefined;\n return endpoint?.uri;\n}\n\nfunction buildWebPushCreateBody(\n options: RegisterWebPushSubscriptionOptions,\n pushSubscription: PushSubscription,\n delivery: WebPushDeliveryOptions,\n): Record<string, unknown> {\n const description = options.description ?? 'Web Push notification';\n if (options.protocol === 'ngsiv2') {\n return {\n description,\n subject: options.subject,\n notification: {\n webpush: toNgsiv2WebPushNotification(pushSubscription, delivery),\n },\n };\n }\n const { uri, webpush } = toNgsiLdWebPushEndpoint(pushSubscription, delivery);\n return {\n type: 'Subscription',\n description,\n entities: options.entities,\n ...(options.watchedAttributes && { watchedAttributes: options.watchedAttributes }),\n ...(options.q && { q: options.q }),\n notification: {\n endpoint: { uri, accept: 'application/json', protocol: 'webpush', webpush },\n },\n };\n}\n\nfunction buildWebPushPatchBody(\n protocol: WebPushRegisterProtocol,\n pushSubscription: PushSubscription,\n delivery: WebPushDeliveryOptions,\n): Record<string, unknown> {\n if (protocol === 'ngsiv2') {\n return {\n notification: {\n webpush: toNgsiv2WebPushNotification(pushSubscription, delivery),\n },\n };\n }\n const { uri, webpush } = toNgsiLdWebPushEndpoint(pushSubscription, delivery);\n return {\n notification: {\n endpoint: { uri, accept: 'application/json', protocol: 'webpush', webpush },\n },\n };\n}\n\nfunction readWebPushSubscriptionId(storageKey: string): string | null {\n try {\n if (typeof localStorage === 'undefined') return null;\n return localStorage.getItem(storageKey);\n } catch {\n return null;\n }\n}\n\nfunction writeWebPushSubscriptionId(storageKey: string, subscriptionId: string): void {\n try {\n if (typeof localStorage === 'undefined') {\n throw new GeonicDBError(\n 'registerWebPushSubscription() requires localStorage for idempotent registration',\n );\n }\n localStorage.setItem(storageKey, subscriptionId);\n } catch (err) {\n if (err instanceof GeonicDBError) throw err;\n throw new GeonicDBError(\n 'registerWebPushSubscription() could not persist subscription id to localStorage',\n );\n }\n}\n\n/**\n * Probe that `localStorage` can store values before creating a remote\n * subscription. Uses a sibling probe key so an existing persisted id is not\n * cleared. Avoids orphan GeonicDB documents when write fails after POST.\n */\nfunction assertWebPushSubscriptionStorageWritable(storageKey: string): void {\n const probeKey = storageKey + '::__writable__';\n try {\n if (typeof localStorage === 'undefined') {\n throw new GeonicDBError(\n 'registerWebPushSubscription() requires localStorage for idempotent registration',\n );\n }\n localStorage.setItem(probeKey, '1');\n if (localStorage.getItem(probeKey) !== '1') {\n throw new GeonicDBError(\n 'registerWebPushSubscription() could not persist subscription id to localStorage',\n );\n }\n localStorage.removeItem(probeKey);\n } catch (err) {\n try {\n if (typeof localStorage !== 'undefined') localStorage.removeItem(probeKey);\n } catch {\n // ignore cleanup failure\n }\n if (err instanceof GeonicDBError) throw err;\n throw new GeonicDBError(\n 'registerWebPushSubscription() could not persist subscription id to localStorage',\n );\n }\n}\n\nfunction clearWebPushSubscriptionId(storageKey: string): void {\n try {\n if (typeof localStorage === 'undefined') return;\n localStorage.removeItem(storageKey);\n } catch {\n // ignore \u2014 next create will overwrite if storage becomes available\n }\n}\n", "/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\nimport type { GeonicDBEventMap } from './types';\n\ntype Listener<T> = (data: T) => void;\n\n/**\n * Minimal typed event emitter for GeonicDB SDK.\n */\nexport class EventEmitter {\n private _listeners: Record<string, Listener<unknown>[]> =\n Object.create(null) as Record<string, Listener<unknown>[]>;\n\n on<K extends keyof GeonicDBEventMap>(\n event: K,\n callback: Listener<GeonicDBEventMap[K]>,\n ): this;\n on(event: string, callback: Listener<unknown>): this;\n on(event: string, callback: Listener<unknown>): this {\n if (!this._listeners[event]) {\n this._listeners[event] = [];\n }\n this._listeners[event].push(callback);\n return this;\n }\n\n off<K extends keyof GeonicDBEventMap>(\n event: K,\n callback?: Listener<GeonicDBEventMap[K]>,\n ): this;\n off(event: string, callback?: Listener<unknown>): this;\n off(event: string, callback?: Listener<unknown>): this {\n if (!this._listeners[event]) return this;\n if (!callback) {\n delete this._listeners[event];\n } else {\n this._listeners[event] = this._listeners[event].filter(\n (cb) => cb !== callback,\n );\n }\n return this;\n }\n\n protected emit<K extends keyof GeonicDBEventMap>(\n event: K,\n data?: GeonicDBEventMap[K],\n ): void;\n protected emit(event: string, data?: unknown): void;\n protected emit(event: string, data?: unknown): void {\n const cbs = this._listeners[event];\n if (!cbs) return;\n const snapshot = cbs.slice();\n for (const cb of snapshot) {\n try {\n cb(data);\n } catch {\n // listener error \u2014 swallow silently\n }\n }\n }\n}\n", "/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\n/** Default token TTL in seconds when expiresIn is not specified in setCredentials(). */\nexport const DEFAULT_TOKEN_TTL_SEC = 3_600;\n\n/** Token refresh leeway in ms \u2014 refresh 2 minutes before expiry. */\nexport const TOKEN_REFRESH_LEEWAY_MS = 120_000;\n\n/** Minimum wait time in ms before refreshing a token. */\nexport const TOKEN_REFRESH_MIN_MS = 10_000;\n\n/** Maximum WebSocket reconnection attempts. */\nexport const RECONNECT_MAX_ATTEMPTS = 10;\n\n/** Base delay in ms for exponential backoff reconnection. */\nexport const RECONNECT_BASE_MS = 1_000;\n\n/** Maximum delay in ms for exponential backoff reconnection. */\nexport const RECONNECT_MAX_DELAY_MS = 30_000;\n\n/** WebSocket sub-protocol name for browser authentication. */\nexport const SUB_PROTOCOL = 'access_token';\n\n/**\n * NGSI-LD core @context. Injected into `application/ld+json` request bodies that\n * omit `@context` so writes stay spec-compliant (#1583): the broker requires an\n * inline `@context` for `application/ld+json` (ETSI GS CIM 009 clause 6.3.5).\n */\nexport const NGSI_LD_CORE_CONTEXT =\n 'https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.9.jsonld';\n\n// ============================================================\n// SDK Client Cache (#1043 Phase A)\n// Mirrors `SDK_CACHE` in src/config/defaults.ts. Update both when changing.\n// ============================================================\n\n/** In-memory cache LRU capacity (entries). */\nexport const SDK_CACHE_MAX_ENTRIES_DEFAULT = 1_000;\n\n/** Default polling interval for `db.poll()` in milliseconds. */\nexport const SDK_POLL_INTERVAL_MS_DEFAULT = 5_000;\n\n// ============================================================\n// DPoP session persistence (#1230)\n// ============================================================\n\n/**\n * IndexedDB database name for persisted DPoP sessions. Origin-scoped, so\n * multiple SDK instances in the same origin share the same database.\n */\nexport const DPOP_IDB_NAME = 'geonicdb-sdk';\n\n/** IndexedDB object store name. Single fixed entry keyed by tenant. */\nexport const DPOP_IDB_STORE = 'dpop-sessions';\n\n/** Bumped when the on-disk session shape changes. */\nexport const DPOP_IDB_VERSION = 1;\n\n// ============================================================\n// Persistent ETag cache (#1262)\n// ============================================================\n//\n// The ETag/If-None-Match cache (`SdkCache`) is in-memory and therefore lost on\n// page reload. To keep the `304` revalidation path working after a reload, the\n// cache is mirrored into IndexedDB and rehydrated when a remembered session is\n// restored. A separate database from the DPoP store avoids version-coordination\n// between the two persistence concerns.\n\n/**\n * IndexedDB database name for the persisted SDK response cache. Origin-scoped,\n * so multiple SDK instances in the same origin share the same database.\n */\nexport const SDK_CACHE_IDB_NAME = 'geonicdb-sdk-cache';\n\n/** IndexedDB object store name. Records keyed by `${scope}\\u0000${cacheKey}`. */\nexport const SDK_CACHE_IDB_STORE = 'responses';\n\n/** Bumped when the on-disk cache record shape changes. */\nexport const SDK_CACHE_IDB_VERSION = 1;\n\n// ============================================================\n// Web Push (#3014 / #3060)\n// ============================================================\n\n/**\n * Discovery endpoint for the deployment's VAPID public key.\n *\n * SDK \u306F npm \u5411\u3051\u306B\u5358\u72EC\u30D3\u30EB\u30C9\u3055\u308C\u672C\u4F53\u306E `@config/defaults` \u3092 import \u3057\u306A\u3044\u305F\u3081\u3001\n * \u3053\u3053\u3067\u30EA\u30C6\u30E9\u30EB\u3068\u3057\u3066\u6301\u3064\u3002**\u672C\u4F53\u306E `DISCOVERY.WEBPUSH_VAPID_KEY_PATH`\n * (`src/config/defaults.ts`) \u3092\u5909\u3048\u305F\u3089\u3053\u306E\u5024\u3082\u540C\u671F\u3059\u308B\u3053\u3068** (SDK Consistency)\u3002\n */\nexport const WEBPUSH_VAPID_KEY_PATH = '/.well-known/webpush-vapid-key';\n\n/**\n * localStorage key prefix for the GeonicDB subscription id persisted by\n * `GeonicDB.registerWebPushSubscription()` (#3092). The full key also includes\n * baseUrl, tenant, and protocol so one origin can talk to multiple deployments\n * without colliding.\n */\nexport const WEBPUSH_SUBSCRIPTION_ID_STORAGE_PREFIX = 'geonicdb:webpush:subscriptionId';\n", "/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\nimport { DPOP_IDB_NAME, DPOP_IDB_STORE, DPOP_IDB_VERSION } from './constants';\nimport type { DPoPKeyPair, PersistedDPoPSession } from './types';\n\n/** Whether the Web Crypto DPoP API is available in the current environment. */\nexport const dpopSupported =\n typeof crypto !== 'undefined' &&\n !!crypto.subtle &&\n !!crypto.subtle.generateKey;\n\n/**\n * Whether IndexedDB is available. We require it for DPoP session persistence\n * because \u2014 unlike localStorage \u2014 the structured clone algorithm can store a\n * non-extractable `CryptoKey` object as-is, preserving `extractable: false`\n * across page reloads (#1230).\n */\nexport const dpopPersistenceSupported =\n typeof (globalThis as { indexedDB?: unknown }).indexedDB !== 'undefined' &&\n typeof structuredClone !== 'undefined';\n\n/* istanbul ignore next: b64url is exercised only via createDPoPProof which\n itself requires a real Web Crypto subtle.sign \u2014 bundled and E2E only. */\nfunction b64url(buf: ArrayBuffer | Uint8Array): string {\n const arr = buf instanceof ArrayBuffer ? new Uint8Array(buf) : buf;\n let str = '';\n for (let i = 0; i < arr.length; i++) {\n str += String.fromCharCode(arr[i]);\n }\n return btoa(str).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/* istanbul ignore next: helper used only by createDPoPProof; covered in E2E. */\nfunction strToUint8(s: string): Uint8Array {\n return new TextEncoder().encode(s);\n}\n\n/* istanbul ignore next: requires a real Web Crypto subtle.generateKey;\n covered by E2E auth/WS flow. */\n/** Generate an ECDSA P-256 key pair for DPoP proof creation. */\nexport async function generateDPoPKeyPair(): Promise<DPoPKeyPair | null> {\n if (!dpopSupported) return null;\n const kp = await crypto.subtle.generateKey(\n { name: 'ECDSA', namedCurve: 'P-256' },\n false,\n ['sign'],\n );\n const pubJwk = await crypto.subtle.exportKey(\n 'jwk',\n kp.publicKey,\n );\n return {\n privateKey: kp.privateKey,\n publicJwk: {\n kty: pubJwk.kty!,\n crv: pubJwk.crv!,\n x: pubJwk.x!,\n y: pubJwk.y!,\n },\n };\n}\n\n/* istanbul ignore next: ECDSA signing requires real Web Crypto; E2E-only. */\n/**\n * Create a DPoP proof JWT (RFC 9449).\n *\n * @param keyPair - The DPoP key pair\n * @param htm - HTTP method\n * @param htu - HTTP target URI\n * @param accessToken - Optional access token for ath claim\n * @param nonce - Optional server-provided nonce\n * @returns Signed DPoP proof JWT or null if keyPair is null\n */\nexport async function createDPoPProof(\n keyPair: DPoPKeyPair | null,\n htm: string,\n htu: string,\n accessToken?: string | null,\n nonce?: string | null,\n): Promise<string | null> {\n if (!keyPair) return null;\n\n const header = { alg: 'ES256', typ: 'dpop+jwt', jwk: keyPair.publicJwk };\n const payload: Record<string, unknown> = {\n jti:\n typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID()\n : Date.now().toString(36) + Math.random().toString(36).substr(2),\n htm,\n htu,\n iat: Math.floor(Date.now() / 1000),\n };\n if (nonce) payload.nonce = nonce;\n\n if (accessToken) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\n const hash = await crypto.subtle.digest('SHA-256', strToUint8(accessToken) as unknown as ArrayBuffer);\n payload.ath = b64url(hash);\n }\n\n const headerB64 = b64url(strToUint8(JSON.stringify(header)));\n const payloadB64 = b64url(strToUint8(JSON.stringify(payload)));\n const signingInput = headerB64 + '.' + payloadB64;\n\n /* eslint-disable @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unnecessary-type-assertion */\n const sigBuf = await crypto.subtle.sign(\n { name: 'ECDSA', hash: 'SHA-256' },\n keyPair.privateKey,\n strToUint8(signingInput) as unknown as ArrayBuffer,\n );\n /* eslint-enable @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unnecessary-type-assertion */\n // WebCrypto ECDSA returns IEEE P1363 r||s format (64 bytes for P-256)\n return signingInput + '.' + b64url(new Uint8Array(sigBuf));\n}\n\n// ============================================================\n// DPoP session persistence \u2014 IndexedDB (#1230)\n// ============================================================\n//\n// localStorage \u306B DPoP \u30BB\u30C3\u30B7\u30E7\u30F3\u3092\u4FDD\u5B58\u3067\u304D\u306A\u3044\u306E\u306F\u3001CryptoKey \u304C\u6587\u5B57\u5217\u306B\n// \u30B7\u30EA\u30A2\u30E9\u30A4\u30BA\u3067\u304D\u306A\u3044\u305F\u3081\u3002JWK \u306B\u30A8\u30AF\u30B9\u30DD\u30FC\u30C8\u3059\u308B\u306B\u306F extractable: true \u3067\n// \u518D\u751F\u6210\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u3001\u305D\u306E\u6642\u70B9\u3067 XSS \u3067\u9375\u304C\u6F0F\u308C\u308B\u3002\n//\n// IndexedDB \u306F\u69CB\u9020\u5316\u8907\u88FD\u30A2\u30EB\u30B4\u30EA\u30BA\u30E0\u3067 CryptoKey \u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3092\u305D\u306E\u307E\u307E\u4FDD\u5B58\n// \u3067\u304D\u308B\u3002\u9375\u306F extractable: false \u306E\u307E\u307E\u6C38\u7D9A\u5316\u3055\u308C\u3001JavaScript \u304B\u3089\u306F\u4F9D\u7136\u3068\n// \u3057\u3066\u30A8\u30AF\u30B9\u30DD\u30FC\u30C8\u4E0D\u53EF\u3002\u30EA\u30ED\u30FC\u30C9\u6642\u306F\u540C\u3058\u9375\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u304C\u5FA9\u5143\u3055\u308C\u308B\u306E\u3067\n// DPoP \u30D7\u30EB\u30FC\u30D5\u7F72\u540D\u3092\u518D\u958B\u3067\u304D\u308B\u3002\u3053\u308C\u304C Web \u306E DPoP \u5B9F\u88C5\u306E\u696D\u754C\u6A19\u6E96\u3002\n//\n// \u30B9\u30C8\u30A2\u306F tenant \u3092\u30AD\u30FC\u306B\u3059\u308B\uFF1A\u540C\u3058 origin \u3067\u8907\u6570\u30C6\u30CA\u30F3\u30C8\u3092\u4F7F\u3044\u5206\u3051\u308B\n// \u30A2\u30D7\u30EA\u3067\u3082\u3001\u5404\u30C6\u30CA\u30F3\u30C8\u306E DPoP \u30BB\u30C3\u30B7\u30E7\u30F3\u304C\u72EC\u7ACB\u3057\u3066\u4FDD\u305F\u308C\u308B\u3002\n//\n// IndexedDB \u306E\u578B\u306F DOM lib \u306B\u3057\u304B\u5B58\u5728\u3057\u306A\u3044\u306E\u3067\u3001SDK \u30D3\u30EB\u30C9\u306F\n// `lib: [\"DOM\"]` \u3067\u901A\u308B\u304C\u3001\u30EA\u30DD\u30B8\u30C8\u30EA\u5168\u4F53\u306E ESLint \u304C\u898B\u308B tsconfig \u306F\n// Node \u7528\u306E lib \u306E\u307F\u3002SDK \u30D5\u30A1\u30A4\u30EB\u3060\u3051\u5225 lint config \u306B\u5207\u308A\u51FA\u3059\u3088\u308A\u3001\n// IndexedDB \u3092\u89E6\u308B\u95A2\u6570\u7FA4\u3092\u5C40\u6240\u7684\u306B eslint-disable \u3059\u308B\u65B9\u304C\u5909\u66F4\u7BC4\u56F2\u304C\u5C0F\u3055\u3044\u3002\n\n/* eslint-disable @typescript-eslint/no-unsafe-call,\n @typescript-eslint/no-unsafe-member-access,\n @typescript-eslint/prefer-promise-reject-errors,\n @typescript-eslint/no-unsafe-assignment,\n @typescript-eslint/no-unsafe-return,\n @typescript-eslint/no-unsafe-argument,\n @typescript-eslint/no-redundant-type-constituents */\n\n/* istanbul ignore next: trivial wrapper, exercised in E2E and in the\n fake-indexeddb-backed unit tests. */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction openSessionDB(): Promise<any> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return new Promise<any>((resolve, reject) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const req: any = (globalThis as any).indexedDB.open(DPOP_IDB_NAME, DPOP_IDB_VERSION);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains(DPOP_IDB_STORE)) {\n db.createObjectStore(DPOP_IDB_STORE);\n }\n };\n req.onsuccess = () => { resolve(req.result); };\n req.onerror = () => { reject(req.error ?? new Error('IndexedDB open failed')); };\n });\n}\n\n/**\n * Persist a DPoP session to IndexedDB, keyed by tenant.\n *\n * Safe to call repeatedly: each call replaces the prior entry for the same\n * tenant. Returns false (without throwing) when IndexedDB is unavailable or\n * the write fails \u2014 callers should treat persistence as best-effort and not\n * abort the login flow.\n */\nexport async function saveDPoPSession(\n session: PersistedDPoPSession,\n): Promise<boolean> {\n if (!dpopPersistenceSupported) return false;\n // IndexedDB \u306E\u578B\u306F DOM lib \u306B\u3057\u304B\u306A\u3044\u3002SDK \u30D3\u30EB\u30C9\u306F lib: DOM \u3067\u901A\u308B\u304C\u3001\n // \u30EA\u30DD\u30B8\u30C8\u30EA\u5168\u4F53\u306E lint context \u306B DOM lib \u3092\u6F0F\u6D29\u3055\u305B\u305F\u304F\u306A\u3044\u306E\u3067\u5C40\u6240\u7684\u306B\n // any \u3068\u3057\u3066\u6271\u3046 (\u3053\u306E\u30BB\u30AF\u30B7\u30E7\u30F3\u5192\u982D\u306E eslint-disable \u3067\u8A31\u5BB9)\u3002\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let db: any = null;\n try {\n db = await openSessionDB();\n await new Promise<void>((resolve, reject) => {\n const tx = db!.transaction(DPOP_IDB_STORE, 'readwrite');\n tx.objectStore(DPOP_IDB_STORE).put(session, session.tenant);\n tx.oncomplete = () => { resolve(); };\n tx.onerror = () => { reject(tx.error ?? new Error('IndexedDB put failed')); };\n tx.onabort = () => { reject(tx.error ?? new Error('IndexedDB transaction aborted')); };\n });\n return true;\n } catch {\n /* istanbul ignore next: write-path catch is reachable only when IDB\n itself faults (quota exceeded, blocked tab, ...) \u2014 best-effort path,\n covered via E2E rather than unit tests. */\n return false;\n } finally {\n db?.close();\n }\n}\n\n/**\n * Load a previously persisted DPoP session for the given tenant.\n *\n * Returns null when no session is stored, when persistence is unavailable,\n * or when the stored session is shaped unexpectedly (schema drift across\n * versions). Never throws.\n */\nexport async function loadDPoPSession(\n tenant: string,\n): Promise<PersistedDPoPSession | null> {\n if (!dpopPersistenceSupported) return null;\n // IndexedDB \u306E\u578B\u306F DOM lib \u306B\u3057\u304B\u306A\u3044\u3002SDK \u30D3\u30EB\u30C9\u306F lib: DOM \u3067\u901A\u308B\u304C\u3001\n // \u30EA\u30DD\u30B8\u30C8\u30EA\u5168\u4F53\u306E lint context \u306B DOM lib \u3092\u6F0F\u6D29\u3055\u305B\u305F\u304F\u306A\u3044\u306E\u3067\u5C40\u6240\u7684\u306B\n // any \u3068\u3057\u3066\u6271\u3046 (\u3053\u306E\u30BB\u30AF\u30B7\u30E7\u30F3\u5192\u982D\u306E eslint-disable \u3067\u8A31\u5BB9)\u3002\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let db: any = null;\n try {\n db = await openSessionDB();\n const data = await new Promise<unknown>((resolve, reject) => {\n const tx = db!.transaction(DPOP_IDB_STORE, 'readonly');\n const req = tx.objectStore(DPOP_IDB_STORE).get(tenant);\n req.onsuccess = () => { resolve(req.result); };\n req.onerror = () => { reject(req.error ?? new Error('IndexedDB get failed')); };\n });\n return validatePersistedSession(data);\n } catch {\n /* istanbul ignore next: read-path catch is reachable only when IDB\n itself faults (db deletion mid-read, blocked tab, ...) \u2014 covered\n via E2E rather than unit tests. */\n return null;\n } finally {\n db?.close();\n }\n}\n\n/**\n * Remove the persisted DPoP session for the given tenant. Always resolves \u2014\n * idempotent and never throws, so logout() flows can call it unconditionally.\n */\nexport async function clearDPoPSession(tenant: string): Promise<void> {\n if (!dpopPersistenceSupported) return;\n // IndexedDB \u306E\u578B\u306F DOM lib \u306B\u3057\u304B\u306A\u3044\u3002SDK \u30D3\u30EB\u30C9\u306F lib: DOM \u3067\u901A\u308B\u304C\u3001\n // \u30EA\u30DD\u30B8\u30C8\u30EA\u5168\u4F53\u306E lint context \u306B DOM lib \u3092\u6F0F\u6D29\u3055\u305B\u305F\u304F\u306A\u3044\u306E\u3067\u5C40\u6240\u7684\u306B\n // any \u3068\u3057\u3066\u6271\u3046 (\u3053\u306E\u30BB\u30AF\u30B7\u30E7\u30F3\u5192\u982D\u306E eslint-disable \u3067\u8A31\u5BB9)\u3002\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let db: any = null;\n try {\n db = await openSessionDB();\n await new Promise<void>((resolve, reject) => {\n const tx = db!.transaction(DPOP_IDB_STORE, 'readwrite');\n tx.objectStore(DPOP_IDB_STORE).delete(tenant);\n tx.oncomplete = () => { resolve(); };\n tx.onerror = () => { reject(tx.error ?? new Error('IndexedDB delete failed')); };\n tx.onabort = () => { reject(tx.error ?? new Error('IndexedDB transaction aborted')); };\n });\n } catch {\n // swallow \u2014 clearing is best-effort\n } finally {\n db?.close();\n }\n}\n\nfunction validatePersistedSession(data: unknown): PersistedDPoPSession | null {\n if (!data || typeof data !== 'object') return null;\n const s = data as Record<string, unknown>;\n const kp = s.keyPair as Record<string, unknown> | undefined;\n if (\n typeof s.tenant !== 'string' ||\n typeof s.accessToken !== 'string' ||\n typeof s.refreshToken !== 'string' ||\n typeof s.expiresAt !== 'number' ||\n !kp ||\n !kp.privateKey ||\n !kp.publicJwk\n ) {\n return null;\n }\n return data as PersistedDPoPSession;\n}\n\n/* eslint-enable @typescript-eslint/no-unsafe-call,\n @typescript-eslint/no-unsafe-member-access,\n @typescript-eslint/prefer-promise-reject-errors,\n @typescript-eslint/no-unsafe-assignment,\n @typescript-eslint/no-unsafe-return,\n @typescript-eslint/no-unsafe-argument,\n @typescript-eslint/no-redundant-type-constituents */\n", "/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\nconst BATCH_SIZE = 1000;\nconst MAX_ITERATIONS = 1_000_000;\n\n/** Check whether the first `bits` bits of a hash are zero. */\nfunction checkZeros(hash: Uint8Array, bits: number): boolean {\n const fullBytes = Math.floor(bits / 8);\n const remainBits = bits % 8;\n for (let k = 0; k < fullBytes; k++) {\n if (hash[k] !== 0) return false;\n }\n if (remainBits > 0) {\n const mask = 0xff << (8 - remainBits);\n if ((hash[fullBytes] & mask) !== 0) return false;\n }\n return true;\n}\n\n/**\n * Solve a Proof of Work challenge asynchronously using Web Crypto.\n *\n * Hashes `challenge + nonce` (SHA-256) in batches and checks for\n * the required number of leading zero bits.\n *\n * @param challenge - The challenge string from the server\n * @param difficulty - Number of leading zero bits required\n * @returns The nonce that satisfies the difficulty\n */\nexport async function solvePoW(\n challenge: string,\n difficulty: number,\n): Promise<number> {\n if (!Number.isInteger(difficulty) || difficulty < 0 || difficulty > 256) {\n throw new Error('difficulty must be an integer between 0 and 256');\n }\n const encoder = new TextEncoder();\n let i = 0;\n\n async function tryBatch(): Promise<number> {\n const batchEnd = Math.min(i + BATCH_SIZE, MAX_ITERATIONS);\n const promises: Promise<{ idx: number; hash: Uint8Array }>[] = [];\n\n for (; i < batchEnd; i++) {\n const idx = i;\n promises.push(\n crypto.subtle\n .digest('SHA-256', encoder.encode(challenge + String(idx)))\n .then((buf) => ({ idx, hash: new Uint8Array(buf) })),\n );\n }\n\n const results = await Promise.all(promises);\n for (const result of results) {\n if (checkZeros(result.hash, difficulty)) {\n return result.idx;\n }\n }\n /* istanbul ignore next: hitting MAX_ITERATIONS (1e6) without a solution is\n practically unreachable for realistic difficulties (<= ~20 bits) and\n would make tests run for many seconds. Covered indirectly by tryBatch's\n recursion via the difficulty-8 test. */\n if (i >= MAX_ITERATIONS) throw new Error('PoW solution not found');\n /* istanbul ignore next: recursion path triggers only when the first batch\n of 1000 hashes finds no solution. With difficulty <= 16 this rarely\n happens within unit-test timeouts. */\n return tryBatch();\n }\n\n return tryBatch();\n}\n", "/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\n/**\n * Which request produced an error.\n *\n * `login()` performs two requests \u2014 `POST /auth/login` and then\n * `POST /auth/dpop-bind` \u2014 and both surface the server's `detail` /\n * `description` verbatim as an `AuthenticationError`. Without this, a rejected\n * credential and a rejected DPoP bind are indistinguishable from the error\n * alone, so callers (and anyone reading a bug report) cannot tell which step\n * actually failed.\n */\nexport interface RequestContext {\n method: string;\n /**\n * The endpoint path, without the base URL \u2014 e.g. `/auth/dpop-bind`. When the\n * base URL itself carries a path prefix (`https://host/broker`), the path on\n * the wire includes that prefix; this field names the endpoint, not the full\n * request path.\n */\n path: string;\n}\n\n/**\n * Base error class for GeonicDB SDK.\n * All SDK errors extend this class, allowing `instanceof GeonicDBError` checks.\n */\nexport class GeonicDBError extends Error {\n /** HTTP status code (if applicable) */\n readonly statusCode: number;\n\n /** The request that failed, when the SDK knows it. */\n readonly request?: RequestContext;\n\n constructor(message: string, statusCode = 0) {\n super(message);\n this.name = 'GeonicDBError';\n this.statusCode = statusCode;\n }\n}\n\n/**\n * Record which request an error came from: sets `request` and prefixes the\n * message with `[METHOD path]`. Returns the same instance so it can wrap a\n * `throw` expression directly, and preserves the concrete error class.\n */\nexport function withRequest<E extends GeonicDBError>(error: E, request: RequestContext): E {\n (error as { request?: RequestContext }).request = request;\n error.message = `[${request.method} ${request.path}] ${error.message}`;\n return error;\n}\n\n/**\n * Authentication failed (invalid credentials, expired token, or an HTTP\n * failure on an auth endpoint). Defaults to 401; when thrown from an HTTP\n * response, pass the real status so callers see what the server returned\n * (#2649). Subclasses override with 409 / 403 for control-flow errors.\n */\nexport class AuthenticationError extends GeonicDBError {\n constructor(message = 'Authentication failed', statusCode = 401) {\n super(message, statusCode);\n this.name = 'AuthenticationError';\n }\n}\n\n/** 403 \u2014 Authorization denied (insufficient permissions). */\nexport class AuthorizationError extends GeonicDBError {\n constructor(message = 'Access denied') {\n super(message, 403);\n this.name = 'AuthorizationError';\n }\n}\n\n/** 404 \u2014 Resource not found. */\nexport class NotFoundError extends GeonicDBError {\n constructor(message = 'Not found') {\n super(message, 404);\n this.name = 'NotFoundError';\n }\n}\n\n/** 409 \u2014 Conflict (e.g. entity already exists). */\nexport class ConflictError extends GeonicDBError {\n constructor(message = 'Conflict') {\n super(message, 409);\n this.name = 'ConflictError';\n }\n}\n\n/**\n * 409 \u2014 \u4E00\u6642\u30D1\u30B9\u30EF\u30FC\u30C9\u3067\u306E\u521D\u56DE\u30ED\u30B0\u30A4\u30F3\u3067\u3001\u65B0\u30D1\u30B9\u30EF\u30FC\u30C9\u306E\u8A2D\u5B9A\u304C\u5FC5\u8981 (#1532)\u3002\n * `login(email, tempPassword)` \u304C\u3053\u308C\u3092\u6295\u3052\u305F\u3089\u3001\u65B0\u30D1\u30B9\u30EF\u30FC\u30C9\u3092\u96C6\u3081\u3066\n * `login(email, tempPassword, { newPassword })` \u3092\u547C\u3073\u76F4\u3059\u3002\n */\nexport class PasswordResetRequiredError extends AuthenticationError {\n constructor(message = 'Password reset required: set a new password to complete login') {\n super(message, 409);\n this.name = 'PasswordResetRequiredError';\n }\n}\n\n/**\n * 403 \u2014 \u4E00\u6642\u30D1\u30B9\u30EF\u30FC\u30C9\u306E\u6709\u52B9\u671F\u9650\u5207\u308C (#1532)\u3002\u7BA1\u7406\u8005\u306B\u518D\u767A\u884C\u3092\u4F9D\u983C\u3059\u308B\u3002\n */\nexport class TemporaryPasswordExpiredError extends AuthenticationError {\n constructor(message = 'Temporary password expired: ask an administrator to re-issue it') {\n super(message, 403);\n this.name = 'TemporaryPasswordExpiredError';\n }\n}\n\n/** 422 \u2014 Validation error (bad request payload). */\nexport class ValidationError extends GeonicDBError {\n constructor(message = 'Validation failed') {\n super(message, 422);\n this.name = 'ValidationError';\n }\n}\n\n/** 429 \u2014 Rate limited. */\nexport class RateLimitError extends GeonicDBError {\n /** Seconds to wait before retrying (from Retry-After header) */\n readonly retryAfter: number;\n\n constructor(message = 'Rate limit exceeded', retryAfter = 1) {\n super(message, 429);\n this.name = 'RateLimitError';\n this.retryAfter = retryAfter;\n }\n}\n\n/**\n * 507 \u2014 Storage quota exceeded (subscriptions / entities / registrations /\n * temporal data points beyond the plan's grace ceiling). Callers typically\n * surface a \"try again later\" toast rather than treating this as a permanent\n * failure (#3092).\n */\nexport class QuotaExceededError extends GeonicDBError {\n constructor(message = 'Quota exceeded') {\n super(message, 507);\n this.name = 'QuotaExceededError';\n }\n}\n\n/** Network error (fetch failed, DNS resolution, timeout). */\nexport class NetworkError extends GeonicDBError {\n constructor(message = 'Network error') {\n super(message, 0);\n this.name = 'NetworkError';\n }\n}\n\n/**\n * Create a typed error from an HTTP response status and error body.\n * Used internally by SDK methods to throw the appropriate error class.\n */\nexport function createErrorFromResponse(\n status: number,\n body: Record<string, string>,\n fallbackMessage: string,\n): GeonicDBError {\n const message = body.detail || body.description || fallbackMessage;\n switch (status) {\n case 401:\n return new AuthenticationError(message);\n case 403:\n return new AuthorizationError(message);\n case 404:\n return new NotFoundError(message);\n case 409:\n return new ConflictError(message);\n case 422:\n return new ValidationError(message);\n case 429:\n return new RateLimitError(message);\n case 507:\n return new QuotaExceededError(message);\n default:\n return new GeonicDBError(message, status);\n }\n}\n", "/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\nimport { SDK_CACHE_MAX_ENTRIES_DEFAULT } from './constants';\nimport type { CachePersistence } from './cache-persistence';\n\n/**\n * In-memory client-side cache for the GeonicDB SDK (#991 Phase A).\n *\n * Stores ETag / Last-Modified / response body / response headers per\n * `${method}:${path}` key. The cache layer is opt-out (enabled by default) and\n * driven by the SDK's `request()` method:\n *\n * 1. On a cacheable GET, the SDK looks up the cache and adds `If-None-Match` /\n * `If-Modified-Since` to the outgoing request.\n * 2. If the server returns `304 Not Modified`, the cached body is returned to\n * the caller (transparent to the consumer).\n * 3. On `200 OK`, the cache entry is replaced with the fresh body and ETag.\n * 4. WebSocket entity events do **not** auto-invalidate the cache. Because\n * data endpoints respond with `Cache-Control: private, no-cache`, every\n * cached read already revalidates with the origin via `If-None-Match`,\n * so the server returns `304` if data is unchanged or `200` with the\n * fresh body if it changed. Keeping the cache entries during WebSocket\n * activity preserves the bandwidth-saving `304` path; deleting them\n * forces a full `200` body on the next read with no benefit.\n * 5. Use `clearCache()` for explicit, full invalidation (e.g. on auth change).\n *\n * Persistence (#1262): when a persistence backend is attached and enabled\n * (browser + remembered session), `set`/`delete` write through to IndexedDB\n * so cached validators survive a page reload. `hydrate()` reloads them after a\n * session is restored, so the first request after a reload can send\n * `If-None-Match` instead of cold-starting into a full `200`. In non-browser\n * runtimes (no IndexedDB) the cache stays purely in-memory.\n *\n * Eviction is LRU based on `maxEntries`. There is no TTL \u2014 freshness is\n * delegated entirely to the server's RFC 7234 `Cache-Control` directives,\n * which the SDK respects via revalidation (the server returns\n * `private, no-cache` for data endpoints \u2014 `private` blocks shared / CDN\n * caches and `no-cache` forces revalidation from the SDK's private cache,\n * so every cached lookup re-validates with the origin).\n */\n\nexport interface CacheEntry {\n /** ETag captured from the previous response. */\n etag?: string;\n /** Last-Modified captured from the previous response (RFC 1123 HTTP-date). */\n lastModified?: string;\n /**\n * Cached response body. Stored as the parsed JSON value (or string for\n * non-JSON responses) so subsequent 304 hits can hand the same shape back to\n * the caller without re-parsing.\n */\n data: unknown;\n /**\n * Selected response headers worth replaying on a 304 hit (e.g.\n * `NGSILD-Results-Count`, `Fiware-Total-Count`, `Link`). Stored as a plain\n * record because `Headers` instances are not serialisable.\n */\n headers: Record<string, string>;\n /** Cache insertion timestamp (epoch ms). Used for LRU only. */\n cachedAt: number;\n}\n\n/**\n * Minimal LRU cache for SDK responses.\n *\n * The implementation relies on `Map`'s insertion-order property (ES2015+):\n * deleting and re-inserting an entry moves it to the most-recent position, so\n * eviction simply removes the head of the iterator when capacity is exceeded.\n */\nexport class SdkCache {\n private readonly _store = new Map<string, CacheEntry>();\n private readonly _maxEntries: number;\n\n /**\n * Optional persistent backing store (IndexedDB) so cached validators survive\n * a page reload (#1262). `null` in non-browser runtimes \u2014 the cache then\n * behaves as a pure in-memory LRU.\n */\n private _persistence: CachePersistence | null = null;\n /**\n * Auth-scope namespace for persisted entries. The auth layer sets this to a\n * tenant + authenticated-principal identifier so that entries written by one\n * principal are never read back by another sharing the same origin/tenant\n * (different tabs / SDK instances each scope to their own principal).\n */\n private _scope = '';\n /**\n * Whether write-through persistence is currently active. Driven by the auth\n * layer: enabled only for remembered sessions, so persisted entries belong to\n * exactly one principal and are dropped on every credential change.\n */\n private _persistEnabled = false;\n /**\n * Serialises persistence writes so they complete in call order (#1262\n * review). `set`/`delete`/`clearPersisted` are otherwise independent\n * fire-and-forget ops; if a late `put()` resolved after a `remove()` /\n * `clearScope()`, an evicted/deleted/logged-out entry could reappear in\n * IndexedDB. Chaining them keeps the persisted store consistent with the\n * in-memory state and with the auth-boundary flush.\n */\n private _persistQueue: Promise<void> = Promise.resolve();\n\n constructor(maxEntries: number = SDK_CACHE_MAX_ENTRIES_DEFAULT) {\n if (!Number.isInteger(maxEntries) || maxEntries <= 0) {\n throw new Error(`SdkCache maxEntries must be a positive integer, got ${String(maxEntries)}`);\n }\n this._maxEntries = maxEntries;\n }\n\n /** Wire a persistence backend (called once at construction when available). */\n attachPersistence(persistence: CachePersistence): void {\n this._persistence = persistence;\n }\n\n /** Whether a persistence backend is attached. */\n hasPersistence(): boolean {\n return this._persistence !== null;\n }\n\n /** Set the auth-scope namespace used for persisted reads/writes. */\n setScope(scope: string): void {\n this._scope = scope;\n }\n\n /** Enable or disable write-through persistence (driven by `rememberSession`). */\n setPersistEnabled(enabled: boolean): void {\n this._persistEnabled = enabled && this._persistence !== null;\n }\n\n /** True when set/delete should propagate to the persistent store. */\n private get _persisting(): boolean {\n return this._persistEnabled && this._persistence !== null;\n }\n\n /**\n * Append a persistence operation to the serialised queue. The backend and\n * scope are captured at enqueue time so a later scope change cannot retarget\n * an already-queued op. A failing op is swallowed (persistence is\n * best-effort) and does not stall the chain. Returns the tail so callers that\n * need ordering (`clearPersisted`) can await it.\n */\n private _enqueuePersist(\n op: (persistence: CachePersistence, scope: string) => Promise<void>,\n ): Promise<void> {\n const persistence = this._persistence;\n if (!persistence) return this._persistQueue;\n const scope = this._scope;\n this._persistQueue = this._persistQueue\n .catch(() => undefined)\n .then(() => op(persistence, scope))\n .catch(() => undefined);\n return this._persistQueue;\n }\n\n /**\n * Load persisted entries for the current scope into memory, oldest first so\n * the in-memory LRU recency order is preserved. Best-effort and idempotent \u2014\n * a no-op when no backend is attached. Called after a remembered session is\n * restored so the very first request after a reload can send `If-None-Match`.\n */\n async hydrate(): Promise<void> {\n if (!this._persistence) return;\n const entries = await this._persistence.loadScope(this._scope, this._maxEntries);\n for (const { key, entry } of entries) {\n if (this._store.has(key)) this._store.delete(key);\n this._store.set(key, entry);\n }\n // Defensive trim \u2014 loadScope already caps to maxEntries, but a pre-populated\n // store could push us over.\n while (this._store.size > this._maxEntries) {\n const oldest = this._store.keys().next().value;\n /* istanbul ignore if -- size > maxEntries guarantees a first key */\n if (oldest === undefined) break;\n this._store.delete(oldest);\n }\n }\n\n /** Drop every persisted entry for the current scope (auth change / flush). */\n async clearPersisted(): Promise<void> {\n if (!this._persistence) return;\n await this._enqueuePersist((persistence, scope) => persistence.clearScope(scope));\n }\n\n /** Build a stable cache key. Method is upper-cased so casing differences do not produce duplicate entries. */\n static keyFor(method: string, path: string): string {\n return `${method.toUpperCase()}:${path}`;\n }\n\n /** Returns the entry and bumps it to most-recent position, or `undefined`. */\n get(key: string): CacheEntry | undefined {\n const entry = this._store.get(key);\n if (!entry) return undefined;\n // Bump LRU position\n this._store.delete(key);\n this._store.set(key, entry);\n return entry;\n }\n\n /** Insert or replace an entry. Evicts the LRU entry when over capacity. */\n set(key: string, entry: CacheEntry): void {\n if (this._store.has(key)) {\n this._store.delete(key);\n }\n this._store.set(key, entry);\n const evicted: string[] = [];\n while (this._store.size > this._maxEntries) {\n const oldest = this._store.keys().next().value;\n /* istanbul ignore if -- size > maxEntries guarantees a first key */\n if (oldest === undefined) break;\n this._store.delete(oldest);\n evicted.push(oldest);\n }\n // Mirror the mutation into the persistent store (best-effort, fire-and-forget)\n // so the entry survives a reload. Evicted keys are dropped to keep the\n // persisted set bounded by the same LRU capacity (#1262).\n if (this._persisting) {\n void this._enqueuePersist(async (persistence, scope) => {\n await persistence.put(scope, key, entry);\n if (evicted.length > 0) await persistence.remove(scope, evicted);\n });\n }\n }\n\n /** Delete a specific entry. Returns whether anything was removed. */\n delete(key: string): boolean {\n const removed = this._store.delete(key);\n if (removed && this._persisting) {\n void this._enqueuePersist((persistence, scope) => persistence.remove(scope, [key]));\n }\n return removed;\n }\n\n /**\n * Delete every entry whose key matches a predicate. Returns the array of\n * removed keys so callers can emit `cacheInvalidated` events.\n */\n deleteWhere(predicate: (key: string, entry: CacheEntry) => boolean): string[] {\n const removed: string[] = [];\n for (const [key, entry] of this._store) {\n if (predicate(key, entry)) {\n this._store.delete(key);\n removed.push(key);\n }\n }\n return removed;\n }\n\n /** Drop everything. */\n clear(): void {\n this._store.clear();\n }\n\n /** Current entry count. Useful for tests / metrics. */\n size(): number {\n return this._store.size;\n }\n}\n\n/**\n * Pull a small set of response headers worth retaining on a 304 replay.\n * Stripping unknown headers keeps memory bounded and avoids leaking transport\n * details (e.g. `set-cookie`) into the SDK consumer.\n */\nconst HEADERS_TO_PERSIST = [\n 'content-type',\n 'etag',\n 'last-modified',\n 'cache-control',\n 'vary',\n 'link',\n 'ngsild-results-count',\n 'fiware-total-count',\n 'x-total-count',\n 'ngsild-next',\n 'fiware-next-token',\n] as const;\n\nexport function snapshotHeaders(headers: Headers): Record<string, string> {\n const out: Record<string, string> = {};\n for (const name of HEADERS_TO_PERSIST) {\n const value = headers.get(name);\n if (value !== null) out[name] = value;\n }\n return out;\n}\n\n/** Whether a request method is cacheable per RFC 7234 \u00A74.2.1 (safe + idempotent + no body changes). */\nexport function isCacheableMethod(method: string): boolean {\n const upper = method.toUpperCase();\n return upper === 'GET' || upper === 'HEAD';\n}\n", "/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\nimport {\n clearDPoPSession,\n createDPoPProof,\n dpopSupported,\n generateDPoPKeyPair,\n loadDPoPSession,\n saveDPoPSession,\n} from './dpop';\nimport { solvePoW } from './pow';\nimport { DEFAULT_TOKEN_TTL_SEC, NGSI_LD_CORE_CONTEXT } from './constants';\nimport {\n AuthenticationError,\n PasswordResetRequiredError,\n TemporaryPasswordExpiredError,\n withRequest,\n} from './errors';\nimport { SdkCache, snapshotHeaders, isCacheableMethod } from './cache';\nimport type {\n CacheEvent,\n CredentialsOptions,\n DPoPKeyPair,\n LoginOptions,\n LoginResponse,\n RefreshedCredentials,\n} from './types';\n\ntype CacheEventName = 'cacheHit' | 'cacheMiss' | 'cacheInvalidated';\n\n/** Internal authentication state for the GeonicDB SDK. */\nexport class AuthManager {\n private _baseUrl: string;\n\n /** The base URL requests are sent against (trailing slash already stripped). */\n get baseUrl(): string {\n return this._baseUrl;\n }\n\n private _apiKey: string;\n private _tenant: string;\n\n /** Fiware-Service / NGSILD-Tenant value configured at construction time. */\n get tenant(): string {\n return this._tenant;\n }\n\n _debug: boolean;\n /**\n * #1105: anonymous \u30E2\u30FC\u30C9\u3002true \u306E\u5834\u5408\u3001\u30C8\u30FC\u30AF\u30F3\u3092\u53D6\u5F97\u305B\u305A Authorization\n * \u30D8\u30C3\u30C0\u7121\u3057\u3067\u9001\u4FE1\u3059\u308B\u3002\u30B5\u30FC\u30D0\u30FC\u5074 (`optionalAuth`) \u3067 role='anonymous' \u3068\u3057\u3066\n * \u901A\u308A\u3001XACML \u304C\u8A8D\u53EF\u5224\u5B9A\u3059\u308B\u3002\n *\n * `_initialAnonymous` \u306F\u30B3\u30F3\u30B9\u30C8\u30E9\u30AF\u30BF\u3067\u6307\u5B9A\u3055\u308C\u305F\u5E0C\u671B\u30E2\u30FC\u30C9 (immutable)\u3002\n * `_anonymous` \u306F\u73FE\u5728\u306E\u30E2\u30FC\u30C9\u72B6\u614B\u3067\u3001`login()` / `setCredentials()` \u3067 false\n * \u306B\u964D\u308A\u3001`logout()` \u3067 `_initialAnonymous` \u306E\u5024\u306B\u623B\u308B\u3002\n *\n * \u30C8\u30FC\u30AF\u30F3\u6709\u7121 (`_token === null`) \u304B\u3089\u306F\u63A8\u8AD6\u3057\u306A\u3044\u3002`_token` \u306F refresh\n * \u5931\u6557\u6642\u306B null \u3078\u843D\u3061\u308B\u304C\u3001\u30E6\u30FC\u30B6\u304C\u660E\u793A\u7684\u306B `logout()` \u3057\u3066\u3044\u306A\u3044\u4EE5\u4E0A\u3001\n * \u6B21\u306E\u30EA\u30AF\u30A8\u30B9\u30C8\u3092\u52DD\u624B\u306B Authorization \u30D8\u30C3\u30C0\u7121\u3057\u3067\u9001\u3063\u3066\u306F\u306A\u3089\u306A\u3044\n * (#1113 review)\u3002\n */\n private readonly _initialAnonymous: boolean;\n private _anonymous: boolean;\n\n _token: string | null = null;\n _tokenExpiry = 0;\n _tokenType: 'Bearer' | 'DPoP' = 'Bearer';\n _refreshToken: string | null = null;\n _dpopKeyPair: DPoPKeyPair | null = null;\n _dpopReady: Promise<void> | null = null;\n _dpopNonce: string | null = null;\n /**\n * When true, every DPoP token mutation (login bind, refresh) is mirrored to\n * IndexedDB so the session survives a page reload (#1230). Toggled by\n * `login(email, password, { rememberSession: true })` and\n * `restoreSession()` (#1244).\n */\n _rememberSession = false;\n /** Email captured at login, persisted for UI display. */\n _email: string | null = null;\n private _tokenPromise: Promise<string> | null = null;\n\n /** Callback to emit tokenRefresh events. Set by GeonicDB class. */\n onTokenRefresh: ((creds: RefreshedCredentials) => void) | null = null;\n\n /**\n * SDK-level cache (#991 Phase A). When set, cacheable GET requests are\n * served via ETag/304 with automatic If-None-Match negotiation, and\n * concurrent requests to the same path are deduplicated.\n */\n private _cache: SdkCache | null = null;\n\n /** Emitter for cacheHit / cacheMiss / cacheInvalidated events. */\n private _emitCacheEvent: ((name: CacheEventName, payload: CacheEvent) => void) | null = null;\n\n /** In-flight request map keyed by `${METHOD}:${path}` for request deduplication. */\n private readonly _inFlight = new Map<string, Promise<Response>>();\n\n constructor(\n baseUrl: string,\n apiKey: string,\n tenant: string,\n debug = false,\n anonymous = false,\n ) {\n if (anonymous && apiKey) {\n throw new Error(\n 'GeonicDB SDK: `anonymous: true` cannot be combined with `apiKey`. ' +\n 'Anonymous mode skips token acquisition entirely.',\n );\n }\n this._baseUrl = baseUrl;\n this._apiKey = apiKey;\n this._tenant = tenant;\n this._debug = debug;\n this._initialAnonymous = anonymous;\n this._anonymous = anonymous;\n\n // anonymous \u30E2\u30FC\u30C9\u3067\u306F DPoP \u9375\u30DA\u30A2\u3092\u751F\u6210\u3057\u306A\u3044\uFF08\u4F7F\u308F\u308C\u306A\u3044\uFF09\u3002\n /* istanbul ignore next: dpopSupported is false in jsdom (no\n crypto.subtle.generateKey) \u2014 covered by E2E. */\n if (!anonymous && dpopSupported) {\n this._dpopReady = generateDPoPKeyPair()\n .then((kp) => {\n // #1230 review: restoreSession() \u304C\u30B3\u30F3\u30B9\u30C8\u30E9\u30AF\u30BF\u5B8C\u4E86\u524D\u306B\u547C\u3070\u308C\u3066\n // \u9375\u3092 IndexedDB \u304B\u3089\u5FA9\u5143\u3057\u305F\u5834\u5408\u3001\u3053\u3053\u3067\u9045\u5EF6\u3057\u3066 resolve \u3059\u308B\u9375\u3067\n // \u4E0A\u66F8\u304D\u3059\u308B\u3068\u5FA9\u5143\u3057\u305F\u30C8\u30FC\u30AF\u30F3\u3068\u306E\u7D10\u4ED8\u3051\u304C\u58CA\u308C\u308B\u3002\u65E2\u306B\u9375\u304C\u5165\u3063\u3066\n // \u3044\u308C\u3070\u89E6\u3089\u306A\u3044\u3002\n if (!this._dpopKeyPair) this._dpopKeyPair = kp;\n })\n .catch(() => {\n if (!this._dpopKeyPair) this._dpopKeyPair = null;\n });\n }\n }\n\n /**\n * True when running unauthenticated.\n *\n * Returns the explicit mode flag, NOT derived from `_token === null`. After\n * a refresh failure clears `_token`, the SDK must NOT silently switch to\n * anonymous; the caller must explicitly `logout()` to revert (#1113 review).\n */\n isAnonymous(): boolean {\n return this._anonymous;\n }\n\n private _log(...args: unknown[]): void {\n if (this._debug) console.log('[GeonicDB]', ...args);\n }\n\n /** Wire an SdkCache instance (called by GeonicDB constructor when caching is enabled). */\n setCache(cache: SdkCache | null): void {\n this._cache = cache;\n // #1262: namespace persisted cache entries by tenant + principal so a reload\n // only rehydrates entries belonging to the current authenticated subject.\n cache?.setScope(this._cacheScope());\n }\n\n /**\n * #1262: persistence scope for the SDK cache \u2014 tenant + authenticated\n * principal (email when available). Keying on the principal, not just the\n * tenant, prevents a different subject sharing the same origin/tenant (another\n * tab, another SDK instance, a later login) from reading entries written by a\n * previous principal. The principal segment is empty for sessions without a\n * known email; such sessions never persist (persistence is gated on a\n * remembered DPoP session that always carries an email), so the empty segment\n * is not reachable as a write scope in practice.\n */\n private _cacheScope(): string {\n return `${this._tenant}\\u0001${this._email ?? ''}`;\n }\n\n /**\n * #1262: enable write-through persistence for the SDK cache so it survives a\n * reload. Always re-scopes to the current tenant + principal first.\n *\n * `clearFirst` drops any persisted entries left by a previous session of the\n * same principal \u2014 used on a fresh `login()`. It is `false` on\n * `restoreSession()`, where the persisted entries belong to the very session\n * being restored and are about to be rehydrated.\n */\n private async _enablePersistentCache(clearFirst: boolean): Promise<void> {\n if (!this._cache) return;\n this._cache.setScope(this._cacheScope());\n if (clearFirst) await this._cache.clearPersisted();\n this._cache.setPersistEnabled(true);\n }\n\n /**\n * #1262: disable write-through persistence and drop the persisted cache for\n * the current principal. Called on credential changes that do not persist a\n * session (`setCredentials()`, non-remembered `login()`, `logout()`),\n * mirroring the in-memory invalidation so a stale body can never outlive its\n * principal. Callers that null out `_email` must invoke this **before** doing\n * so, otherwise the scope no longer resolves to the principal being dropped.\n */\n private _disablePersistentCache(): void {\n if (!this._cache) return;\n this._cache.setScope(this._cacheScope());\n this._cache.setPersistEnabled(false);\n void this._cache.clearPersisted();\n }\n\n /** Provide the cache event emitter (forwarded to the GeonicDB EventEmitter). */\n setCacheEventEmitter(\n emitter: ((name: CacheEventName, payload: CacheEvent) => void) | null,\n ): void {\n this._emitCacheEvent = emitter;\n }\n\n /** Expose the cache for invalidation (e.g. WebSocket entity events). */\n getCache(): SdkCache | null {\n return this._cache;\n }\n\n /** Emit a cache event if a listener is wired. */\n emitCacheEvent(name: CacheEventName, payload: CacheEvent): void {\n this._emitCacheEvent?.(name, payload);\n }\n\n /**\n * Login with email and password.\n *\n * #1244: DPoP (RFC 9449) is transparent and always-on \u2014 there is no `dpop`\n * option to set. After the Bearer login succeeds, the SDK automatically\n * exchanges it for a DPoP sender-constrained session so a token leaked from\n * storage (e.g. localStorage XSS) cannot be replayed from another origin.\n *\n * When the runtime lacks Web Crypto (`crypto.subtle.generateKey`, e.g. a\n * non-HTTPS context), DPoP cannot be used and the SDK silently keeps the\n * Bearer session \u2014 login still succeeds. Callers never deal with DPoP\n * directly; pass `{ rememberSession: true }` only to keep the user signed in\n * across reloads.\n */\n async login(\n email: string,\n password: string,\n options?: LoginOptions,\n ): Promise<LoginResponse> {\n this._log('login', email);\n\n const wantsRemember = options?.rememberSession === true;\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (this._tenant) headers['NGSILD-Tenant'] = this._tenant;\n\n const loginBody: Record<string, string> = { email, password };\n // #1532: \u4E00\u6642\u30D1\u30B9\u30EF\u30FC\u30C9\u3067\u306E\u521D\u56DE\u30ED\u30B0\u30A4\u30F3\u6642\u306B\u65B0\u30D1\u30B9\u30EF\u30FC\u30C9\u3092\u540C\u68B1\u3059\u308B (\u5358\u767A\u578B)\u3002\n if (options?.newPassword !== undefined) loginBody.newPassword = options.newPassword;\n\n const res = await fetch(this._baseUrl + '/auth/login', {\n method: 'POST',\n headers,\n body: JSON.stringify(loginBody),\n });\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n const message = e.detail || e.description || 'Login failed: ' + res.status;\n // #1532: \u5F37\u5236\u30D1\u30B9\u30EF\u30FC\u30C9\u5909\u66F4\u30D5\u30ED\u30FC\u3092\u6A5F\u68B0\u53EF\u8AAD\u30AD\u30FC\u3067\u5224\u5225\u3057\u3001\u5C02\u7528\u30A8\u30E9\u30FC\u3092\u6295\u3052\u308B\u3002\n // \u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u306F PasswordResetRequiredError \u3092\u6355\u6349\u3057\u3066\u65B0\u30D1\u30B9\u30EF\u30FC\u30C9\u3092\u96C6\u3081\u3001\n // login(email, tempPassword, { newPassword }) \u3092\u547C\u3073\u76F4\u3059\u3002\n const code = e.error || e.type || e.title || '';\n if (res.status === 409 && code.includes('PasswordResetRequired')) {\n throw withRequest(new PasswordResetRequiredError(message), {\n method: 'POST',\n path: '/auth/login',\n });\n }\n if (res.status === 403 && code.includes('TemporaryPasswordExpired')) {\n throw withRequest(new TemporaryPasswordExpiredError(message), {\n method: 'POST',\n path: '/auth/login',\n });\n }\n throw withRequest(new AuthenticationError(message, res.status), {\n method: 'POST',\n path: '/auth/login',\n });\n }\n const data = (await res.json()) as LoginResponse;\n // #1113 review: \u8A8D\u8A3C\u6210\u529F\u3067\u660E\u793A\u7684\u306B anonymous \u30E2\u30FC\u30C9\u3092\u629C\u3051\u308B\u3002\n this._anonymous = false;\n this._token = data.accessToken;\n this._tokenExpiry = Date.now() + (data.expiresIn - 60) * 1000;\n this._tokenType = 'Bearer';\n this._refreshToken = data.refreshToken;\n this._email = email;\n // #1230 review: \u524D\u56DE login \u306E _rememberSession \u304C\u6B8B\u3089\u306A\u3044\u3088\u3046\u3001\u6BCE\u56DE\u660E\u793A\u7684\u306B\n // \u4E0A\u66F8\u304D\u3059\u308B (false\u2192true / true\u2192false \u3069\u3061\u3089\u306E\u9077\u79FB\u306B\u3082\u8FFD\u968F)\u3002\n this._rememberSession = wantsRemember;\n // #1043 review (Critical): drop cache + dedup state from any previous identity.\n this._invalidateAuthScopedState();\n // #1262: pause persistent cache writes during the credential transition.\n // The terminal branch below either re-enables it (remembered session) or\n // disables + flushes it (everything else).\n this._cache?.setPersistEnabled(false);\n\n // #1244 review: rememberSession \u3092\u660E\u793A\u7684\u306B\u30AA\u30D5 (\u7701\u7565\u542B\u3080) \u3067\u30ED\u30B0\u30A4\u30F3\u3057\u305F\u5834\u5408\u3001\n // \u904E\u53BB\u306B\u6C38\u7D9A\u5316\u3055\u308C\u305F\u540C tenant \u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u3092\u7834\u68C4\u3059\u308B\u3002\u6B8B\u3059\u3068 restoreSession()\n // \u3067\u53E4\u3044\u30BB\u30C3\u30B7\u30E7\u30F3\u304C\u5FA9\u6D3B\u3057\u3001\u5171\u6709\u7AEF\u672B\u3067\u610F\u56F3\u3057\u306A\u3044\u518D\u30ED\u30B0\u30A4\u30F3\u3092\u62DB\u304F\u3002\n if (!wantsRemember && this._tenant) {\n void clearDPoPSession(this._tenant);\n }\n\n // #1244: DPoP \u3092\u5E38\u6642\u30AA\u30F3\u3067\u900F\u904E\u7684\u306B\u9069\u7528\u3059\u308B\u3002\u9375\u751F\u6210\u304C\u5B8C\u4E86\u3057\u3066\u3044\u3066\u9375\u30DA\u30A2\u304C\n // \u3042\u308C\u3070\u5FC5\u305A DPoP-bound \u5316\u3059\u308B\u3002Web Crypto \u975E\u5BFE\u5FDC\u74B0\u5883 (crypto.subtle \u7121\u3057)\n // \u3067\u306F\u9375\u30DA\u30A2\u304C\u751F\u6210\u3055\u308C\u306A\u3044\u305F\u3081\u3001\u9ED9\u3063\u3066 Bearer \u306E\u307E\u307E\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u3059\u308B\n // \u2014 login \u81EA\u4F53\u306F\u6210\u529F\u3059\u308B\u3002\u958B\u767A\u8005\u306F DPoP \u3092\u610F\u8B58\u3057\u306A\u3044\u3002\n if (this._dpopReady) await this._dpopReady;\n if (this._dpopKeyPair) {\n try {\n const bound = await this._bindDpopSession();\n // \u9375\u3092 IndexedDB \u306B\u6C38\u7D9A\u5316\u3057\u3001\u30EA\u30ED\u30FC\u30C9\u5F8C\u3082 restoreSession() \u3067\u540C\u3058 DPoP\n // \u30BB\u30C3\u30B7\u30E7\u30F3\u3092\u7D99\u7D9A\u3067\u304D\u308B\u3088\u3046\u306B\u3059\u308B\u3002\u6C38\u7D9A\u5316\u5931\u6557 (IndexedDB \u5229\u7528\u4E0D\u53EF\u306A\u3069)\n // \u306F\u30ED\u30B0\u30A4\u30F3\u81EA\u4F53\u3092\u59A8\u3052\u306A\u3044 \u2014 \u30EA\u30ED\u30FC\u30C9\u6642\u306B\u518D\u30ED\u30B0\u30A4\u30F3\u304C\u5FC5\u8981\u306B\u306A\u308B\u3060\u3051\u3002\n if (this._rememberSession) {\n await this._persistDpopSession();\n // #1262: persist the ETag cache for this remembered session so the\n // 304 path survives a reload. Drop any entries left by a previous\n // principal on this tenant before enabling write-through.\n await this._enablePersistentCache(true);\n } else {\n // #1262: non-remembered session keeps the cache in-memory only; drop\n // any persisted entries so they cannot leak into a later session.\n this._disablePersistentCache();\n }\n return bound;\n } catch (err) {\n // #1244 review: \u9375\u30DA\u30A2\u304C\u3042\u3063\u3066 DPoP \u30D0\u30A4\u30F3\u30C9\u3092\u300C\u8A66\u307F\u3066\u5931\u6557\u300D\u3057\u305F\u5834\u5408\u306F\n // fail-CLOSED \u306B\u3059\u308B\u3002Bearer \u8CC7\u683C\u60C5\u5831\u3092 state \u306B\u6B8B\u3059\u3068\u5F8C\u7D9A\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\n // Bearer \u3067\u6210\u7ACB\u3059\u308B\u30C0\u30A6\u30F3\u30B0\u30EC\u30FC\u30C9\u306B\u306A\u308B\u305F\u3081\u3001\u4ECA\u56DE\u306E login \u3067\u8A2D\u5B9A\u3057\u305F\n // \u8A8D\u8A3C state \u3092\u7834\u68C4\u3057\u3066\u304B\u3089\u518D throw \u3059\u308B\u3002crypto \u975E\u5BFE\u5FDC\u3067\u300C\u305D\u3082\u305D\u3082\n // \u8A66\u307F\u306A\u3044\u300D\u30B0\u30EC\u30FC\u30B9\u30D5\u30EB\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF (\u4E0B\u306E return data) \u3068\u306F\u533A\u5225\u3059\u308B\u3002\n this._token = null;\n this._tokenExpiry = 0;\n this._tokenType = 'Bearer';\n this._refreshToken = null;\n this._tokenPromise = null;\n this._invalidateAuthScopedState();\n // #1262: drop the persisted cache while `_email` still resolves to the\n // failed-login principal, then clear the remembered-session state.\n this._disablePersistentCache();\n this._rememberSession = false;\n this._email = null;\n throw err;\n }\n }\n // crypto.subtle \u4E0D\u5728 \u2192 DPoP \u4E0D\u53EF\u3002Bearer \u30BB\u30C3\u30B7\u30E7\u30F3\u306F sender-constrained \u306B\n // \u3067\u304D\u306A\u3044\u305F\u3081\u6C38\u7D9A\u5316\u3082\u3057\u306A\u3044 (rememberSession \u306F no-op)\u3002\n // #1244 review (#1262): rememberSession \u3092\u660E\u793A\u8981\u6C42\u3057\u3066\u3082 DPoP \u9375\u304C\u7121\u3044\u305F\u3081\n // \u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u6C38\u7D9A\u5316\u3067\u304D\u306A\u3044\u3002\u53E4\u3044\u6C38\u7D9A DPoP \u30BB\u30C3\u30B7\u30E7\u30F3\u304C\u6B8B\u3063\u3066\u3044\u308B\u3068\u5F8C\u7D9A\u306E\n // restoreSession() \u3067\u5225 principal \u304C\u5FA9\u6D3B\u3057\u5F97\u308B\u305F\u3081\u3001\u7121\u6761\u4EF6\u3067\u7834\u68C4\u3059\u308B\u3002\n this._rememberSession = false;\n if (this._tenant) {\n void clearDPoPSession(this._tenant);\n }\n // #1262: \u540C\u69D8\u306B\u30AD\u30E3\u30C3\u30B7\u30E5\u6C38\u7D9A\u5316\u3082\u7121\u52B9\u5316\u3057\u3001\u904E\u53BB\u306E\u6C38\u7D9A\u30A8\u30F3\u30C8\u30EA\u3092\u7834\u68C4\u3059\u308B\u3002\n this._disablePersistentCache();\n return data;\n }\n\n /**\n * Exchange the current Bearer session for a DPoP-bound session via\n * `POST /auth/dpop-bind` (RFC 9449). The server re-issues access and\n * refresh tokens with `cnf.jkt` matching this SDK's DPoP key pair.\n *\n * Assumes `_token` / `_refreshToken` are already populated by a prior\n * `login()` call (callers should not invoke this directly).\n *\n * @internal\n */\n private async _bindDpopSession(): Promise<LoginResponse> {\n if (!this._token) {\n throw new AuthenticationError(\n 'Cannot bind DPoP session: no Bearer token. login() must succeed first.',\n );\n }\n if (this._dpopReady) await this._dpopReady;\n if (!this._dpopKeyPair) {\n throw new AuthenticationError(\n 'DPoP binding requires a generated DPoP key pair, but key generation ' +\n 'was unavailable or failed. DPoP requires Web Crypto subtle.generateKey.',\n );\n }\n\n const bindUrl = this._baseUrl + '/auth/dpop-bind';\n const accessToken = this._token;\n const data = await this._doDpopBind(bindUrl, accessToken, this._dpopNonce, 0);\n\n // \u6210\u529F: \u30C8\u30FC\u30AF\u30F3\u3092 DPoP-bound \u7248\u306B\u5DEE\u3057\u66FF\u3048\u308B\u3002tokenType \u30B5\u30FC\u30D0\u5FDC\u7B54\n // (`'DPoP'`) \u3092\u4FE1\u983C\u3057\u3064\u3064\u3001\u30B5\u30FC\u30D0\u304C\u53E4\u304F\u3066\u672A\u8A2D\u5B9A\u3060\u3063\u305F\u5834\u5408\u306B\u3082\n // \u52D5\u4F5C\u3059\u308B\u3088\u3046\u660E\u793A\u7684\u306B 'DPoP' \u3067\u4E0A\u66F8\u304D\u3059\u308B\u3002\n this._token = data.accessToken;\n this._tokenExpiry = Date.now() + (data.expiresIn - 60) * 1000;\n this._tokenType = 'DPoP';\n this._refreshToken = data.refreshToken;\n // #1043 review: identity \u304C\u540C\u4E00\u3067\u3082 tokenType \u304C\u5909\u308F\u308B\u305F\u3081\u3001Bearer \u6642\u4EE3\u306E\n // \u30AD\u30E3\u30C3\u30B7\u30E5\u30FBdedup \u306F\u7834\u68C4\u3059\u308B\u3002\n this._invalidateAuthScopedState();\n return { ...data, tokenType: 'DPoP' };\n }\n\n private async _doDpopBind(\n bindUrl: string,\n accessToken: string,\n dpopNonce: string | null,\n retryCount: number,\n ): Promise<LoginResponse> {\n const dpopProof = await createDPoPProof(\n this._dpopKeyPair,\n 'POST',\n bindUrl,\n accessToken,\n dpopNonce,\n );\n if (!dpopProof) {\n // _dpopKeyPair is guaranteed non-null by the caller, but TS narrowing\n // does not propagate through createDPoPProof's signature.\n throw new AuthenticationError('DPoP proof generation failed');\n }\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Authorization: 'Bearer ' + accessToken,\n DPoP: dpopProof,\n };\n if (this._tenant) headers['NGSILD-Tenant'] = this._tenant;\n\n const res = await fetch(bindUrl, { method: 'POST', headers });\n\n const serverNonce = res.headers.get('DPoP-Nonce');\n if (serverNonce) this._dpopNonce = serverNonce;\n\n // RFC 9449 \u00A78: nonce \u5FC5\u9808 \u2192 DPoP-Nonce \u30D8\u30C3\u30C0\u30FC\u3092\u53D6\u3063\u3066\u518D\u8A66\u884C\n if (\n res.status === 401 &&\n serverNonce &&\n serverNonce !== dpopNonce &&\n retryCount < AuthManager.MAX_DPOP_RETRIES\n ) {\n return this._doDpopBind(bindUrl, accessToken, serverNonce, retryCount + 1);\n }\n\n if (!res.ok) {\n const e = await res.json().catch(() => ({})) as Record<string, string>;\n throw withRequest(\n new AuthenticationError(\n e.detail ||\n e.description ||\n 'DPoP bind failed: ' + res.status,\n res.status,\n ),\n { method: 'POST', path: '/auth/dpop-bind' },\n );\n }\n\n return (await res.json()) as LoginResponse;\n }\n\n /**\n * Set credentials externally (e.g. from a login API response).\n * When tokenType is 'Bearer' and refreshToken is provided, all subsequent\n * API calls and connect() bypass DPoP/PoW entirely.\n */\n setCredentials(opts: CredentialsOptions): void {\n if (!opts || !opts.token) throw new Error('token is required');\n // #1113 review: \u533F\u540D\u69CB\u7BC9\u30A4\u30F3\u30B9\u30BF\u30F3\u30B9\u306F DPoP \u9375\u30DA\u30A2\u751F\u6210\u81EA\u4F53\u3092 skip \u3059\u308B\u306E\u3067\n // `tokenType: 'DPoP'` \u3092\u53D7\u3051\u3066\u3082 proof \u7121\u3057 Bearer \u9001\u4FE1\u306B\u6210\u308A\u4E0B\u304C\u308B\u3002\n // \u5B89\u5168\u5074\u3067\u660E\u793A\u62D2\u5426\u3059\u308B\u3002\n //\n // \u6CE8: `_dpopKeyPair` \u306E\u6709\u7121\u3067\u5224\u5B9A\u3059\u308B\u3068\u3001\u975E\u533F\u540D\u30A4\u30F3\u30B9\u30BF\u30F3\u30B9\u3067\u3082\u69CB\u7BC9\u76F4\u5F8C\u306E\n // \u9375\u751F\u6210\u5B8C\u4E86\u524D (`_dpopReady` \u672A\u89E3\u6C7A) \u306B\u547C\u3070\u308C\u305F\u5834\u5408\u306B\u8AA4\u3063\u3066\u5F3E\u3044\u3066\u3057\u307E\u3046\u3002\n // \u3053\u3053\u306F\u30B3\u30F3\u30B9\u30C8\u30E9\u30AF\u30BF\u3067\u306E\u5E0C\u671B\u30E2\u30FC\u30C9 `_initialAnonymous` \u3067\u5224\u5B9A\u3059\u308B\n // (#1113 review-2)\u3002\n if (this._initialAnonymous && opts.tokenType === 'DPoP') {\n throw new Error(\n 'DPoP credentials cannot be set on an SDK instance constructed with ' +\n '`anonymous: true` (no DPoP key pair is generated for anonymous instances). ' +\n 'Use Bearer credentials, or construct the SDK in non-anonymous mode.',\n );\n }\n // #1113 review: \u8A8D\u8A3C\u60C5\u5831\u6CE8\u5165\u3067\u660E\u793A\u7684\u306B anonymous \u30E2\u30FC\u30C9\u3092\u629C\u3051\u308B\u3002\n this._anonymous = false;\n this._token = opts.token;\n this._tokenType = opts.tokenType || 'Bearer';\n this._tokenExpiry = opts.expiresIn != null\n ? Date.now() + (opts.expiresIn - 60) * 1000\n : Date.now() + DEFAULT_TOKEN_TTL_SEC * 1000;\n this._refreshToken = opts.refreshToken || null;\n this._tokenPromise = null;\n // #1043 review (Critical): cache + in-flight dedup is keyed only by\n // METHOD:path, so credentials changes must invalidate them to prevent\n // cross-user response reuse.\n this._invalidateAuthScopedState();\n // #1262: externally-injected credentials are not a persisted DPoP session,\n // so keep the cache in-memory only and drop the previous principal's\n // persisted entries \u2014 done while `_email` still resolves to that principal.\n this._disablePersistentCache();\n // #1262 review: also reset the remembered-session state. Otherwise a stale\n // `_rememberSession === true` / `_email` from a prior `login()` would make\n // the next token refresh persist these injected credentials into a DPoP\n // session (see `_refreshBearerToken`), and `restoreSession()` could later\n // resurrect a principal the caller never intended to persist.\n this._rememberSession = false;\n this._email = null;\n if (this._tenant) {\n void clearDPoPSession(this._tenant);\n }\n }\n\n /** Clear all credentials. */\n logout(): void {\n this._token = null;\n this._tokenExpiry = 0;\n this._refreshToken = null;\n this._tokenPromise = null;\n // #1262: flush the persisted ETag cache while `_email` still resolves to the\n // principal being logged out (the scope is tenant + principal), then null\n // it. Done before `_invalidateAuthScopedState()` ordering does not matter\n // for the in-memory side, but the persisted scope must be computed first.\n this._disablePersistentCache();\n this._email = null;\n // #1230 / #1244 review: \u6C38\u7D9A\u5316\u3055\u308C\u305F\u30BB\u30C3\u30B7\u30E7\u30F3\u3092 **\u7121\u6761\u4EF6\u3067** \u7834\u68C4\u3059\u308B\u3002\n // `_rememberSession` \u6761\u4EF6\u3092\u4ED8\u3051\u308B\u3068\u3001\u5225\u306E login \u3067 rememberSession \u3092\n // \u30AA\u30D5\u306B\u3057\u305F\u5F8C (= \u30D5\u30E9\u30B0\u304C false) \u3067\u3082\u904E\u53BB\u306E\u6C38\u7D9A\u30A8\u30F3\u30C8\u30EA\u304C\u6B8B\u308A\u3001\n // restoreSession() \u3067\u5FA9\u6D3B\u3059\u308B\u4F59\u5730\u304C\u751F\u307E\u308C\u308B\u3002clearDPoPSession \u306F\u51AA\u7B49\u3067\n // \u30A8\u30F3\u30C8\u30EA\u7121\u3057\u3067\u3082 no-op \u306A\u306E\u3067\u3001tenant \u304C\u3042\u308C\u3070\u5E38\u306B\u6D88\u3059\u3002\u5931\u6557\u3057\u3066\u3082\n // logout \u81EA\u4F53\u306F\u5B8C\u4E86\u6271\u3044 (await \u3057\u306A\u3044)\u3002\n if (this._tenant) {\n void clearDPoPSession(this._tenant);\n }\n this._rememberSession = false;\n // #1113 review: \u30B3\u30F3\u30B9\u30C8\u30E9\u30AF\u30BF\u3067 anonymous: true \u6307\u5B9A\u306E\u30A4\u30F3\u30B9\u30BF\u30F3\u30B9\u306E\u307F\n // logout() \u3067 anonymous \u30E2\u30FC\u30C9\u306B\u5FA9\u5E30\u3059\u308B\u3002\u305D\u308C\u4EE5\u5916\u306F false \u306E\u307E\u307E\u3002\n this._anonymous = this._initialAnonymous;\n this._invalidateAuthScopedState();\n }\n\n /**\n * Restore a session previously persisted via\n * `login(email, password, { rememberSession: true })`. Returns true if a\n * usable session was found and loaded into the\n * SDK, false otherwise (no entry, IndexedDB unavailable, or the entry was\n * for a different tenant).\n *\n * After a successful restore the SDK behaves as if `login()` had just\n * succeeded: tokens are set, the DPoP key pair is rehydrated as a\n * non-extractable `CryptoKey`, and subsequent requests automatically\n * carry a fresh DPoP proof. Token refresh continues to flow through\n * `/auth/refresh` and emits `tokenRefresh` events as usual.\n *\n * Token expiry is NOT validated here \u2014 the next `ensureToken()` call will\n * trigger a refresh if the access token has expired (#1230). An expired\n * refresh token will manifest as an `AuthenticationError` from that\n * refresh attempt; consumers should treat such errors as \"session ended,\n * fall back to login()\".\n */\n async restoreSession(): Promise<boolean> {\n if (this._initialAnonymous) return false;\n if (!this._tenant) return false;\n const persisted = await loadDPoPSession(this._tenant);\n if (!persisted) return false;\n\n // \u5225 tenant \u306E entry \u3092\u9593\u9055\u3063\u3066\u8AAD\u307F\u51FA\u3059\u3053\u3068\u306F getter \u3067\u9632\u3044\u3067\u3044\u308B\u304C\u3001\n // \u5FF5\u306E\u305F\u3081\u4E00\u81F4\u78BA\u8A8D\u3002\n if (persisted.tenant !== this._tenant) return false;\n\n this._anonymous = false;\n this._token = persisted.accessToken;\n this._refreshToken = persisted.refreshToken;\n this._tokenExpiry = persisted.expiresAt;\n this._tokenType = 'DPoP';\n this._dpopKeyPair = persisted.keyPair;\n // \u9375\u751F\u6210 Promise \u306F\u3082\u3046\u5F85\u3064\u5FC5\u8981\u304C\u306A\u3044 (\u9375\u306F IDB \u304B\u3089\u5FA9\u5143\u6E08\u307F)\u3002\n this._dpopReady = Promise.resolve();\n this._dpopNonce = null;\n this._rememberSession = true;\n this._email = persisted.email ?? null;\n this._tokenPromise = null;\n this._invalidateAuthScopedState();\n // #1262: the restored session is the same principal that persisted the\n // cache, so re-enable write-through (without clearing) and rehydrate the\n // entries \u2014 the first request after a reload can now send If-None-Match.\n await this._enablePersistentCache(false);\n await this._cache?.hydrate();\n return true;\n }\n\n /**\n * Whether this AuthManager currently keeps the session persisted across\n * reloads (`rememberSession`). Exposed for the `GeonicDB` wrapper; internal\n * consumers should read `_rememberSession` directly.\n */\n isSessionRemembered(): boolean {\n return this._rememberSession;\n }\n\n /**\n * Persist the current DPoP session to IndexedDB. Best-effort: a failed\n * write is logged in debug mode but never propagated, since persistence\n * is purely a UX optimization.\n *\n * @internal\n */\n private async _persistDpopSession(): Promise<void> {\n if (\n !this._rememberSession ||\n !this._tenant ||\n !this._dpopKeyPair ||\n !this._token ||\n !this._refreshToken\n ) {\n return;\n }\n const ok = await saveDPoPSession({\n tenant: this._tenant,\n accessToken: this._token,\n refreshToken: this._refreshToken,\n expiresAt: this._tokenExpiry,\n email: this._email ?? undefined,\n keyPair: this._dpopKeyPair,\n });\n if (!ok) this._log('DPoP session persistence failed (IndexedDB unavailable?)');\n }\n\n /**\n * Drop everything that may have been associated with the previous auth\n * context. Called on `login()` (after token assignment), `setCredentials()`,\n * and `logout()`. Without this, a cached body or in-flight Response from\n * user A could be returned to user B after a credentials swap.\n */\n private _invalidateAuthScopedState(): void {\n this._cache?.clear();\n this._inFlight.clear();\n }\n\n /**\n * Ensure a valid token is available, refreshing or acquiring as needed.\n *\n * #1105: In anonymous mode (no apiKey, no Bearer credentials) this throws\n * `AuthenticationError`. Callers that may run anonymously should branch on\n * `isAnonymous()` first instead of calling `ensureToken()` blindly.\n */\n async ensureToken(): Promise<string> {\n if (this._token && Date.now() < this._tokenExpiry) {\n return this._token;\n }\n\n // Deduplicate concurrent calls\n if (this._tokenPromise) {\n return this._tokenPromise;\n }\n\n // Bearer JWT refresh path \u2014 skip DPoP/PoW\n if (this._refreshToken) {\n this._tokenPromise = this._refreshBearerToken();\n return this._tokenPromise;\n }\n\n // anonymous \u30E2\u30FC\u30C9\u3067\u30C8\u30FC\u30AF\u30F3\u7121\u3057 \u2192 \u53D6\u5F97\u7D4C\u8DEF\u3092\u6301\u305F\u306A\u3044\n if (this._anonymous) {\n throw new AuthenticationError(\n 'Anonymous mode: no token available. Call login() or setCredentials() to authenticate.',\n );\n }\n\n // #1113 review: refresh \u5931\u6557\u3067 `_refreshToken` \u304C null \u306B\u306A\u3063\u305F Bearer-only\n // \u30A4\u30F3\u30B9\u30BF\u30F3\u30B9\u3084\u3001apiKey \u304C\u7A7A\u306E\u307E\u307E PoW \u30D5\u30ED\u30FC\u3078\u843D\u3061\u308B\u3053\u3068\u3092\u9632\u3050\u3002\n // \u3053\u3053\u3067\u660E\u793A\u7684\u306B\u5931\u6557\u3055\u305B\u306A\u3044\u3068 `_acquireTokenViaPow()` \u304C `api_key: ''` \u3067\n // `/auth/nonce` \u3092\u53E9\u3044\u3066\u3057\u307E\u3046\u3002\n if (!this._apiKey) {\n throw new AuthenticationError(\n 'No authentication method available. Call login() / setCredentials() to authenticate, or construct the SDK with an apiKey.',\n );\n }\n\n // API key + DPoP/PoW path\n /* istanbul ignore next: full PoW + DPoP token-exchange path requires the\n live /auth/nonce + /oauth/token endpoints \u2014 covered by E2E. */\n this._tokenPromise = this._acquireTokenViaPow();\n /* istanbul ignore next */\n return this._tokenPromise;\n }\n\n private async _refreshBearerToken(): Promise<string> {\n this._log('refreshing token (type=%s)', this._tokenType);\n try {\n const isDPoP = this._tokenType === 'DPoP';\n const refreshUrl = this._baseUrl + '/auth/refresh';\n const body = JSON.stringify({ refreshToken: this._refreshToken });\n\n // #1221: DPoP-bound refresh \u306F proof \u5FC5\u9808 (RFC 9449)\u3002tokenType \u306F\n // \u524D\u56DE issue \u6642\u306B\u30B5\u30FC\u30D0\u5FDC\u7B54\u3067\u78BA\u5B9A\u6E08\u307F\u306A\u306E\u3067\u3001\u305D\u308C\u3092\u4FE1\u983C\u3059\u308B\u3002\n const res = isDPoP\n ? await this._doDpopRefresh(refreshUrl, body, this._dpopNonce, 0)\n : await fetch(refreshUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n });\n\n if (!res.ok) {\n this._refreshToken = null;\n this._token = null;\n this._tokenPromise = null;\n throw new AuthenticationError('Token refresh failed: ' + res.status, res.status);\n }\n const data = (await res.json()) as LoginResponse;\n this._token = data.accessToken;\n this._tokenExpiry = Date.now() + (data.expiresIn - 60) * 1000;\n // \u30B5\u30FC\u30D0\u5FDC\u7B54\u306E tokenType \u3092\u4FE1\u983C\u3059\u308B\u3002Bearer\u2192DPoP \u306E\u7247\u9053\u79FB\u884C\u306E\u307F\u3067\u3001\n // DPoP-bound refresh \u304C Bearer \u306B\u964D\u683C\u3059\u308B\u3053\u3068\u306F\u306A\u3044 (\u672C\u4F53\u5074\u306F\n // boundJkt \u304C\u3042\u308C\u3070\u5E38\u306B 'DPoP' \u3092\u8FD4\u3059)\u3002\n this._tokenType = data.tokenType ?? (isDPoP ? 'DPoP' : 'Bearer');\n this._refreshToken = data.refreshToken;\n this._tokenPromise = null;\n this.onTokenRefresh?.({\n token: this._token,\n tokenType: this._tokenType,\n expiresIn: data.expiresIn,\n refreshToken: this._refreshToken,\n });\n // #1230: DPoP \u30BB\u30C3\u30B7\u30E7\u30F3\u3092\u6C38\u7D9A\u5316\u3057\u3066\u3044\u308B\u5834\u5408\u306F\u3001\u65B0\u3057\u3044\u30C8\u30FC\u30AF\u30F3\u3092\n // IDB \u306B\u66F8\u304D\u623B\u3059\u3002\u66F8\u304D\u8FBC\u307F\u306F\u975E\u540C\u671F\u3060\u304C await \u3057\u306A\u3044 (\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\n // \u7D4C\u8DEF\u306E\u6240\u8981\u6642\u9593\u306B\u3076\u3089\u4E0B\u3052\u306A\u3044)\u3002\u5931\u6557\u306F best-effort\u3002\n if (this._rememberSession) void this._persistDpopSession();\n return this._token;\n } catch (err) {\n this._tokenPromise = null;\n throw err;\n }\n }\n\n /**\n * Perform a DPoP-bound `/auth/refresh` call with the standard RFC 9449 \u00A78\n * `use_dpop_nonce` retry handshake.\n *\n * @internal\n */\n private async _doDpopRefresh(\n refreshUrl: string,\n body: string,\n dpopNonce: string | null,\n retryCount: number,\n ): Promise<Response> {\n if (this._dpopReady) await this._dpopReady;\n if (!this._dpopKeyPair) {\n throw new AuthenticationError(\n 'DPoP-bound refresh requires a DPoP key pair, but key generation was unavailable.',\n );\n }\n const proof = await createDPoPProof(\n this._dpopKeyPair,\n 'POST',\n refreshUrl,\n null,\n dpopNonce,\n );\n if (!proof) {\n throw new AuthenticationError('DPoP proof generation failed');\n }\n const res = await fetch(refreshUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', DPoP: proof },\n body,\n });\n const serverNonce = res.headers.get('DPoP-Nonce');\n if (serverNonce) this._dpopNonce = serverNonce;\n if (\n res.status === 401 &&\n serverNonce &&\n serverNonce !== dpopNonce &&\n retryCount < AuthManager.MAX_DPOP_RETRIES\n ) {\n return this._doDpopRefresh(refreshUrl, body, serverNonce, retryCount + 1);\n }\n return res;\n }\n\n /* istanbul ignore next: PoW + DPoP token-exchange flow is exercised by E2E\n against a live server. Unit-mocking each fetch + crypto step would not\n verify the actual nonce/proof exchange. */\n private async _acquireTokenViaPow(): Promise<string> {\n try {\n if (this._dpopReady) await this._dpopReady;\n\n // Step 1: Get nonce + PoW challenge\n const nonceRes = await fetch(this._baseUrl + '/auth/nonce', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ api_key: this._apiKey }),\n });\n if (!nonceRes.ok)\n throw new AuthenticationError('Nonce request failed: ' + nonceRes.status, nonceRes.status);\n const nonceData = (await nonceRes.json()) as {\n nonce: string;\n challenge: string;\n difficulty: number;\n dpop_nonce?: string;\n };\n\n // RFC 9449 \u00A78: store pre-issued DPoP-Nonce\n if (nonceData.dpop_nonce) this._dpopNonce = nonceData.dpop_nonce;\n\n // Step 2: Solve PoW\n const proof = await solvePoW(nonceData.challenge, nonceData.difficulty);\n\n // Step 3: Exchange for token\n const tokenUrl = this._baseUrl + '/oauth/token';\n const tokenBody = JSON.stringify({\n grant_type: 'api_key',\n api_key: this._apiKey,\n nonce: nonceData.nonce,\n proof: String(proof),\n });\n\n const res = await this._doTokenExchange(tokenUrl, tokenBody, this._dpopNonce);\n\n if (!res.ok)\n throw new AuthenticationError('Token request failed: ' + res.status, res.status);\n const newNonce = res.headers.get('DPoP-Nonce');\n if (newNonce) this._dpopNonce = newNonce;\n\n const data = (await res.json()) as {\n access_token: string;\n expires_in: number;\n token_type?: string;\n };\n this._token = data.access_token;\n this._tokenExpiry = Date.now() + (data.expires_in - 60) * 1000;\n this._tokenType = (data.token_type as 'Bearer' | 'DPoP') || 'Bearer';\n this._tokenPromise = null;\n return this._token;\n } catch (err) {\n this._tokenPromise = null;\n throw err;\n }\n }\n\n private static readonly MAX_DPOP_RETRIES = 3;\n\n /* istanbul ignore next: DPoP nonce-retry handshake (RFC 9449 \u00A78) \u2014 requires\n a real server emitting `use_dpop_nonce` 400s. Exercised by E2E. */\n private async _doTokenExchange(\n tokenUrl: string,\n tokenBody: string,\n dpopNonce: string | null,\n retryCount = 0,\n ): Promise<Response> {\n const reqHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n const dpopProof = await createDPoPProof(\n this._dpopKeyPair,\n 'POST',\n tokenUrl,\n null,\n dpopNonce,\n );\n if (dpopProof) reqHeaders['DPoP'] = dpopProof;\n\n const res = await fetch(tokenUrl, {\n method: 'POST',\n headers: reqHeaders,\n body: tokenBody,\n });\n\n // RFC 9449 \u00A78: use_dpop_nonce \u2192 retry with server nonce\n if (res.status === 400 && this._dpopKeyPair) {\n const errBody = await res.json().catch(() => ({})) as Record<string, string>;\n if (errBody.error === 'use_dpop_nonce') {\n const serverNonce = res.headers.get('DPoP-Nonce');\n if (serverNonce && serverNonce !== dpopNonce && retryCount < AuthManager.MAX_DPOP_RETRIES) {\n this._dpopNonce = serverNonce;\n return this._doTokenExchange(tokenUrl, tokenBody, serverNonce, retryCount + 1);\n }\n }\n throw new AuthenticationError(\n 'Token request failed: ' +\n (errBody.error_description || errBody.error || '400'),\n 400,\n );\n }\n\n return res;\n }\n\n /**\n * Make an authenticated HTTP request with automatic token refresh and DPoP.\n *\n * When the SDK cache is enabled and the request is cacheable\n * (GET / HEAD without a body), this method:\n * - Sends `If-None-Match` / `If-Modified-Since` derived from a previously\n * cached entry, if any.\n * - Returns the cached body wrapped in a synthesized `200` Response when\n * the server replies `304 Not Modified`.\n * - Persists fresh `200` responses (with their ETag/Last-Modified) into\n * the cache.\n * - Deduplicates concurrent in-flight requests to the same path.\n */\n async request(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<Response> {\n this._log(method, path);\n\n if (!this._cache || !isCacheableMethod(method) || body !== undefined) {\n const token = this.isAnonymous() ? null : await this.ensureToken();\n const res = await this._doAuthenticatedRequest(method, path, body, token);\n this._log(method, path, '\u2192', res.status);\n return res;\n }\n\n const key = SdkCache.keyFor(method, path);\n\n // Request deduplication: if the same path is already in flight,\n // return the same Response (cloned) so concurrent callers share the result.\n const inFlight = this._inFlight.get(key);\n if (inFlight) {\n return inFlight.then((res) => res.clone());\n }\n\n const promise = this._cachedRequest(method, path, key);\n this._inFlight.set(key, promise);\n try {\n return await promise;\n } finally {\n this._inFlight.delete(key);\n }\n }\n\n private async _cachedRequest(\n method: string,\n path: string,\n key: string,\n ): Promise<Response> {\n const cache = this._cache;\n /* istanbul ignore if: defensive guard \u2014 request() already gates on this._cache. */\n if (!cache) {\n // Defensive: caller already checked, but TS narrowing prefers an explicit guard.\n const token = this.isAnonymous() ? null : await this.ensureToken();\n return this._doAuthenticatedRequest(method, path, undefined, token);\n }\n const cached = cache.get(key);\n const conditional: Record<string, string> = {};\n if (cached?.etag) conditional['If-None-Match'] = cached.etag;\n if (cached?.lastModified) conditional['If-Modified-Since'] = cached.lastModified;\n\n const token = this.isAnonymous() ? null : await this.ensureToken();\n const res = await this._doAuthenticatedRequest(method, path, undefined, token, 0, conditional);\n this._log(method, path, '\u2192', res.status);\n\n // 304 Not Modified \u2014 replay the cached body as a synthetic 200.\n // The SDK consumer should be unaware of conditional negotiation.\n //\n // The 304 response itself may carry refreshed validators (ETag /\n // Last-Modified rotation, updated Cache-Control etc.). Merge them into\n // the cached entry so the next request sends the latest If-None-Match.\n if (res.status === 304 && cached) {\n const refreshedHeaders = { ...cached.headers, ...snapshotHeaders(res.headers) };\n const refreshedEtag = res.headers.get('etag') ?? cached.etag;\n const refreshedLastModified = res.headers.get('last-modified') ?? cached.lastModified;\n cache.set(key, {\n ...cached,\n etag: refreshedEtag,\n lastModified: refreshedLastModified,\n headers: refreshedHeaders,\n cachedAt: Date.now(),\n });\n this.emitCacheEvent('cacheHit', { key, path });\n const body = typeof cached.data === 'string' ? cached.data : JSON.stringify(cached.data);\n return new Response(body, { status: 200, headers: refreshedHeaders });\n }\n\n // Persist fresh 200 responses that carry a validator.\n if (res.status === 200) {\n const etag = res.headers.get('etag') ?? undefined;\n const lastModified = res.headers.get('last-modified') ?? undefined;\n if (etag || lastModified) {\n const text = await res.clone().text();\n let data: unknown = text;\n try {\n data = JSON.parse(text);\n } catch {\n // not JSON \u2014 keep the raw text\n }\n cache.set(key, {\n etag,\n lastModified,\n data,\n headers: snapshotHeaders(res.headers),\n cachedAt: Date.now(),\n });\n this.emitCacheEvent('cacheMiss', { key, path });\n }\n }\n\n return res;\n }\n\n private static readonly MAX_REQUEST_RETRIES = 3;\n\n private async _doAuthenticatedRequest(\n method: string,\n path: string,\n body: unknown,\n token: string | null,\n retryCount = 0,\n extraHeaders: Record<string, string> = {},\n ): Promise<Response> {\n // #1113 review-3: \u975E\u533F\u540D\u30A4\u30F3\u30B9\u30BF\u30F3\u30B9\u3067 `setCredentials({ tokenType: 'DPoP' })`\n // \u3092\u53D7\u3051\u305F\u76F4\u5F8C\u306B\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u98DB\u3076\u3068\u3001\u30B3\u30F3\u30B9\u30C8\u30E9\u30AF\u30BF\u3067\u958B\u59CB\u3057\u305F\u9375\u751F\u6210\u304C\u307E\u3060\n // \u5B8C\u4E86\u3057\u3066\u3044\u306A\u3044 (`_dpopKeyPair === null`) \u53EF\u80FD\u6027\u304C\u3042\u308B\u3002`_doAuthenticatedRequest`\n // \u5185\u3067 `_dpopReady` \u3092 await \u3057\u306A\u3044\u3068\u3001DPoP \u306E\u306F\u305A\u304C proof \u7121\u3057\u306E\n // `Authorization: Bearer ...` \u3067\u9001\u3089\u308C\u3066\u3057\u307E\u3046\u3002\u9375\u751F\u6210\u304C\u5931\u6557\u3057\u3066\u3044\u305F\n // \u5834\u5408\u306F AuthenticationError \u3067 fail-closed \u3055\u305B\u308B\u3002\n /* istanbul ignore if: DPoP path requires a real Web Crypto key pair \u2014\n jsdom skips key generation (dpopSupported=false). Exercised by E2E. */\n if (this._tokenType === 'DPoP' && token !== null) {\n if (this._dpopReady) await this._dpopReady;\n if (!this._dpopKeyPair) {\n throw new AuthenticationError(\n 'DPoP credentials require a generated DPoP key pair, but key generation ' +\n 'was unavailable or failed.',\n );\n }\n }\n\n const url = this._baseUrl + path;\n /* istanbul ignore next: DPoP token type is unreachable in jsdom tests. */\n const isDPoP = this._tokenType === 'DPoP' && !!this._dpopKeyPair && token !== null;\n\n // #1583 / #1599 / #2069: every NGSI-LD write body must carry an inline @context under\n // application/ld+json (which the SDK always sends). Inject the core context when a body\n // omits it so existing callers keep working; a caller-supplied @context is preserved.\n //\n // Scope = the NGSI-LD API (`/ngsi-ld/v1/**`) \u2014 exactly where the broker applies ETSI\n // GS CIM 009 clause 6.3.5 (`assertContextSourceCompliance`, the NGSI-LD router entry).\n // It must NOT touch other endpoints (auth/admin/rules/snapshots reached via the generic\n // request() escape hatch): many use `.strict()` schemas that would reject the injected\n // @context with a 400. `/ngsi-ld/v1/jsonldContexts` is excluded too \u2014 its body *is* the\n // context document being registered.\n //\n // Array bodies (`/entityOperations/*`) get the context **per element**: each element is\n // its own JSON-LD document (clause 5.6.7.3), and the broker reports a missing one as a\n // per-entity BadRequestData inside a 207. Non-object elements are left alone \u2014\n // `entityOperations/delete` takes Entity ID strings (clause 5.6.10.3) which cannot carry\n // an @context.\n const pathname = path.split('?')[0];\n const isNgsiLdWrite =\n pathname.startsWith('/ngsi-ld/v1/') && pathname !== '/ngsi-ld/v1/jsonldContexts';\n const withCoreContext = (value: unknown): unknown =>\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n !('@context' in (value as Record<string, unknown>))\n ? { ...(value as Record<string, unknown>), '@context': NGSI_LD_CORE_CONTEXT }\n : value;\n let effectiveBody = body;\n if (isNgsiLdWrite && effectiveBody !== undefined) {\n effectiveBody = Array.isArray(effectiveBody)\n ? effectiveBody.map(withCoreContext)\n : withCoreContext(effectiveBody);\n }\n const bodyStr = effectiveBody !== undefined ? JSON.stringify(effectiveBody) : undefined;\n\n const doRequest = async (\n reqToken: string | null,\n dpopNonce: string | null,\n ): Promise<Response> => {\n const reqIsDPoP =\n this._tokenType === 'DPoP' && !!this._dpopKeyPair && reqToken !== null;\n /* istanbul ignore next: DPoP proof creation requires a real Web Crypto key. */\n const dpopProof = reqIsDPoP\n ? await createDPoPProof(\n this._dpopKeyPair,\n method,\n url,\n reqToken,\n dpopNonce,\n )\n : null;\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/ld+json',\n Accept: 'application/ld+json',\n ...extraHeaders,\n };\n // #1105: anonymous \u30E2\u30FC\u30C9\u3067\u306F Authorization \u3092\u4ED8\u3051\u306A\u3044\u3002\n if (reqToken !== null) {\n /* istanbul ignore next: DPoP authorization scheme path \u2014 covered by E2E. */\n headers['Authorization'] = (reqIsDPoP ? 'DPoP ' : 'Bearer ') + reqToken;\n }\n /* istanbul ignore next: DPoP proof header \u2014 covered by E2E. */\n if (dpopProof) headers['DPoP'] = dpopProof;\n if (this._tenant) headers['Fiware-Service'] = this._tenant;\n\n return fetch(url, { method, headers, body: bodyStr });\n };\n\n let currentToken = token;\n let res = await doRequest(currentToken, this._dpopNonce);\n const previousNonce = this._dpopNonce;\n const newNonce = res.headers.get('DPoP-Nonce');\n /* istanbul ignore if: DPoP-Nonce header only emitted by live DPoP server. */\n if (newNonce) this._dpopNonce = newNonce;\n\n // anonymous \u30EA\u30AF\u30A8\u30B9\u30C8\u306E 401 \u306F\u518D\u8A66\u884C\u3057\u306A\u3044 (XACML Deny \u2192 \u305D\u306E\u307E\u307E\u8FD4\u3059)\n const canRetry401 = currentToken !== null;\n\n // RFC 9449 \u00A78: use_dpop_nonce \u2192 retry with server nonce\n /* istanbul ignore if: DPoP nonce-rotation 401 retry requires a live DPoP\n server. The Bearer 401 retry below is unit-tested; this branch is\n covered by E2E. */\n if (res.status === 401 && isDPoP && newNonce && newNonce !== previousNonce) {\n res = await doRequest(currentToken, newNonce);\n const rn = res.headers.get('DPoP-Nonce');\n if (rn) this._dpopNonce = rn;\n if (res.status === 401 && canRetry401) {\n this._token = null;\n this._tokenPromise = null;\n currentToken = await this.ensureToken();\n res = await doRequest(currentToken, this._dpopNonce);\n const fn = res.headers.get('DPoP-Nonce');\n if (fn) this._dpopNonce = fn;\n }\n } else if (res.status === 401 && canRetry401) {\n this._token = null;\n this._tokenPromise = null;\n currentToken = await this.ensureToken();\n res = await doRequest(currentToken, this._dpopNonce);\n const rn = res.headers.get('DPoP-Nonce');\n /* istanbul ignore if: DPoP nonce header only emitted by live DPoP server. */\n if (rn) this._dpopNonce = rn;\n }\n\n if (res.status === 429 && retryCount < AuthManager.MAX_REQUEST_RETRIES) {\n const retryNonce = res.headers.get('DPoP-Nonce');\n /* istanbul ignore if: DPoP nonce header only emitted by live DPoP server. */\n if (retryNonce) this._dpopNonce = retryNonce;\n const delay =\n parseInt(res.headers.get('Retry-After') || '1', 10) * 1000;\n await new Promise<void>((resolve) => setTimeout(resolve, delay));\n return this._doAuthenticatedRequest(method, path, body, currentToken, retryCount + 1, extraHeaders);\n }\n\n return res;\n }\n}\n", "/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\nimport { createDPoPProof } from './dpop';\nimport type { AuthManager } from './auth';\nimport {\n TOKEN_REFRESH_LEEWAY_MS,\n TOKEN_REFRESH_MIN_MS,\n RECONNECT_MAX_ATTEMPTS,\n RECONNECT_BASE_MS,\n RECONNECT_MAX_DELAY_MS,\n SUB_PROTOCOL,\n} from './constants';\nimport type { SubscriptionMessage } from './types';\n\ntype EmitFn = (event: string, data?: unknown) => void;\n\n/* istanbul ignore file: WebSocketManager wraps a real browser WebSocket and\n only meaningfully runs against a live server (E2E coverage via\n tests/e2e/features/...). Unit-testing the timer-driven token refresh,\n exponential reconnect backoff, DPoP bind, and onmessage/onclose state\n machine in jsdom would require fragile mocks that do not exercise the\n actual transport. The pure subscribe/disconnect bookkeeping is covered\n incidentally via SDK index tests. */\n\n/**\n * WebSocket connection manager for the GeonicDB SDK.\n *\n * Handles connection lifecycle, DPoP binding, token refresh,\n * automatic reconnection with exponential backoff, and subscriptions.\n */\nexport class WebSocketManager {\n private _auth: AuthManager;\n private _tenant: string;\n private _wsEndpointOverride: string | null;\n private _baseUrl: string;\n private _emit: EmitFn;\n\n private _ws: WebSocket | null = null;\n private _wsEndpoint: string | null = null;\n private _deploymentHostname: string | null = null;\n private _wsIntentionalClose = false;\n private _reconnectAttempts = 0;\n private _tokenRefreshTimer: ReturnType<typeof setTimeout> | null = null;\n private _reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private _subscription: SubscriptionMessage | null = null;\n private _pendingSubscription = false;\n\n constructor(\n auth: AuthManager,\n baseUrl: string,\n tenant: string,\n emit: EmitFn,\n wsEndpointOverride?: string,\n ) {\n this._auth = auth;\n this._baseUrl = baseUrl;\n this._tenant = tenant;\n this._emit = emit;\n this._wsEndpointOverride = wsEndpointOverride || null;\n }\n\n private _log(...args: unknown[]): void {\n if (this._auth._debug) console.log('[GeonicDB:WS]', ...args);\n }\n\n /** Establish WebSocket connection (authentication is automatic). */\n async connect(): Promise<void> {\n // #1105: anonymous \u30E2\u30FC\u30C9\u3067\u306F WS \u63A5\u7D9A\u4E0D\u53EF (\u30B5\u30FC\u30D0\u30FC local-ws-server.ts \u306F\n // token \u5FC5\u9808)\u3002HTTP \u306E\u307F anonymous \u3092\u30B5\u30DD\u30FC\u30C8\u3059\u308B\u3002\n if (this._auth.isAnonymous()) {\n const err = new Error(\n 'WebSocket connect() is not supported in anonymous mode. ' +\n 'Authenticate via login() or setCredentials() before connect().',\n );\n this._emit('error', err);\n throw err;\n }\n if (this._reconnectTimer) {\n clearTimeout(this._reconnectTimer);\n this._reconnectTimer = null;\n }\n if (\n this._ws &&\n (this._ws.readyState === WebSocket.OPEN ||\n this._ws.readyState === WebSocket.CONNECTING)\n ) {\n return;\n }\n this._wsIntentionalClose = false;\n this._reconnectAttempts = 0;\n\n try {\n const token = await this._auth.ensureToken();\n await this._openWebSocket(token);\n } catch (err) {\n this._emit('error', err);\n }\n }\n\n /** Force-reconnect (closes existing connection, preserves subscriptions). */\n async reconnect(): Promise<void> {\n this._wsIntentionalClose = true;\n this._clearTimers();\n if (this._ws) {\n this._ws.close(1000, 'Client reconnect');\n this._ws = null;\n }\n return this.connect();\n }\n\n /** Disconnect WebSocket (does not fire 'disconnected' event). */\n disconnect(): void {\n this._wsIntentionalClose = true;\n this._clearTimers();\n this._subscription = null;\n this._pendingSubscription = false;\n if (this._ws) {\n this._ws.close(1000, 'Client disconnect');\n this._ws = null;\n }\n }\n\n /** Check if WebSocket is currently open. */\n isConnected(): boolean {\n return !!(this._ws && this._ws.readyState === WebSocket.OPEN);\n }\n\n /** Subscribe to entity events (can be called before or after connect). */\n subscribe(options?: {\n entityTypes?: string[];\n idPattern?: string;\n scopeQ?: string;\n /** #2026: \u914D\u4FE1\u30A4\u30D9\u30F3\u30C8\u306E\u30EC\u30F3\u30C0\u30EA\u30F3\u30B0\u306B\u4F7F\u3046 `@context` (URL / URL \u914D\u5217)\u3002 */\n '@context'?: string | string[];\n }): void {\n const msg: SubscriptionMessage = { action: 'subscribe' };\n if (options) {\n if (options.entityTypes) msg.entityTypes = options.entityTypes;\n if (options.idPattern) msg.idPattern = options.idPattern;\n // #1132: scopeQ \u3092\u30B5\u30FC\u30D0\u5074\u306B\u4F1D\u3048\u308B\u3002\u30B5\u30FC\u30D0\u304C\u672A\u5BFE\u5FDC\u306E\u5834\u5408\u306F\u9ED9\u3063\u3066\u7121\u8996\u3055\u308C\u308B\n if (options.scopeQ) msg.scopeQ = options.scopeQ;\n // #2026: \u914D\u4FE1\u7269\u306E\u578B\u540D\u30FB\u5C5E\u6027\u540D\u3092 compact \u3059\u308B\u8A9E\u5F59\u3002\u672A\u5BFE\u5FDC\u30B5\u30FC\u30D0\u3067\u306F\u9ED9\u3063\u3066\u7121\u8996\u3055\u308C\u308B\n if (options['@context']) msg['@context'] = options['@context'];\n }\n this._subscription = msg;\n // If DPoP pending verification, store subscription but don't send yet\n if (this._pendingSubscription) return;\n if (this._ws && this._ws.readyState === WebSocket.OPEN) {\n this._ws.send(JSON.stringify(msg));\n }\n }\n\n private async _discoverWsEndpoint(): Promise<string> {\n if (this._wsEndpointOverride) return this._wsEndpointOverride;\n if (this._wsEndpoint) return this._wsEndpoint;\n\n // Fetch endpoint from server discovery API\n try {\n const res = await fetch(this._baseUrl + '/sdk/v1/streaming');\n if (res.ok) {\n const info = (await res.json()) as {\n enabled: boolean;\n endpoint: string | null;\n deployment?: string;\n };\n if (info.enabled && info.endpoint) {\n this._wsEndpoint = info.endpoint;\n if (info.deployment) {\n this._deploymentHostname = info.deployment;\n }\n return this._wsEndpoint;\n }\n }\n } catch {\n // Discovery failed \u2014 fall back to URL scheme conversion\n }\n\n // Fallback: convert http(s) baseUrl to ws(s)\n this._wsEndpoint = this._baseUrl.replace(/^http/, 'ws');\n return this._wsEndpoint;\n }\n\n private async _openWebSocket(token: string): Promise<void> {\n const endpoint = await this._discoverWsEndpoint();\n return new Promise<void>((resolve, reject) => {\n const params = new URLSearchParams({ tenant: this._tenant });\n if (this._deploymentHostname) {\n params.set('deployment', this._deploymentHostname);\n }\n const wsUrl =\n endpoint +\n (endpoint.indexOf('?') === -1 ? '?' : '&') +\n params.toString();\n\n this._log('connecting', wsUrl);\n const ws = new WebSocket(wsUrl, [SUB_PROTOCOL, token]);\n this._ws = ws;\n\n ws.onopen = (): void => {\n if (this._ws !== ws) return;\n this._log('connected');\n this._reconnectAttempts = 0;\n\n const isDPoP =\n this._auth._tokenType === 'DPoP' && !!this._auth._dpopKeyPair;\n\n const bindPromise = isDPoP\n ? createDPoPProof(this._auth._dpopKeyPair, 'GET', wsUrl, null).then(\n (proof) => {\n if (proof && ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ action: 'dpop_bind', proof }));\n }\n },\n )\n : Promise.resolve();\n\n bindPromise\n .then(() => {\n this._scheduleTokenRefresh();\n\n if (isDPoP) {\n this._pendingSubscription = true;\n } else if (this._subscription) {\n ws.send(JSON.stringify(this._subscription));\n }\n\n this._emit('open');\n this._emit('connected');\n resolve();\n })\n .catch((err) => {\n this._emit('error', err);\n resolve();\n });\n };\n\n ws.onmessage = (event: MessageEvent): void => {\n if (this._ws !== ws) return;\n let msg: Record<string, unknown>;\n try {\n msg = JSON.parse(event.data as string) as Record<string, unknown>;\n } catch {\n return;\n }\n\n if (msg.type === 'pong') return;\n\n // #2055: \u8CFC\u8AAD\u5B8C\u4E86\u306E ack\u3002**\u3053\u3053\u3067\u63E1\u3089\u306A\u3044\u3068\u4E0B\u306E fallthrough \u304C\n // \u300C\u672A\u77E5\u306E type = \u30A8\u30F3\u30C6\u30A3\u30C6\u30A3\u30A4\u30D9\u30F3\u30C8\u300D\u3068\u3057\u3066\u6271\u3044\u3001\u8CFC\u8AAD\u76F4\u5F8C\u306B\u507D\u306E\u30A4\u30D9\u30F3\u30C8\u3092\n // 1 \u4EF6\u914D\u3063\u3066\u3057\u307E\u3046\u3002** \u30B5\u30FC\u30D0\u306F `subscribe` \u306E\u30D5\u30A3\u30EB\u30BF\u9069\u7528\u5F8C\u306B\u3053\u308C\u3092\u9001\u308B\n // (`@context` \u306E\u89E3\u6C7A\u304C\u975E\u540C\u671F\u306A\u306E\u3067\u3001\u53D7\u7406\u3068\u6709\u52B9\u5316\u306E\u9593\u306B\u7A93\u304C\u3042\u308B)\u3002\n if (msg.type === 'subscribed') {\n this._emit('subscribed', undefined);\n return;\n }\n\n if (msg.type === 'dpop_verified') {\n this._pendingSubscription = false;\n if (this._subscription) {\n ws.send(JSON.stringify(this._subscription));\n }\n return;\n }\n\n if (msg.type === 'error') {\n this._emit('error', new Error(msg.message as string));\n return;\n }\n\n this._log('event', msg.type, msg.entityId || '');\n\n // Entity events: build a complete NGSI-LD entity from id + type + data\n if (msg.entityId && msg.data && typeof msg.data === 'object') {\n msg.entity = {\n id: msg.entityId,\n type: msg.entityType,\n ...(msg.data as Record<string, unknown>),\n };\n }\n\n this._emit(msg.type as string, msg);\n this._emit('message', msg);\n };\n\n ws.onclose = (event: CloseEvent): void => {\n if (this._ws !== ws) return;\n this._clearTimers();\n\n if (this._wsIntentionalClose) {\n this._emit('close', event);\n return;\n }\n\n this._emit('close', event);\n this._emit('disconnected');\n this._autoReconnect();\n };\n\n ws.onerror = (err: Event): void => {\n if (this._ws !== ws) return;\n this._emit('error', err);\n if (ws.readyState !== WebSocket.OPEN) {\n reject(new Error('WebSocket connection failed'));\n }\n };\n });\n }\n\n private _scheduleTokenRefresh(): void {\n if (this._tokenRefreshTimer) clearTimeout(this._tokenRefreshTimer);\n\n const remaining = this._auth._tokenExpiry - Date.now();\n const refreshIn = Math.max(\n remaining - TOKEN_REFRESH_LEEWAY_MS,\n TOKEN_REFRESH_MIN_MS,\n );\n\n this._tokenRefreshTimer = setTimeout(() => {\n const oldWs = this._ws;\n this._auth._token = null;\n this._auth\n .ensureToken()\n .then((newToken) => {\n if (!oldWs || oldWs.readyState !== WebSocket.OPEN) return;\n\n this._wsIntentionalClose = true;\n oldWs.close(1000, 'Token refresh');\n\n this._wsIntentionalClose = false;\n return this._openWebSocket(newToken);\n })\n .catch((err) => {\n this._emit('error', err);\n this._autoReconnect();\n });\n }, refreshIn);\n }\n\n private _autoReconnect(): void {\n if (this._wsIntentionalClose) return;\n if (\n this._ws &&\n (this._ws.readyState === WebSocket.OPEN ||\n this._ws.readyState === WebSocket.CONNECTING)\n ) {\n return;\n }\n\n if (this._reconnectAttempts >= RECONNECT_MAX_ATTEMPTS) {\n this._emit('error', new Error('Max reconnection attempts reached'));\n return;\n }\n\n const delay = Math.min(\n RECONNECT_BASE_MS * Math.pow(2, this._reconnectAttempts),\n RECONNECT_MAX_DELAY_MS,\n );\n this._reconnectAttempts++;\n this._emit('reconnecting', {\n attempt: this._reconnectAttempts,\n delay,\n });\n\n this._reconnectTimer = setTimeout(() => {\n this._auth._token = null;\n this._auth\n .ensureToken()\n .then((token) => this._openWebSocket(token))\n .catch(() => this._autoReconnect());\n }, delay);\n }\n\n private _clearTimers(): void {\n if (this._tokenRefreshTimer) {\n clearTimeout(this._tokenRefreshTimer);\n this._tokenRefreshTimer = null;\n }\n if (this._reconnectTimer) {\n clearTimeout(this._reconnectTimer);\n this._reconnectTimer = null;\n }\n }\n}\n", "/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\nimport {\n SDK_CACHE_IDB_NAME,\n SDK_CACHE_IDB_STORE,\n SDK_CACHE_IDB_VERSION,\n} from './constants';\nimport type { CacheEntry } from './cache';\n\n/**\n * Persistent backing store for the SDK's ETag/If-None-Match cache (#1262).\n *\n * `SdkCache` is an in-memory LRU and is therefore lost on page reload, which\n * kills the `304` revalidation path for SPAs: a reloaded page has no cached\n * validators, cannot send `If-None-Match`, and the server always responds\n * `200` with a full body. This module mirrors cache entries into IndexedDB so\n * they survive a reload and the validators can be replayed.\n *\n * Persistence is wired only in environments that expose IndexedDB (browsers).\n * Node-based consumers (CLI, server-side) keep the pure in-memory behaviour \u2014\n * `createCachePersistence()` returns `null` there and `SdkCache` runs unchanged.\n *\n * Auth boundary: entries are namespaced by a `scope` string (the tenant) and\n * the persisted store is dropped on every credential change (`login()`,\n * `setCredentials()`, `logout()`). Persisted entries are only ever read back\n * when a *remembered* session is restored \u2014 i.e. the very same principal that\n * wrote them \u2014 so a cached body can never be replayed across users.\n */\nexport interface CachePersistence {\n /**\n * Load up to `limit` persisted entries for a scope, ordered oldest \u2192\n * newest by `cachedAt` so the caller can replay them into an LRU in the\n * correct recency order. Never throws \u2014 resolves `[]` on any failure.\n */\n loadScope(\n scope: string,\n limit: number,\n ): Promise<Array<{ key: string; entry: CacheEntry }>>;\n /** Insert or replace a single entry. Best-effort; never throws. */\n put(scope: string, key: string, entry: CacheEntry): Promise<void>;\n /** Remove specific entries within a scope. Best-effort; never throws. */\n remove(scope: string, keys: string[]): Promise<void>;\n /** Drop every entry for a scope (auth change / explicit flush). Never throws. */\n clearScope(scope: string): Promise<void>;\n}\n\n/**\n * Whether IndexedDB is available for cache persistence. Mirrors the DPoP\n * persistence probe in `dpop.ts`: both `indexedDB` and `structuredClone`\n * (used by IndexedDB internally for the value clone) must exist.\n */\nexport const cachePersistenceSupported =\n typeof (globalThis as { indexedDB?: unknown }).indexedDB !== 'undefined' &&\n typeof structuredClone !== 'undefined';\n\n/** NUL separates the scope from the cache key in a composite record id. */\nconst SCOPE_SEP = '\\u0000';\n\n/** Highest code point \u2014 upper bound for a scope-prefixed key range. */\nconst SCOPE_RANGE_END = '\uFFFF';\n\ninterface PersistedCacheRecord {\n /** `${scope}\\u0000${key}` \u2014 primary key. */\n id: string;\n scope: string;\n key: string;\n entry: CacheEntry;\n}\n\n// ============================================================\n// IndexedDB-backed implementation (#1262)\n// ============================================================\n//\n// The IndexedDB DOM types live only in `lib: [\"DOM\"]`, but the repo-wide\n// tsconfig/ESLint context uses the Node libs. Rather than split the SDK into a\n// separate lint config, the IDB-touching code is treated as `any` under a\n// local eslint-disable \u2014 the same trade-off already made in `dpop.ts`.\n\n/* eslint-disable @typescript-eslint/no-unsafe-call,\n @typescript-eslint/no-unsafe-member-access,\n @typescript-eslint/prefer-promise-reject-errors,\n @typescript-eslint/no-unsafe-assignment,\n @typescript-eslint/no-unsafe-argument,\n @typescript-eslint/no-explicit-any */\n\n/* istanbul ignore next: trivial wrapper, exercised via the\n fake-indexeddb-backed unit tests rather than statement-counted here. */\nfunction openCacheDB(): Promise<any> {\n return new Promise<any>((resolve, reject) => {\n const req: any = (globalThis as any).indexedDB.open(\n SDK_CACHE_IDB_NAME,\n SDK_CACHE_IDB_VERSION,\n );\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains(SDK_CACHE_IDB_STORE)) {\n db.createObjectStore(SDK_CACHE_IDB_STORE, { keyPath: 'id' });\n }\n };\n req.onsuccess = () => { resolve(req.result); };\n req.onerror = () => { reject(req.error ?? new Error('IndexedDB open failed')); };\n // A version upgrade blocked by another open tab would otherwise leave the\n // open request pending forever, so the per-op `catch` never runs. Reject to\n // keep persistence best-effort and fail fast.\n req.onblocked = () => { reject(req.error ?? new Error('IndexedDB open blocked')); };\n });\n}\n\nfunction recordId(scope: string, key: string): string {\n return scope + SCOPE_SEP + key;\n}\n\n/** IndexedDB implementation of {@link CachePersistence}. Stateless \u2014 opens a\n * fresh connection per operation, like the DPoP persistence helpers. */\nexport class IdbCachePersistence implements CachePersistence {\n async loadScope(\n scope: string,\n limit: number,\n ): Promise<Array<{ key: string; entry: CacheEntry }>> {\n let db: any = null;\n try {\n db = await openCacheDB();\n const records = await new Promise<PersistedCacheRecord[]>((resolve, reject) => {\n const tx = db!.transaction(SDK_CACHE_IDB_STORE, 'readonly');\n const range = (globalThis as any).IDBKeyRange.bound(\n scope + SCOPE_SEP,\n scope + SCOPE_SEP + SCOPE_RANGE_END,\n );\n const req = tx.objectStore(SDK_CACHE_IDB_STORE).getAll(range);\n req.onsuccess = () => { resolve(req.result ?? []); };\n req.onerror = () => { reject(req.error ?? new Error('IndexedDB getAll failed')); };\n });\n return records\n .filter((r) => r && r.scope === scope && typeof r.key === 'string' && r.entry)\n .sort((a, b) => (a.entry.cachedAt ?? 0) - (b.entry.cachedAt ?? 0))\n .slice(-limit)\n .map((r) => ({ key: r.key, entry: r.entry }));\n } catch {\n /* istanbul ignore next: reachable only on IDB faults (db deletion\n mid-read, blocked tab); persistence is best-effort. */\n return [];\n } finally {\n db?.close();\n }\n }\n\n async put(scope: string, key: string, entry: CacheEntry): Promise<void> {\n let db: any = null;\n try {\n db = await openCacheDB();\n await new Promise<void>((resolve, reject) => {\n const tx = db!.transaction(SDK_CACHE_IDB_STORE, 'readwrite');\n const record: PersistedCacheRecord = { id: recordId(scope, key), scope, key, entry };\n tx.objectStore(SDK_CACHE_IDB_STORE).put(record);\n tx.oncomplete = () => { resolve(); };\n tx.onerror = () => { reject(tx.error ?? new Error('IndexedDB put failed')); };\n tx.onabort = () => { reject(tx.error ?? new Error('IndexedDB transaction aborted')); };\n });\n } catch {\n /* istanbul ignore next: write-path catch is reachable only on IDB faults\n (quota exceeded, blocked tab); best-effort, covered via E2E. */\n // swallow \u2014 persistence is best-effort\n } finally {\n db?.close();\n }\n }\n\n async remove(scope: string, keys: string[]): Promise<void> {\n if (keys.length === 0) return;\n let db: any = null;\n try {\n db = await openCacheDB();\n await new Promise<void>((resolve, reject) => {\n const tx = db!.transaction(SDK_CACHE_IDB_STORE, 'readwrite');\n const store = tx.objectStore(SDK_CACHE_IDB_STORE);\n for (const key of keys) store.delete(recordId(scope, key));\n tx.oncomplete = () => { resolve(); };\n tx.onerror = () => { reject(tx.error ?? new Error('IndexedDB delete failed')); };\n tx.onabort = () => { reject(tx.error ?? new Error('IndexedDB transaction aborted')); };\n });\n } catch {\n // swallow \u2014 best-effort\n } finally {\n db?.close();\n }\n }\n\n async clearScope(scope: string): Promise<void> {\n let db: any = null;\n try {\n db = await openCacheDB();\n await new Promise<void>((resolve, reject) => {\n const tx = db!.transaction(SDK_CACHE_IDB_STORE, 'readwrite');\n const range = (globalThis as any).IDBKeyRange.bound(\n scope + SCOPE_SEP,\n scope + SCOPE_SEP + SCOPE_RANGE_END,\n );\n tx.objectStore(SDK_CACHE_IDB_STORE).delete(range);\n tx.oncomplete = () => { resolve(); };\n tx.onerror = () => { reject(tx.error ?? new Error('IndexedDB clear failed')); };\n tx.onabort = () => { reject(tx.error ?? new Error('IndexedDB transaction aborted')); };\n });\n } catch {\n // swallow \u2014 best-effort\n } finally {\n db?.close();\n }\n }\n}\n\n/* eslint-enable @typescript-eslint/no-unsafe-call,\n @typescript-eslint/no-unsafe-member-access,\n @typescript-eslint/prefer-promise-reject-errors,\n @typescript-eslint/no-unsafe-assignment,\n @typescript-eslint/no-unsafe-argument,\n @typescript-eslint/no-explicit-any */\n\n/**\n * Construct a cache persistence backend for the current runtime, or `null`\n * when IndexedDB is unavailable (Node, non-secure contexts) \u2014 callers then\n * keep the pure in-memory cache.\n */\nexport function createCachePersistence(): CachePersistence | null {\n return cachePersistenceSupported ? new IdbCachePersistence() : null;\n}\n", "/**\n * Copyright (c) 2026 Geolonia Inc.\n * Released under the MIT License. See src/sdk/LICENSE for details.\n */\n\n/**\n * Web Push (VAPID) notification helpers (#3014 / #3060).\n *\n * Pure conversion functions between the browser Push API and the\n * NGSIv2 `notification.webpush` / NGSI-LD `endpoint.webpush` shapes\n * documented in docs/SUBSCRIPTIONS.md. These do not touch the network \u2014\n * see `GeonicDB.getWebPushVapidKey()` / `subscribeWebPush()` /\n * `registerWebPushSubscription()` / `unsubscribeWebPush()` in `./index`\n * for the browser-facing API.\n */\n\nimport { WEBPUSH_SUBSCRIPTION_ID_STORAGE_PREFIX } from './constants';\n\n/** `PushSubscription.toJSON().keys` shape (RFC 8291). */\nexport interface WebPushKeys {\n p256dh: string;\n auth: string;\n}\n\n/** RFC 8030 delivery options, shared by both NGSI protocol shapes. */\nexport interface WebPushDeliveryOptions {\n /** Push message TTL in seconds. */\n ttl?: number;\n urgency?: 'very-low' | 'low' | 'normal' | 'high';\n topic?: string;\n}\n\n/** Options for `GeonicDB.subscribeWebPush()`. */\nexport interface WebPushSubscribeOptions {\n /**\n * VAPID public key (base64url). Auto-fetched from\n * `GET /.well-known/webpush-vapid-key` when omitted.\n */\n applicationServerKey?: string;\n /**\n * Service worker registration to subscribe through. Defaults to\n * `navigator.serviceWorker.ready` \u2014 the caller must have already\n * registered a service worker (e.g. `navigator.serviceWorker.register('/sw.js')`).\n */\n serviceWorkerRegistration?: ServiceWorkerRegistration;\n}\n\n/** Which NGSI protocol `registerWebPushSubscription()` should talk to (#3092). */\nexport type WebPushRegisterProtocol = 'ngsiv2' | 'ngsi-ld';\n\n/** Entity selector shared by NGSIv2 `subject.entities` and NGSI-LD `entities`. */\nexport interface WebPushRegisterEntitySelector {\n id?: string;\n idPattern?: string;\n type?: string;\n}\n\n/** NGSIv2 subscription `subject` accepted by `registerWebPushSubscription()`. */\nexport interface WebPushRegisterNgsiv2Subject {\n entities: WebPushRegisterEntitySelector[];\n condition?: {\n attrs?: string[];\n expression?: string;\n };\n}\n\n/**\n * Result of `GeonicDB.registerWebPushSubscription()` (#3092).\n * `created` is `true` only when a new GeonicDB subscription was POSTed;\n * reuse (and VAPID-rotation PATCH) returns `created: false`.\n */\nexport interface RegisterWebPushSubscriptionResult {\n subscriptionId: string;\n created: boolean;\n}\n\n/**\n * Options for `GeonicDB.registerWebPushSubscription()` (#3092).\n *\n * `protocol` is required with no default \u2014 the SDK already exposes separate\n * NGSIv2 / NGSI-LD converters, and an implicit default would silently register\n * against the wrong API.\n */\nexport interface RegisterWebPushSubscriptionOptions\n extends WebPushSubscribeOptions, WebPushDeliveryOptions {\n protocol: WebPushRegisterProtocol;\n /** Subscription description. Defaults to `'Web Push notification'`. */\n description?: string;\n /** Required when `protocol` is `'ngsiv2'`. */\n subject?: WebPushRegisterNgsiv2Subject;\n /** Required when `protocol` is `'ngsi-ld'`. */\n entities?: WebPushRegisterEntitySelector[];\n /** NGSI-LD `watchedAttributes` (ignored for NGSIv2 \u2014 use `subject.condition.attrs`). */\n watchedAttributes?: string[];\n /** NGSI-LD `q` filter (ignored for NGSIv2). */\n q?: string;\n /**\n * Override the localStorage key used for idempotency. Default includes\n * baseUrl + tenant + protocol.\n */\n storageKey?: string;\n}\n\n/**\n * Build the default localStorage key for a Web Push GeonicDB subscription id.\n * Exported for tests and for callers that want to clear the persisted id.\n */\nexport function buildWebPushSubscriptionStorageKey(\n baseUrl: string,\n tenant: string,\n protocol: WebPushRegisterProtocol,\n): string {\n return (\n WEBPUSH_SUBSCRIPTION_ID_STORAGE_PREFIX +\n ':' +\n encodeURIComponent(baseUrl) +\n ':' +\n encodeURIComponent(tenant) +\n ':' +\n protocol\n );\n}\n\n/**\n * Accepted by all conversion functions: either a `PushSubscription.toJSON()`\n * plain-object shape (`{ endpoint, keys: { p256dh, auth } }`), or a native\n * `PushSubscription` instance, which has no `keys` property and exposes the\n * raw key material via `getKey()` instead (RFC 8291).\n */\nexport interface WebPushSubscriptionLike {\n endpoint?: string;\n keys?: Partial<WebPushKeys>;\n getKey?: (name: 'p256dh' | 'auth') => ArrayBuffer | null;\n}\n\nfunction arrayBufferToBase64Url(buf: ArrayBuffer): string {\n const bytes = new Uint8Array(buf);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nfunction extractKeys(subscription: WebPushSubscriptionLike): {\n endpoint: string;\n keys: WebPushKeys;\n} {\n const endpoint = subscription.endpoint;\n let p256dh = subscription.keys?.p256dh;\n let auth = subscription.keys?.auth;\n // Native PushSubscription instances (e.g. returned by\n // GeonicDB.subscribeWebPush()) have no `keys` property \u2014 only\n // `getKey(name)`, returning the raw key as an ArrayBuffer.\n if ((!p256dh || !auth) && typeof subscription.getKey === 'function') {\n const p256dhBuf = subscription.getKey('p256dh');\n const authBuf = subscription.getKey('auth');\n if (!p256dh && p256dhBuf) p256dh = arrayBufferToBase64Url(p256dhBuf);\n if (!auth && authBuf) auth = arrayBufferToBase64Url(authBuf);\n }\n if (!endpoint || !p256dh || !auth) {\n throw new Error(\n 'Invalid PushSubscription: endpoint, keys.p256dh, and keys.auth are all required',\n );\n }\n return { endpoint, keys: { p256dh, auth } };\n}\n\n/**\n * Convert a browser `PushSubscription` (or its `.toJSON()` output) into the\n * NGSIv2 `notification.webpush` shape (#3014).\n *\n * @example\n * ```ts\n * // Prefer the idempotent high-level helper (#3092):\n * await db.registerWebPushSubscription({\n * protocol: 'ngsiv2',\n * subject: { entities: [{ type: 'Sensor' }], condition: { attrs: ['value'] } },\n * ttl: 86400,\n * });\n *\n * // Or convert manually:\n * const subscription = await db.subscribeWebPush();\n * const webpush = toNgsiv2WebPushNotification(subscription, { ttl: 86400 });\n * await db.request('POST', '/v2/subscriptions', {\n * description: 'Web Push notification',\n * subject: { entities: [{ type: 'Sensor' }], condition: { attrs: ['value'] } },\n * notification: { webpush },\n * });\n * ```\n */\nexport function toNgsiv2WebPushNotification(\n subscription: WebPushSubscriptionLike,\n options?: WebPushDeliveryOptions,\n): { url: string; keys: WebPushKeys } & WebPushDeliveryOptions {\n const { endpoint, keys } = extractKeys(subscription);\n return { url: endpoint, keys, ...options };\n}\n\n/**\n * Convert a browser `PushSubscription` (or its `.toJSON()` output) into the\n * NGSI-LD `endpoint.webpush` shape (#3014, GeonicDB extension). The\n * returned object is meant to be spread into `notification.endpoint`\n * alongside `uri`.\n *\n * @example\n * ```ts\n * await db.registerWebPushSubscription({\n * protocol: 'ngsi-ld',\n * entities: [{ type: 'Sensor' }],\n * watchedAttributes: ['value'],\n * });\n *\n * // Or convert manually:\n * const subscription = await db.subscribeWebPush();\n * const { uri, webpush } = toNgsiLdWebPushEndpoint(subscription);\n * await db.request('POST', '/ngsi-ld/v1/subscriptions', {\n * type: 'Subscription',\n * entities: [{ type: 'Sensor' }],\n * notification: { endpoint: { uri, accept: 'application/json', webpush } },\n * });\n * ```\n */\nexport function toNgsiLdWebPushEndpoint(\n subscription: WebPushSubscriptionLike,\n options?: WebPushDeliveryOptions,\n): { uri: string; webpush: { keys: WebPushKeys } & WebPushDeliveryOptions } {\n const { endpoint, keys } = extractKeys(subscription);\n return { uri: endpoint, webpush: { keys, ...options } };\n}\n\n/**\n * Convert a base64url-encoded VAPID public key (as returned by\n * `GET /.well-known/webpush-vapid-key`) into the `Uint8Array` expected by\n * `PushManager.subscribe({ applicationServerKey })`.\n */\nexport function urlBase64ToUint8Array(base64Url: string): Uint8Array<ArrayBuffer> {\n const padding = '='.repeat((4 - (base64Url.length % 4)) % 4);\n const base64 = (base64Url + padding).replace(/-/g, '+').replace(/_/g, '/');\n const raw = atob(base64);\n const output = new Uint8Array(raw.length);\n for (let i = 0; i < raw.length; i++) {\n output[i] = raw.charCodeAt(i);\n }\n return output;\n}\n\n/**\n * Whether an existing `PushSubscription` was created with the given VAPID\n * public key. Used by `GeonicDB.subscribeWebPush()` to detect a stale\n * subscription left over from before a VAPID key rotation \u2014 reusing it would\n * otherwise fail silently (the push service rejects deliveries signed with\n * the new key, with nothing surfaced to the SDK caller).\n *\n * Returns `true` (assume match, keep the existing subscription) when the key\n * can't be read \u2014 some older Push API implementations don't expose\n * `PushSubscription.options`.\n */\nexport function matchesApplicationServerKey(\n subscription: { options?: { applicationServerKey?: ArrayBuffer | null } | null },\n desiredKey: Uint8Array,\n): boolean {\n const existingKey = subscription.options?.applicationServerKey;\n if (!existingKey) return true;\n const existingBytes = new Uint8Array(existingKey);\n if (existingBytes.length !== desiredKey.length) return false;\n for (let i = 0; i < existingBytes.length; i++) {\n if (existingBytes[i] !== desiredKey[i]) return false;\n }\n return true;\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACYO,IAAM,eAAN,MAAmB;AAAA,EAAnB;AACL,wBAAQ,cACN,uBAAO,OAAO,IAAI;AAAA;AAAA,EAOpB,GAAG,OAAe,UAAmC;AACnD,QAAI,CAAC,KAAK,WAAW,KAAK,GAAG;AAC3B,WAAK,WAAW,KAAK,IAAI,CAAC;AAAA,IAC5B;AACA,SAAK,WAAW,KAAK,EAAE,KAAK,QAAQ;AACpC,WAAO;AAAA,EACT;AAAA,EAOA,IAAI,OAAe,UAAoC;AACrD,QAAI,CAAC,KAAK,WAAW,KAAK,EAAG,QAAO;AACpC,QAAI,CAAC,UAAU;AACb,aAAO,KAAK,WAAW,KAAK;AAAA,IAC9B,OAAO;AACL,WAAK,WAAW,KAAK,IAAI,KAAK,WAAW,KAAK,EAAE;AAAA,QAC9C,CAAC,OAAO,OAAO;AAAA,MACjB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAOU,KAAK,OAAe,MAAsB;AAClD,UAAM,MAAM,KAAK,WAAW,KAAK;AACjC,QAAI,CAAC,IAAK;AACV,UAAM,WAAW,IAAI,MAAM;AAC3B,eAAW,MAAM,UAAU;AACzB,UAAI;AACF,WAAG,IAAI;AAAA,MACT,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;;;ACzDO,IAAM,wBAAwB;AAG9B,IAAM,0BAA0B;AAGhC,IAAM,uBAAuB;AAG7B,IAAM,yBAAyB;AAG/B,IAAM,oBAAoB;AAG1B,IAAM,yBAAyB;AAG/B,IAAM,eAAe;AAOrB,IAAM,uBACX;AAQK,IAAM,gCAAgC;AAGtC,IAAM,+BAA+B;AAUrC,IAAM,gBAAgB;AAGtB,IAAM,iBAAiB;AAGvB,IAAM,mBAAmB;AAgBzB,IAAM,qBAAqB;AAG3B,IAAM,sBAAsB;AAG5B,IAAM,wBAAwB;AAa9B,IAAM,yBAAyB;AAQ/B,IAAM,yCAAyC;;;AC7F/C,IAAM,gBACX,OAAO,WAAW,eAClB,CAAC,CAAC,OAAO,UACT,CAAC,CAAC,OAAO,OAAO;AAQX,IAAM,2BACX,OAAQ,WAAuC,cAAc,eAC7D,OAAO,oBAAoB;AAI7B,SAAS,OAAO,KAAuC;AACrD,QAAM,MAAM,eAAe,cAAc,IAAI,WAAW,GAAG,IAAI;AAC/D,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,WAAO,OAAO,aAAa,IAAI,CAAC,CAAC;AAAA,EACnC;AACA,SAAO,KAAK,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE;AAC5E;AAGA,SAAS,WAAW,GAAuB;AACzC,SAAO,IAAI,YAAY,EAAE,OAAO,CAAC;AACnC;AAKA,eAAsB,sBAAmD;AACvE,MAAI,CAAC,cAAe,QAAO;AAC3B,QAAM,KAAK,MAAM,OAAO,OAAO;AAAA,IAC7B,EAAE,MAAM,SAAS,YAAY,QAAQ;AAAA,IACrC;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AACA,QAAM,SAAS,MAAM,OAAO,OAAO;AAAA,IACjC;AAAA,IACA,GAAG;AAAA,EACL;AACA,SAAO;AAAA,IACL,YAAY,GAAG;AAAA,IACf,WAAW;AAAA,MACT,KAAK,OAAO;AAAA,MACZ,KAAK,OAAO;AAAA,MACZ,GAAG,OAAO;AAAA,MACV,GAAG,OAAO;AAAA,IACZ;AAAA,EACF;AACF;AAaA,eAAsB,gBACpB,SACA,KACA,KACA,aACA,OACwB;AACxB,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,SAAS,EAAE,KAAK,SAAS,KAAK,YAAY,KAAK,QAAQ,UAAU;AACvE,QAAM,UAAmC;AAAA,IACvC,KACE,OAAO,OAAO,eAAe,aACzB,OAAO,WAAW,IAClB,KAAK,IAAI,EAAE,SAAS,EAAE,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,CAAC;AAAA,IACnE;AAAA,IACA;AAAA,IACA,KAAK,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,EACnC;AACA,MAAI,MAAO,SAAQ,QAAQ;AAE3B,MAAI,aAAa;AAEf,UAAM,OAAO,MAAM,OAAO,OAAO,OAAO,WAAW,WAAW,WAAW,CAA2B;AACpG,YAAQ,MAAM,OAAO,IAAI;AAAA,EAC3B;AAEA,QAAM,YAAY,OAAO,WAAW,KAAK,UAAU,MAAM,CAAC,CAAC;AAC3D,QAAM,aAAa,OAAO,WAAW,KAAK,UAAU,OAAO,CAAC,CAAC;AAC7D,QAAM,eAAe,YAAY,MAAM;AAGvC,QAAM,SAAS,MAAM,OAAO,OAAO;AAAA,IACjC,EAAE,MAAM,SAAS,MAAM,UAAU;AAAA,IACjC,QAAQ;AAAA,IACR,WAAW,YAAY;AAAA,EACzB;AAGA,SAAO,eAAe,MAAM,OAAO,IAAI,WAAW,MAAM,CAAC;AAC3D;AAkCA,SAAS,gBAA8B;AAErC,SAAO,IAAI,QAAa,CAAC,SAAS,WAAW;AAE3C,UAAM,MAAY,WAAmB,UAAU,KAAK,eAAe,gBAAgB;AACnF,QAAI,kBAAkB,MAAM;AAC1B,YAAM,KAAK,IAAI;AACf,UAAI,CAAC,GAAG,iBAAiB,SAAS,cAAc,GAAG;AACjD,WAAG,kBAAkB,cAAc;AAAA,MACrC;AAAA,IACF;AACA,QAAI,YAAY,MAAM;AAAE,cAAQ,IAAI,MAAM;AAAA,IAAG;AAC7C,QAAI,UAAU,MAAM;AAAE,aAAO,IAAI,SAAS,IAAI,MAAM,uBAAuB,CAAC;AAAA,IAAG;AAAA,EACjF,CAAC;AACH;AAUA,eAAsB,gBACpB,SACkB;AAClB,MAAI,CAAC,yBAA0B,QAAO;AAKtC,MAAI,KAAU;AACd,MAAI;AACF,SAAK,MAAM,cAAc;AACzB,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,YAAM,KAAK,GAAI,YAAY,gBAAgB,WAAW;AACtD,SAAG,YAAY,cAAc,EAAE,IAAI,SAAS,QAAQ,MAAM;AAC1D,SAAG,aAAa,MAAM;AAAE,gBAAQ;AAAA,MAAG;AACnC,SAAG,UAAU,MAAM;AAAE,eAAO,GAAG,SAAS,IAAI,MAAM,sBAAsB,CAAC;AAAA,MAAG;AAC5E,SAAG,UAAU,MAAM;AAAE,eAAO,GAAG,SAAS,IAAI,MAAM,+BAA+B,CAAC;AAAA,MAAG;AAAA,IACvF,CAAC;AACD,WAAO;AAAA,EACT,QAAQ;AAIN,WAAO;AAAA,EACT,UAAE;AACA,QAAI,MAAM;AAAA,EACZ;AACF;AASA,eAAsB,gBACpB,QACsC;AACtC,MAAI,CAAC,yBAA0B,QAAO;AAKtC,MAAI,KAAU;AACd,MAAI;AACF,SAAK,MAAM,cAAc;AACzB,UAAM,OAAO,MAAM,IAAI,QAAiB,CAAC,SAAS,WAAW;AAC3D,YAAM,KAAK,GAAI,YAAY,gBAAgB,UAAU;AACrD,YAAM,MAAM,GAAG,YAAY,cAAc,EAAE,IAAI,MAAM;AACrD,UAAI,YAAY,MAAM;AAAE,gBAAQ,IAAI,MAAM;AAAA,MAAG;AAC7C,UAAI,UAAU,MAAM;AAAE,eAAO,IAAI,SAAS,IAAI,MAAM,sBAAsB,CAAC;AAAA,MAAG;AAAA,IAChF,CAAC;AACD,WAAO,yBAAyB,IAAI;AAAA,EACtC,QAAQ;AAIN,WAAO;AAAA,EACT,UAAE;AACA,QAAI,MAAM;AAAA,EACZ;AACF;AAMA,eAAsB,iBAAiB,QAA+B;AACpE,MAAI,CAAC,yBAA0B;AAK/B,MAAI,KAAU;AACd,MAAI;AACF,SAAK,MAAM,cAAc;AACzB,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,YAAM,KAAK,GAAI,YAAY,gBAAgB,WAAW;AACtD,SAAG,YAAY,cAAc,EAAE,OAAO,MAAM;AAC5C,SAAG,aAAa,MAAM;AAAE,gBAAQ;AAAA,MAAG;AACnC,SAAG,UAAU,MAAM;AAAE,eAAO,GAAG,SAAS,IAAI,MAAM,yBAAyB,CAAC;AAAA,MAAG;AAC/E,SAAG,UAAU,MAAM;AAAE,eAAO,GAAG,SAAS,IAAI,MAAM,+BAA+B,CAAC;AAAA,MAAG;AAAA,IACvF,CAAC;AAAA,EACH,QAAQ;AAAA,EAER,UAAE;AACA,QAAI,MAAM;AAAA,EACZ;AACF;AAEA,SAAS,yBAAyB,MAA4C;AAC5E,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,QAAM,IAAI;AACV,QAAM,KAAK,EAAE;AACb,MACE,OAAO,EAAE,WAAW,YACpB,OAAO,EAAE,gBAAgB,YACzB,OAAO,EAAE,iBAAiB,YAC1B,OAAO,EAAE,cAAc,YACvB,CAAC,MACD,CAAC,GAAG,cACJ,CAAC,GAAG,WACJ;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;;;ACpRA,IAAM,aAAa;AACnB,IAAM,iBAAiB;AAGvB,SAAS,WAAW,MAAkB,MAAuB;AAC3D,QAAM,YAAY,KAAK,MAAM,OAAO,CAAC;AACrC,QAAM,aAAa,OAAO;AAC1B,WAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,QAAI,KAAK,CAAC,MAAM,EAAG,QAAO;AAAA,EAC5B;AACA,MAAI,aAAa,GAAG;AAClB,UAAM,OAAO,OAAS,IAAI;AAC1B,SAAK,KAAK,SAAS,IAAI,UAAU,EAAG,QAAO;AAAA,EAC7C;AACA,SAAO;AACT;AAYA,eAAsB,SACpB,WACA,YACiB;AACjB,MAAI,CAAC,OAAO,UAAU,UAAU,KAAK,aAAa,KAAK,aAAa,KAAK;AACvE,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AACA,QAAM,UAAU,IAAI,YAAY;AAChC,MAAI,IAAI;AAER,iBAAe,WAA4B;AACzC,UAAM,WAAW,KAAK,IAAI,IAAI,YAAY,cAAc;AACxD,UAAM,WAAyD,CAAC;AAEhE,WAAO,IAAI,UAAU,KAAK;AACxB,YAAM,MAAM;AACZ,eAAS;AAAA,QACP,OAAO,OACJ,OAAO,WAAW,QAAQ,OAAO,YAAY,OAAO,GAAG,CAAC,CAAC,EACzD,KAAK,CAAC,SAAS,EAAE,KAAK,MAAM,IAAI,WAAW,GAAG,EAAE,EAAE;AAAA,MACvD;AAAA,IACF;AAEA,UAAM,UAAU,MAAM,QAAQ,IAAI,QAAQ;AAC1C,eAAW,UAAU,SAAS;AAC5B,UAAI,WAAW,OAAO,MAAM,UAAU,GAAG;AACvC,eAAO,OAAO;AAAA,MAChB;AAAA,IACF;AAKA,QAAI,KAAK,eAAgB,OAAM,IAAI,MAAM,wBAAwB;AAIjE,WAAO,SAAS;AAAA,EAClB;AAEA,SAAO,SAAS;AAClB;;;AC3CO,IAAM,gBAAN,cAA4B,MAAM;AAAA,EAOvC,YAAY,SAAiB,aAAa,GAAG;AAC3C,UAAM,OAAO;AANf;AAAA,wBAAS;AAGT;AAAA,wBAAS;AAIP,SAAK,OAAO;AACZ,SAAK,aAAa;AAAA,EACpB;AACF;AAOO,SAAS,YAAqC,OAAU,SAA4B;AACzF,EAAC,MAAuC,UAAU;AAClD,QAAM,UAAU,IAAI,QAAQ,MAAM,IAAI,QAAQ,IAAI,KAAK,MAAM,OAAO;AACpE,SAAO;AACT;AAQO,IAAM,sBAAN,cAAkC,cAAc;AAAA,EACrD,YAAY,UAAU,yBAAyB,aAAa,KAAK;AAC/D,UAAM,SAAS,UAAU;AACzB,SAAK,OAAO;AAAA,EACd;AACF;AAGO,IAAM,qBAAN,cAAiC,cAAc;AAAA,EACpD,YAAY,UAAU,iBAAiB;AACrC,UAAM,SAAS,GAAG;AAClB,SAAK,OAAO;AAAA,EACd;AACF;AAGO,IAAM,gBAAN,cAA4B,cAAc;AAAA,EAC/C,YAAY,UAAU,aAAa;AACjC,UAAM,SAAS,GAAG;AAClB,SAAK,OAAO;AAAA,EACd;AACF;AAGO,IAAM,gBAAN,cAA4B,cAAc;AAAA,EAC/C,YAAY,UAAU,YAAY;AAChC,UAAM,SAAS,GAAG;AAClB,SAAK,OAAO;AAAA,EACd;AACF;AAOO,IAAM,6BAAN,cAAyC,oBAAoB;AAAA,EAClE,YAAY,UAAU,iEAAiE;AACrF,UAAM,SAAS,GAAG;AAClB,SAAK,OAAO;AAAA,EACd;AACF;AAKO,IAAM,gCAAN,cAA4C,oBAAoB;AAAA,EACrE,YAAY,UAAU,mEAAmE;AACvF,UAAM,SAAS,GAAG;AAClB,SAAK,OAAO;AAAA,EACd;AACF;AAGO,IAAM,kBAAN,cAA8B,cAAc;AAAA,EACjD,YAAY,UAAU,qBAAqB;AACzC,UAAM,SAAS,GAAG;AAClB,SAAK,OAAO;AAAA,EACd;AACF;AAGO,IAAM,iBAAN,cAA6B,cAAc;AAAA,EAIhD,YAAY,UAAU,uBAAuB,aAAa,GAAG;AAC3D,UAAM,SAAS,GAAG;AAHpB;AAAA,wBAAS;AAIP,SAAK,OAAO;AACZ,SAAK,aAAa;AAAA,EACpB;AACF;AAQO,IAAM,qBAAN,cAAiC,cAAc;AAAA,EACpD,YAAY,UAAU,kBAAkB;AACtC,UAAM,SAAS,GAAG;AAClB,SAAK,OAAO;AAAA,EACd;AACF;AAGO,IAAM,eAAN,cAA2B,cAAc;AAAA,EAC9C,YAAY,UAAU,iBAAiB;AACrC,UAAM,SAAS,CAAC;AAChB,SAAK,OAAO;AAAA,EACd;AACF;AAMO,SAAS,wBACd,QACA,MACA,iBACe;AACf,QAAM,UAAU,KAAK,UAAU,KAAK,eAAe;AACnD,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO,IAAI,oBAAoB,OAAO;AAAA,IACxC,KAAK;AACH,aAAO,IAAI,mBAAmB,OAAO;AAAA,IACvC,KAAK;AACH,aAAO,IAAI,cAAc,OAAO;AAAA,IAClC,KAAK;AACH,aAAO,IAAI,cAAc,OAAO;AAAA,IAClC,KAAK;AACH,aAAO,IAAI,gBAAgB,OAAO;AAAA,IACpC,KAAK;AACH,aAAO,IAAI,eAAe,OAAO;AAAA,IACnC,KAAK;AACH,aAAO,IAAI,mBAAmB,OAAO;AAAA,IACvC;AACE,aAAO,IAAI,cAAc,SAAS,MAAM;AAAA,EAC5C;AACF;;;AC/GO,IAAM,WAAN,MAAe;AAAA,EAiCpB,YAAY,aAAqB,+BAA+B;AAhChE,wBAAiB,UAAS,oBAAI,IAAwB;AACtD,wBAAiB;AAOjB;AAAA;AAAA;AAAA;AAAA;AAAA,wBAAQ,gBAAwC;AAOhD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAAQ,UAAS;AAMjB;AAAA;AAAA;AAAA;AAAA;AAAA,wBAAQ,mBAAkB;AAS1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAAQ,iBAA+B,QAAQ,QAAQ;AAGrD,QAAI,CAAC,OAAO,UAAU,UAAU,KAAK,cAAc,GAAG;AACpD,YAAM,IAAI,MAAM,uDAAuD,OAAO,UAAU,CAAC,EAAE;AAAA,IAC7F;AACA,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA,EAGA,kBAAkB,aAAqC;AACrD,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA,EAGA,iBAA0B;AACxB,WAAO,KAAK,iBAAiB;AAAA,EAC/B;AAAA;AAAA,EAGA,SAAS,OAAqB;AAC5B,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAGA,kBAAkB,SAAwB;AACxC,SAAK,kBAAkB,WAAW,KAAK,iBAAiB;AAAA,EAC1D;AAAA;AAAA,EAGA,IAAY,cAAuB;AACjC,WAAO,KAAK,mBAAmB,KAAK,iBAAiB;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,gBACN,IACe;AACf,UAAM,cAAc,KAAK;AACzB,QAAI,CAAC,YAAa,QAAO,KAAK;AAC9B,UAAM,QAAQ,KAAK;AACnB,SAAK,gBAAgB,KAAK,cACvB,MAAM,MAAM,MAAS,EACrB,KAAK,MAAM,GAAG,aAAa,KAAK,CAAC,EACjC,MAAM,MAAM,MAAS;AACxB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,UAAyB;AAC7B,QAAI,CAAC,KAAK,aAAc;AACxB,UAAM,UAAU,MAAM,KAAK,aAAa,UAAU,KAAK,QAAQ,KAAK,WAAW;AAC/E,eAAW,EAAE,KAAK,MAAM,KAAK,SAAS;AACpC,UAAI,KAAK,OAAO,IAAI,GAAG,EAAG,MAAK,OAAO,OAAO,GAAG;AAChD,WAAK,OAAO,IAAI,KAAK,KAAK;AAAA,IAC5B;AAGA,WAAO,KAAK,OAAO,OAAO,KAAK,aAAa;AAC1C,YAAM,SAAS,KAAK,OAAO,KAAK,EAAE,KAAK,EAAE;AAEzC,UAAI,WAAW,OAAW;AAC1B,WAAK,OAAO,OAAO,MAAM;AAAA,IAC3B;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,iBAAgC;AACpC,QAAI,CAAC,KAAK,aAAc;AACxB,UAAM,KAAK,gBAAgB,CAAC,aAAa,UAAU,YAAY,WAAW,KAAK,CAAC;AAAA,EAClF;AAAA;AAAA,EAGA,OAAO,OAAO,QAAgB,MAAsB;AAClD,WAAO,GAAG,OAAO,YAAY,CAAC,IAAI,IAAI;AAAA,EACxC;AAAA;AAAA,EAGA,IAAI,KAAqC;AACvC,UAAM,QAAQ,KAAK,OAAO,IAAI,GAAG;AACjC,QAAI,CAAC,MAAO,QAAO;AAEnB,SAAK,OAAO,OAAO,GAAG;AACtB,SAAK,OAAO,IAAI,KAAK,KAAK;AAC1B,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,IAAI,KAAa,OAAyB;AACxC,QAAI,KAAK,OAAO,IAAI,GAAG,GAAG;AACxB,WAAK,OAAO,OAAO,GAAG;AAAA,IACxB;AACA,SAAK,OAAO,IAAI,KAAK,KAAK;AAC1B,UAAM,UAAoB,CAAC;AAC3B,WAAO,KAAK,OAAO,OAAO,KAAK,aAAa;AAC1C,YAAM,SAAS,KAAK,OAAO,KAAK,EAAE,KAAK,EAAE;AAEzC,UAAI,WAAW,OAAW;AAC1B,WAAK,OAAO,OAAO,MAAM;AACzB,cAAQ,KAAK,MAAM;AAAA,IACrB;AAIA,QAAI,KAAK,aAAa;AACpB,WAAK,KAAK,gBAAgB,OAAO,aAAa,UAAU;AACtD,cAAM,YAAY,IAAI,OAAO,KAAK,KAAK;AACvC,YAAI,QAAQ,SAAS,EAAG,OAAM,YAAY,OAAO,OAAO,OAAO;AAAA,MACjE,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA,EAGA,OAAO,KAAsB;AAC3B,UAAM,UAAU,KAAK,OAAO,OAAO,GAAG;AACtC,QAAI,WAAW,KAAK,aAAa;AAC/B,WAAK,KAAK,gBAAgB,CAAC,aAAa,UAAU,YAAY,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;AAAA,IACpF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,WAAkE;AAC5E,UAAM,UAAoB,CAAC;AAC3B,eAAW,CAAC,KAAK,KAAK,KAAK,KAAK,QAAQ;AACtC,UAAI,UAAU,KAAK,KAAK,GAAG;AACzB,aAAK,OAAO,OAAO,GAAG;AACtB,gBAAQ,KAAK,GAAG;AAAA,MAClB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,OAAO,MAAM;AAAA,EACpB;AAAA;AAAA,EAGA,OAAe;AACb,WAAO,KAAK,OAAO;AAAA,EACrB;AACF;AAOA,IAAM,qBAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,gBAAgB,SAA0C;AACxE,QAAM,MAA8B,CAAC;AACrC,aAAW,QAAQ,oBAAoB;AACrC,UAAM,QAAQ,QAAQ,IAAI,IAAI;AAC9B,QAAI,UAAU,KAAM,KAAI,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AAGO,SAAS,kBAAkB,QAAyB;AACzD,QAAM,QAAQ,OAAO,YAAY;AACjC,SAAO,UAAU,SAAS,UAAU;AACtC;;;ACnQO,IAAM,eAAN,MAAM,aAAY;AAAA,EAoEvB,YACE,SACA,QACA,QACA,QAAQ,OACR,YAAY,OACZ;AAzEF,wBAAQ;AAOR,wBAAQ;AACR,wBAAQ;AAOR;AAeA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAAiB;AACjB,wBAAQ;AAER,kCAAwB;AACxB,wCAAe;AACf,sCAAgC;AAChC,yCAA+B;AAC/B,wCAAmC;AACnC,sCAAmC;AACnC,sCAA4B;AAO5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4CAAmB;AAEnB;AAAA,kCAAwB;AACxB,wBAAQ,iBAAwC;AAGhD;AAAA,0CAAiE;AAOjE;AAAA;AAAA;AAAA;AAAA;AAAA,wBAAQ,UAA0B;AAGlC;AAAA,wBAAQ,mBAAgF;AAGxF;AAAA,wBAAiB,aAAY,oBAAI,IAA+B;AAS9D,QAAI,aAAa,QAAQ;AACvB,YAAM,IAAI;AAAA,QACR;AAAA,MAEF;AAAA,IACF;AACA,SAAK,WAAW;AAChB,SAAK,UAAU;AACf,SAAK,UAAU;AACf,SAAK,SAAS;AACd,SAAK,oBAAoB;AACzB,SAAK,aAAa;AAKlB,QAAI,CAAC,aAAa,eAAe;AAC/B,WAAK,aAAa,oBAAoB,EACnC,KAAK,CAAC,OAAO;AAKZ,YAAI,CAAC,KAAK,aAAc,MAAK,eAAe;AAAA,MAC9C,CAAC,EACA,MAAM,MAAM;AACX,YAAI,CAAC,KAAK,aAAc,MAAK,eAAe;AAAA,MAC9C,CAAC;AAAA,IACL;AAAA,EACF;AAAA;AAAA,EApGA,IAAI,UAAkB;AACpB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAMA,IAAI,SAAiB;AACnB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmGA,cAAuB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,QAAQ,MAAuB;AACrC,QAAI,KAAK,OAAQ,SAAQ,IAAI,cAAc,GAAG,IAAI;AAAA,EACpD;AAAA;AAAA,EAGA,SAAS,OAA8B;AACrC,SAAK,SAAS;AAGd,WAAO,SAAS,KAAK,YAAY,CAAC;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYQ,cAAsB;AAC5B,WAAO,GAAG,KAAK,OAAO,IAAS,KAAK,UAAU,EAAE;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,uBAAuB,YAAoC;AACvE,QAAI,CAAC,KAAK,OAAQ;AAClB,SAAK,OAAO,SAAS,KAAK,YAAY,CAAC;AACvC,QAAI,WAAY,OAAM,KAAK,OAAO,eAAe;AACjD,SAAK,OAAO,kBAAkB,IAAI;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,0BAAgC;AACtC,QAAI,CAAC,KAAK,OAAQ;AAClB,SAAK,OAAO,SAAS,KAAK,YAAY,CAAC;AACvC,SAAK,OAAO,kBAAkB,KAAK;AACnC,SAAK,KAAK,OAAO,eAAe;AAAA,EAClC;AAAA;AAAA,EAGA,qBACE,SACM;AACN,SAAK,kBAAkB;AAAA,EACzB;AAAA;AAAA,EAGA,WAA4B;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,eAAe,MAAsB,SAA2B;AAC9D,SAAK,kBAAkB,MAAM,OAAO;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,MACJ,OACA,UACA,SACwB;AACxB,SAAK,KAAK,SAAS,KAAK;AAExB,UAAM,gBAAgB,SAAS,oBAAoB;AAEnD,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,IAClB;AACA,QAAI,KAAK,QAAS,SAAQ,eAAe,IAAI,KAAK;AAElD,UAAM,YAAoC,EAAE,OAAO,SAAS;AAE5D,QAAI,SAAS,gBAAgB,OAAW,WAAU,cAAc,QAAQ;AAExE,UAAM,MAAM,MAAM,MAAM,KAAK,WAAW,eAAe;AAAA,MACrD,QAAQ;AAAA,MACR;AAAA,MACA,MAAM,KAAK,UAAU,SAAS;AAAA,IAChC,CAAC;AACD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC3C,YAAM,UAAU,EAAE,UAAU,EAAE,eAAe,mBAAmB,IAAI;AAIpE,YAAM,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS;AAC7C,UAAI,IAAI,WAAW,OAAO,KAAK,SAAS,uBAAuB,GAAG;AAChE,cAAM,YAAY,IAAI,2BAA2B,OAAO,GAAG;AAAA,UACzD,QAAQ;AAAA,UACR,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AACA,UAAI,IAAI,WAAW,OAAO,KAAK,SAAS,0BAA0B,GAAG;AACnE,cAAM,YAAY,IAAI,8BAA8B,OAAO,GAAG;AAAA,UAC5D,QAAQ;AAAA,UACR,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AACA,YAAM,YAAY,IAAI,oBAAoB,SAAS,IAAI,MAAM,GAAG;AAAA,QAC9D,QAAQ;AAAA,QACR,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AACA,UAAM,OAAQ,MAAM,IAAI,KAAK;AAE7B,SAAK,aAAa;AAClB,SAAK,SAAS,KAAK;AACnB,SAAK,eAAe,KAAK,IAAI,KAAK,KAAK,YAAY,MAAM;AACzD,SAAK,aAAa;AAClB,SAAK,gBAAgB,KAAK;AAC1B,SAAK,SAAS;AAGd,SAAK,mBAAmB;AAExB,SAAK,2BAA2B;AAIhC,SAAK,QAAQ,kBAAkB,KAAK;AAKpC,QAAI,CAAC,iBAAiB,KAAK,SAAS;AAClC,WAAK,iBAAiB,KAAK,OAAO;AAAA,IACpC;AAMA,QAAI,KAAK,WAAY,OAAM,KAAK;AAChC,QAAI,KAAK,cAAc;AACrB,UAAI;AACF,cAAM,QAAQ,MAAM,KAAK,iBAAiB;AAI1C,YAAI,KAAK,kBAAkB;AACzB,gBAAM,KAAK,oBAAoB;AAI/B,gBAAM,KAAK,uBAAuB,IAAI;AAAA,QACxC,OAAO;AAGL,eAAK,wBAAwB;AAAA,QAC/B;AACA,eAAO;AAAA,MACT,SAAS,KAAK;AAMZ,aAAK,SAAS;AACd,aAAK,eAAe;AACpB,aAAK,aAAa;AAClB,aAAK,gBAAgB;AACrB,aAAK,gBAAgB;AACrB,aAAK,2BAA2B;AAGhC,aAAK,wBAAwB;AAC7B,aAAK,mBAAmB;AACxB,aAAK,SAAS;AACd,cAAM;AAAA,MACR;AAAA,IACF;AAMA,SAAK,mBAAmB;AACxB,QAAI,KAAK,SAAS;AAChB,WAAK,iBAAiB,KAAK,OAAO;AAAA,IACpC;AAEA,SAAK,wBAAwB;AAC7B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAc,mBAA2C;AACvD,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,QAAI,KAAK,WAAY,OAAM,KAAK;AAChC,QAAI,CAAC,KAAK,cAAc;AACtB,YAAM,IAAI;AAAA,QACR;AAAA,MAEF;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,WAAW;AAChC,UAAM,cAAc,KAAK;AACzB,UAAM,OAAO,MAAM,KAAK,YAAY,SAAS,aAAa,KAAK,YAAY,CAAC;AAK5E,SAAK,SAAS,KAAK;AACnB,SAAK,eAAe,KAAK,IAAI,KAAK,KAAK,YAAY,MAAM;AACzD,SAAK,aAAa;AAClB,SAAK,gBAAgB,KAAK;AAG1B,SAAK,2BAA2B;AAChC,WAAO,EAAE,GAAG,MAAM,WAAW,OAAO;AAAA,EACtC;AAAA,EAEA,MAAc,YACZ,SACA,aACA,WACA,YACwB;AACxB,UAAM,YAAY,MAAM;AAAA,MACtB,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,QAAI,CAAC,WAAW;AAGd,YAAM,IAAI,oBAAoB,8BAA8B;AAAA,IAC9D;AAEA,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,MAChB,eAAe,YAAY;AAAA,MAC3B,MAAM;AAAA,IACR;AACA,QAAI,KAAK,QAAS,SAAQ,eAAe,IAAI,KAAK;AAElD,UAAM,MAAM,MAAM,MAAM,SAAS,EAAE,QAAQ,QAAQ,QAAQ,CAAC;AAE5D,UAAM,cAAc,IAAI,QAAQ,IAAI,YAAY;AAChD,QAAI,YAAa,MAAK,aAAa;AAGnC,QACE,IAAI,WAAW,OACf,eACA,gBAAgB,aAChB,aAAa,aAAY,kBACzB;AACA,aAAO,KAAK,YAAY,SAAS,aAAa,aAAa,aAAa,CAAC;AAAA,IAC3E;AAEA,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC3C,YAAM;AAAA,QACJ,IAAI;AAAA,UACF,EAAE,UACA,EAAE,eACF,uBAAuB,IAAI;AAAA,UAC7B,IAAI;AAAA,QACN;AAAA,QACA,EAAE,QAAQ,QAAQ,MAAM,kBAAkB;AAAA,MAC5C;AAAA,IACF;AAEA,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAe,MAAgC;AAC7C,QAAI,CAAC,QAAQ,CAAC,KAAK,MAAO,OAAM,IAAI,MAAM,mBAAmB;AAS7D,QAAI,KAAK,qBAAqB,KAAK,cAAc,QAAQ;AACvD,YAAM,IAAI;AAAA,QACR;AAAA,MAGF;AAAA,IACF;AAEA,SAAK,aAAa;AAClB,SAAK,SAAS,KAAK;AACnB,SAAK,aAAa,KAAK,aAAa;AACpC,SAAK,eAAe,KAAK,aAAa,OAClC,KAAK,IAAI,KAAK,KAAK,YAAY,MAAM,MACrC,KAAK,IAAI,IAAI,wBAAwB;AACzC,SAAK,gBAAgB,KAAK,gBAAgB;AAC1C,SAAK,gBAAgB;AAIrB,SAAK,2BAA2B;AAIhC,SAAK,wBAAwB;AAM7B,SAAK,mBAAmB;AACxB,SAAK,SAAS;AACd,QAAI,KAAK,SAAS;AAChB,WAAK,iBAAiB,KAAK,OAAO;AAAA,IACpC;AAAA,EACF;AAAA;AAAA,EAGA,SAAe;AACb,SAAK,SAAS;AACd,SAAK,eAAe;AACpB,SAAK,gBAAgB;AACrB,SAAK,gBAAgB;AAKrB,SAAK,wBAAwB;AAC7B,SAAK,SAAS;AAOd,QAAI,KAAK,SAAS;AAChB,WAAK,iBAAiB,KAAK,OAAO;AAAA,IACpC;AACA,SAAK,mBAAmB;AAGxB,SAAK,aAAa,KAAK;AACvB,SAAK,2BAA2B;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,MAAM,iBAAmC;AACvC,QAAI,KAAK,kBAAmB,QAAO;AACnC,QAAI,CAAC,KAAK,QAAS,QAAO;AAC1B,UAAM,YAAY,MAAM,gBAAgB,KAAK,OAAO;AACpD,QAAI,CAAC,UAAW,QAAO;AAIvB,QAAI,UAAU,WAAW,KAAK,QAAS,QAAO;AAE9C,SAAK,aAAa;AAClB,SAAK,SAAS,UAAU;AACxB,SAAK,gBAAgB,UAAU;AAC/B,SAAK,eAAe,UAAU;AAC9B,SAAK,aAAa;AAClB,SAAK,eAAe,UAAU;AAE9B,SAAK,aAAa,QAAQ,QAAQ;AAClC,SAAK,aAAa;AAClB,SAAK,mBAAmB;AACxB,SAAK,SAAS,UAAU,SAAS;AACjC,SAAK,gBAAgB;AACrB,SAAK,2BAA2B;AAIhC,UAAM,KAAK,uBAAuB,KAAK;AACvC,UAAM,KAAK,QAAQ,QAAQ;AAC3B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,sBAA+B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,sBAAqC;AACjD,QACE,CAAC,KAAK,oBACN,CAAC,KAAK,WACN,CAAC,KAAK,gBACN,CAAC,KAAK,UACN,CAAC,KAAK,eACN;AACA;AAAA,IACF;AACA,UAAM,KAAK,MAAM,gBAAgB;AAAA,MAC/B,QAAQ,KAAK;AAAA,MACb,aAAa,KAAK;AAAA,MAClB,cAAc,KAAK;AAAA,MACnB,WAAW,KAAK;AAAA,MAChB,OAAO,KAAK,UAAU;AAAA,MACtB,SAAS,KAAK;AAAA,IAChB,CAAC;AACD,QAAI,CAAC,GAAI,MAAK,KAAK,0DAA0D;AAAA,EAC/E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,6BAAmC;AACzC,SAAK,QAAQ,MAAM;AACnB,SAAK,UAAU,MAAM;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,cAA+B;AACnC,QAAI,KAAK,UAAU,KAAK,IAAI,IAAI,KAAK,cAAc;AACjD,aAAO,KAAK;AAAA,IACd;AAGA,QAAI,KAAK,eAAe;AACtB,aAAO,KAAK;AAAA,IACd;AAGA,QAAI,KAAK,eAAe;AACtB,WAAK,gBAAgB,KAAK,oBAAoB;AAC9C,aAAO,KAAK;AAAA,IACd;AAGA,QAAI,KAAK,YAAY;AACnB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAMA,QAAI,CAAC,KAAK,SAAS;AACjB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAKA,SAAK,gBAAgB,KAAK,oBAAoB;AAE9C,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,sBAAuC;AACnD,SAAK,KAAK,8BAA8B,KAAK,UAAU;AACvD,QAAI;AACF,YAAM,SAAS,KAAK,eAAe;AACnC,YAAM,aAAa,KAAK,WAAW;AACnC,YAAM,OAAO,KAAK,UAAU,EAAE,cAAc,KAAK,cAAc,CAAC;AAIhE,YAAM,MAAM,SACR,MAAM,KAAK,eAAe,YAAY,MAAM,KAAK,YAAY,CAAC,IAC9D,MAAM,MAAM,YAAY;AAAA,QACtB,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C;AAAA,MACF,CAAC;AAEL,UAAI,CAAC,IAAI,IAAI;AACX,aAAK,gBAAgB;AACrB,aAAK,SAAS;AACd,aAAK,gBAAgB;AACrB,cAAM,IAAI,oBAAoB,2BAA2B,IAAI,QAAQ,IAAI,MAAM;AAAA,MACjF;AACA,YAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,WAAK,SAAS,KAAK;AACnB,WAAK,eAAe,KAAK,IAAI,KAAK,KAAK,YAAY,MAAM;AAIzD,WAAK,aAAa,KAAK,cAAc,SAAS,SAAS;AACvD,WAAK,gBAAgB,KAAK;AAC1B,WAAK,gBAAgB;AACrB,WAAK,iBAAiB;AAAA,QACpB,OAAO,KAAK;AAAA,QACZ,WAAW,KAAK;AAAA,QAChB,WAAW,KAAK;AAAA,QAChB,cAAc,KAAK;AAAA,MACrB,CAAC;AAID,UAAI,KAAK,iBAAkB,MAAK,KAAK,oBAAoB;AACzD,aAAO,KAAK;AAAA,IACd,SAAS,KAAK;AACZ,WAAK,gBAAgB;AACrB,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,eACZ,YACA,MACA,WACA,YACmB;AACnB,QAAI,KAAK,WAAY,OAAM,KAAK;AAChC,QAAI,CAAC,KAAK,cAAc;AACtB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM,QAAQ,MAAM;AAAA,MAClB,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,oBAAoB,8BAA8B;AAAA,IAC9D;AACA,UAAM,MAAM,MAAM,MAAM,YAAY;AAAA,MAClC,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,oBAAoB,MAAM,MAAM;AAAA,MAC3D;AAAA,IACF,CAAC;AACD,UAAM,cAAc,IAAI,QAAQ,IAAI,YAAY;AAChD,QAAI,YAAa,MAAK,aAAa;AACnC,QACE,IAAI,WAAW,OACf,eACA,gBAAgB,aAChB,aAAa,aAAY,kBACzB;AACA,aAAO,KAAK,eAAe,YAAY,MAAM,aAAa,aAAa,CAAC;AAAA,IAC1E;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,sBAAuC;AACnD,QAAI;AACF,UAAI,KAAK,WAAY,OAAM,KAAK;AAGhC,YAAM,WAAW,MAAM,MAAM,KAAK,WAAW,eAAe;AAAA,QAC1D,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,SAAS,KAAK,QAAQ,CAAC;AAAA,MAChD,CAAC;AACD,UAAI,CAAC,SAAS;AACZ,cAAM,IAAI,oBAAoB,2BAA2B,SAAS,QAAQ,SAAS,MAAM;AAC3F,YAAM,YAAa,MAAM,SAAS,KAAK;AAQvC,UAAI,UAAU,WAAY,MAAK,aAAa,UAAU;AAGtD,YAAM,QAAQ,MAAM,SAAS,UAAU,WAAW,UAAU,UAAU;AAGtE,YAAM,WAAW,KAAK,WAAW;AACjC,YAAM,YAAY,KAAK,UAAU;AAAA,QAC/B,YAAY;AAAA,QACZ,SAAS,KAAK;AAAA,QACd,OAAO,UAAU;AAAA,QACjB,OAAO,OAAO,KAAK;AAAA,MACrB,CAAC;AAED,YAAM,MAAM,MAAM,KAAK,iBAAiB,UAAU,WAAW,KAAK,UAAU;AAE5E,UAAI,CAAC,IAAI;AACP,cAAM,IAAI,oBAAoB,2BAA2B,IAAI,QAAQ,IAAI,MAAM;AACjF,YAAM,WAAW,IAAI,QAAQ,IAAI,YAAY;AAC7C,UAAI,SAAU,MAAK,aAAa;AAEhC,YAAM,OAAQ,MAAM,IAAI,KAAK;AAK7B,WAAK,SAAS,KAAK;AACnB,WAAK,eAAe,KAAK,IAAI,KAAK,KAAK,aAAa,MAAM;AAC1D,WAAK,aAAc,KAAK,cAAoC;AAC5D,WAAK,gBAAgB;AACrB,aAAO,KAAK;AAAA,IACd,SAAS,KAAK;AACZ,WAAK,gBAAgB;AACrB,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA,EAMA,MAAc,iBACZ,UACA,WACA,WACA,aAAa,GACM;AACnB,UAAM,aAAqC;AAAA,MACzC,gBAAgB;AAAA,IAClB;AACA,UAAM,YAAY,MAAM;AAAA,MACtB,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,QAAI,UAAW,YAAW,MAAM,IAAI;AAEpC,UAAM,MAAM,MAAM,MAAM,UAAU;AAAA,MAChC,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,MAAM;AAAA,IACR,CAAC;AAGD,QAAI,IAAI,WAAW,OAAO,KAAK,cAAc;AAC3C,YAAM,UAAU,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACjD,UAAI,QAAQ,UAAU,kBAAkB;AACtC,cAAM,cAAc,IAAI,QAAQ,IAAI,YAAY;AAChD,YAAI,eAAe,gBAAgB,aAAa,aAAa,aAAY,kBAAkB;AACzF,eAAK,aAAa;AAClB,iBAAO,KAAK,iBAAiB,UAAU,WAAW,aAAa,aAAa,CAAC;AAAA,QAC/E;AAAA,MACF;AACA,YAAM,IAAI;AAAA,QACR,4BACG,QAAQ,qBAAqB,QAAQ,SAAS;AAAA,QACjD;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,QACJ,QACA,MACA,MACmB;AACnB,SAAK,KAAK,QAAQ,IAAI;AAEtB,QAAI,CAAC,KAAK,UAAU,CAAC,kBAAkB,MAAM,KAAK,SAAS,QAAW;AACpE,YAAM,QAAQ,KAAK,YAAY,IAAI,OAAO,MAAM,KAAK,YAAY;AACjE,YAAM,MAAM,MAAM,KAAK,wBAAwB,QAAQ,MAAM,MAAM,KAAK;AACxE,WAAK,KAAK,QAAQ,MAAM,UAAK,IAAI,MAAM;AACvC,aAAO;AAAA,IACT;AAEA,UAAM,MAAM,SAAS,OAAO,QAAQ,IAAI;AAIxC,UAAM,WAAW,KAAK,UAAU,IAAI,GAAG;AACvC,QAAI,UAAU;AACZ,aAAO,SAAS,KAAK,CAAC,QAAQ,IAAI,MAAM,CAAC;AAAA,IAC3C;AAEA,UAAM,UAAU,KAAK,eAAe,QAAQ,MAAM,GAAG;AACrD,SAAK,UAAU,IAAI,KAAK,OAAO;AAC/B,QAAI;AACF,aAAO,MAAM;AAAA,IACf,UAAE;AACA,WAAK,UAAU,OAAO,GAAG;AAAA,IAC3B;AAAA,EACF;AAAA,EAEA,MAAc,eACZ,QACA,MACA,KACmB;AACnB,UAAM,QAAQ,KAAK;AAEnB,QAAI,CAAC,OAAO;AAEV,YAAMA,SAAQ,KAAK,YAAY,IAAI,OAAO,MAAM,KAAK,YAAY;AACjE,aAAO,KAAK,wBAAwB,QAAQ,MAAM,QAAWA,MAAK;AAAA,IACpE;AACA,UAAM,SAAS,MAAM,IAAI,GAAG;AAC5B,UAAM,cAAsC,CAAC;AAC7C,QAAI,QAAQ,KAAM,aAAY,eAAe,IAAI,OAAO;AACxD,QAAI,QAAQ,aAAc,aAAY,mBAAmB,IAAI,OAAO;AAEpE,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO,MAAM,KAAK,YAAY;AACjE,UAAM,MAAM,MAAM,KAAK,wBAAwB,QAAQ,MAAM,QAAW,OAAO,GAAG,WAAW;AAC7F,SAAK,KAAK,QAAQ,MAAM,UAAK,IAAI,MAAM;AAQvC,QAAI,IAAI,WAAW,OAAO,QAAQ;AAChC,YAAM,mBAAmB,EAAE,GAAG,OAAO,SAAS,GAAG,gBAAgB,IAAI,OAAO,EAAE;AAC9E,YAAM,gBAAgB,IAAI,QAAQ,IAAI,MAAM,KAAK,OAAO;AACxD,YAAM,wBAAwB,IAAI,QAAQ,IAAI,eAAe,KAAK,OAAO;AACzE,YAAM,IAAI,KAAK;AAAA,QACb,GAAG;AAAA,QACH,MAAM;AAAA,QACN,cAAc;AAAA,QACd,SAAS;AAAA,QACT,UAAU,KAAK,IAAI;AAAA,MACrB,CAAC;AACD,WAAK,eAAe,YAAY,EAAE,KAAK,KAAK,CAAC;AAC7C,YAAM,OAAO,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO,KAAK,UAAU,OAAO,IAAI;AACvF,aAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,KAAK,SAAS,iBAAiB,CAAC;AAAA,IACtE;AAGA,QAAI,IAAI,WAAW,KAAK;AACtB,YAAM,OAAO,IAAI,QAAQ,IAAI,MAAM,KAAK;AACxC,YAAM,eAAe,IAAI,QAAQ,IAAI,eAAe,KAAK;AACzD,UAAI,QAAQ,cAAc;AACxB,cAAM,OAAO,MAAM,IAAI,MAAM,EAAE,KAAK;AACpC,YAAI,OAAgB;AACpB,YAAI;AACF,iBAAO,KAAK,MAAM,IAAI;AAAA,QACxB,QAAQ;AAAA,QAER;AACA,cAAM,IAAI,KAAK;AAAA,UACb;AAAA,UACA;AAAA,UACA;AAAA,UACA,SAAS,gBAAgB,IAAI,OAAO;AAAA,UACpC,UAAU,KAAK,IAAI;AAAA,QACrB,CAAC;AACD,aAAK,eAAe,aAAa,EAAE,KAAK,KAAK,CAAC;AAAA,MAChD;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAIA,MAAc,wBACZ,QACA,MACA,MACA,OACA,aAAa,GACb,eAAuC,CAAC,GACrB;AASnB,QAAI,KAAK,eAAe,UAAU,UAAU,MAAM;AAChD,UAAI,KAAK,WAAY,OAAM,KAAK;AAChC,UAAI,CAAC,KAAK,cAAc;AACtB,cAAM,IAAI;AAAA,UACR;AAAA,QAEF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,MAAM,KAAK,WAAW;AAE5B,UAAM,SAAS,KAAK,eAAe,UAAU,CAAC,CAAC,KAAK,gBAAgB,UAAU;AAkB9E,UAAM,WAAW,KAAK,MAAM,GAAG,EAAE,CAAC;AAClC,UAAM,gBACJ,SAAS,WAAW,cAAc,KAAK,aAAa;AACtD,UAAM,kBAAkB,CAAC,UACvB,UAAU,QACV,OAAO,UAAU,YACjB,CAAC,MAAM,QAAQ,KAAK,KACpB,EAAE,cAAe,SACb,EAAE,GAAI,OAAmC,YAAY,qBAAqB,IAC1E;AACN,QAAI,gBAAgB;AACpB,QAAI,iBAAiB,kBAAkB,QAAW;AAChD,sBAAgB,MAAM,QAAQ,aAAa,IACvC,cAAc,IAAI,eAAe,IACjC,gBAAgB,aAAa;AAAA,IACnC;AACA,UAAM,UAAU,kBAAkB,SAAY,KAAK,UAAU,aAAa,IAAI;AAE9E,UAAM,YAAY,OAChB,UACA,cACsB;AACtB,YAAM,YACJ,KAAK,eAAe,UAAU,CAAC,CAAC,KAAK,gBAAgB,aAAa;AAEpE,YAAM,YAAY,YACd,MAAM;AAAA,QACJ,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,IACA;AAEJ,YAAM,UAAkC;AAAA,QACtC,gBAAgB;AAAA,QAChB,QAAQ;AAAA,QACR,GAAG;AAAA,MACL;AAEA,UAAI,aAAa,MAAM;AAErB,gBAAQ,eAAe,KAAK,YAAY,UAAU,aAAa;AAAA,MACjE;AAEA,UAAI,UAAW,SAAQ,MAAM,IAAI;AACjC,UAAI,KAAK,QAAS,SAAQ,gBAAgB,IAAI,KAAK;AAEnD,aAAO,MAAM,KAAK,EAAE,QAAQ,SAAS,MAAM,QAAQ,CAAC;AAAA,IACtD;AAEA,QAAI,eAAe;AACnB,QAAI,MAAM,MAAM,UAAU,cAAc,KAAK,UAAU;AACvD,UAAM,gBAAgB,KAAK;AAC3B,UAAM,WAAW,IAAI,QAAQ,IAAI,YAAY;AAE7C,QAAI,SAAU,MAAK,aAAa;AAGhC,UAAM,cAAc,iBAAiB;AAMrC,QAAI,IAAI,WAAW,OAAO,UAAU,YAAY,aAAa,eAAe;AAC1E,YAAM,MAAM,UAAU,cAAc,QAAQ;AAC5C,YAAM,KAAK,IAAI,QAAQ,IAAI,YAAY;AACvC,UAAI,GAAI,MAAK,aAAa;AAC1B,UAAI,IAAI,WAAW,OAAO,aAAa;AACrC,aAAK,SAAS;AACd,aAAK,gBAAgB;AACrB,uBAAe,MAAM,KAAK,YAAY;AACtC,cAAM,MAAM,UAAU,cAAc,KAAK,UAAU;AACnD,cAAM,KAAK,IAAI,QAAQ,IAAI,YAAY;AACvC,YAAI,GAAI,MAAK,aAAa;AAAA,MAC5B;AAAA,IACF,WAAW,IAAI,WAAW,OAAO,aAAa;AAC5C,WAAK,SAAS;AACd,WAAK,gBAAgB;AACrB,qBAAe,MAAM,KAAK,YAAY;AACtC,YAAM,MAAM,UAAU,cAAc,KAAK,UAAU;AACnD,YAAM,KAAK,IAAI,QAAQ,IAAI,YAAY;AAEvC,UAAI,GAAI,MAAK,aAAa;AAAA,IAC5B;AAEA,QAAI,IAAI,WAAW,OAAO,aAAa,aAAY,qBAAqB;AACtE,YAAM,aAAa,IAAI,QAAQ,IAAI,YAAY;AAE/C,UAAI,WAAY,MAAK,aAAa;AAClC,YAAM,QACJ,SAAS,IAAI,QAAQ,IAAI,aAAa,KAAK,KAAK,EAAE,IAAI;AACxD,YAAM,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,KAAK,CAAC;AAC/D,aAAO,KAAK,wBAAwB,QAAQ,MAAM,MAAM,cAAc,aAAa,GAAG,YAAY;AAAA,IACpG;AAEA,WAAO;AAAA,EACT;AACF;AAtTE,cA5yBW,cA4yBa,oBAAmB;AAkK3C,cA98BW,cA88Ba,uBAAsB;AA98BzC,IAAM,cAAN;;;ACDA,IAAM,mBAAN,MAAuB;AAAA,EAiB5B,YACE,MACA,SACA,QACA,MACA,oBACA;AAtBF,wBAAQ;AACR,wBAAQ;AACR,wBAAQ;AACR,wBAAQ;AACR,wBAAQ;AAER,wBAAQ,OAAwB;AAChC,wBAAQ,eAA6B;AACrC,wBAAQ,uBAAqC;AAC7C,wBAAQ,uBAAsB;AAC9B,wBAAQ,sBAAqB;AAC7B,wBAAQ,sBAA2D;AACnE,wBAAQ,mBAAwD;AAChE,wBAAQ,iBAA4C;AACpD,wBAAQ,wBAAuB;AAS7B,SAAK,QAAQ;AACb,SAAK,WAAW;AAChB,SAAK,UAAU;AACf,SAAK,QAAQ;AACb,SAAK,sBAAsB,sBAAsB;AAAA,EACnD;AAAA,EAEQ,QAAQ,MAAuB;AACrC,QAAI,KAAK,MAAM,OAAQ,SAAQ,IAAI,iBAAiB,GAAG,IAAI;AAAA,EAC7D;AAAA;AAAA,EAGA,MAAM,UAAyB;AAG7B,QAAI,KAAK,MAAM,YAAY,GAAG;AAC5B,YAAM,MAAM,IAAI;AAAA,QACd;AAAA,MAEF;AACA,WAAK,MAAM,SAAS,GAAG;AACvB,YAAM;AAAA,IACR;AACA,QAAI,KAAK,iBAAiB;AACxB,mBAAa,KAAK,eAAe;AACjC,WAAK,kBAAkB;AAAA,IACzB;AACA,QACE,KAAK,QACJ,KAAK,IAAI,eAAe,UAAU,QACjC,KAAK,IAAI,eAAe,UAAU,aACpC;AACA;AAAA,IACF;AACA,SAAK,sBAAsB;AAC3B,SAAK,qBAAqB;AAE1B,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,MAAM,YAAY;AAC3C,YAAM,KAAK,eAAe,KAAK;AAAA,IACjC,SAAS,KAAK;AACZ,WAAK,MAAM,SAAS,GAAG;AAAA,IACzB;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,YAA2B;AAC/B,SAAK,sBAAsB;AAC3B,SAAK,aAAa;AAClB,QAAI,KAAK,KAAK;AACZ,WAAK,IAAI,MAAM,KAAM,kBAAkB;AACvC,WAAK,MAAM;AAAA,IACb;AACA,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA;AAAA,EAGA,aAAmB;AACjB,SAAK,sBAAsB;AAC3B,SAAK,aAAa;AAClB,SAAK,gBAAgB;AACrB,SAAK,uBAAuB;AAC5B,QAAI,KAAK,KAAK;AACZ,WAAK,IAAI,MAAM,KAAM,mBAAmB;AACxC,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AAAA;AAAA,EAGA,cAAuB;AACrB,WAAO,CAAC,EAAE,KAAK,OAAO,KAAK,IAAI,eAAe,UAAU;AAAA,EAC1D;AAAA;AAAA,EAGA,UAAU,SAMD;AACP,UAAM,MAA2B,EAAE,QAAQ,YAAY;AACvD,QAAI,SAAS;AACX,UAAI,QAAQ,YAAa,KAAI,cAAc,QAAQ;AACnD,UAAI,QAAQ,UAAW,KAAI,YAAY,QAAQ;AAE/C,UAAI,QAAQ,OAAQ,KAAI,SAAS,QAAQ;AAEzC,UAAI,QAAQ,UAAU,EAAG,KAAI,UAAU,IAAI,QAAQ,UAAU;AAAA,IAC/D;AACA,SAAK,gBAAgB;AAErB,QAAI,KAAK,qBAAsB;AAC/B,QAAI,KAAK,OAAO,KAAK,IAAI,eAAe,UAAU,MAAM;AACtD,WAAK,IAAI,KAAK,KAAK,UAAU,GAAG,CAAC;AAAA,IACnC;AAAA,EACF;AAAA,EAEA,MAAc,sBAAuC;AACnD,QAAI,KAAK,oBAAqB,QAAO,KAAK;AAC1C,QAAI,KAAK,YAAa,QAAO,KAAK;AAGlC,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,KAAK,WAAW,mBAAmB;AAC3D,UAAI,IAAI,IAAI;AACV,cAAM,OAAQ,MAAM,IAAI,KAAK;AAK7B,YAAI,KAAK,WAAW,KAAK,UAAU;AACjC,eAAK,cAAc,KAAK;AACxB,cAAI,KAAK,YAAY;AACnB,iBAAK,sBAAsB,KAAK;AAAA,UAClC;AACA,iBAAO,KAAK;AAAA,QACd;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAGA,SAAK,cAAc,KAAK,SAAS,QAAQ,SAAS,IAAI;AACtD,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,eAAe,OAA8B;AACzD,UAAM,WAAW,MAAM,KAAK,oBAAoB;AAChD,WAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,YAAM,SAAS,IAAI,gBAAgB,EAAE,QAAQ,KAAK,QAAQ,CAAC;AAC3D,UAAI,KAAK,qBAAqB;AAC5B,eAAO,IAAI,cAAc,KAAK,mBAAmB;AAAA,MACnD;AACA,YAAM,QACJ,YACC,SAAS,QAAQ,GAAG,MAAM,KAAK,MAAM,OACtC,OAAO,SAAS;AAElB,WAAK,KAAK,cAAc,KAAK;AAC7B,YAAM,KAAK,IAAI,UAAU,OAAO,CAAC,cAAc,KAAK,CAAC;AACrD,WAAK,MAAM;AAEX,SAAG,SAAS,MAAY;AACtB,YAAI,KAAK,QAAQ,GAAI;AACrB,aAAK,KAAK,WAAW;AACrB,aAAK,qBAAqB;AAE1B,cAAM,SACJ,KAAK,MAAM,eAAe,UAAU,CAAC,CAAC,KAAK,MAAM;AAEnD,cAAM,cAAc,SAChB,gBAAgB,KAAK,MAAM,cAAc,OAAO,OAAO,IAAI,EAAE;AAAA,UAC3D,CAAC,UAAU;AACT,gBAAI,SAAS,GAAG,eAAe,UAAU,MAAM;AAC7C,iBAAG,KAAK,KAAK,UAAU,EAAE,QAAQ,aAAa,MAAM,CAAC,CAAC;AAAA,YACxD;AAAA,UACF;AAAA,QACF,IACA,QAAQ,QAAQ;AAEpB,oBACG,KAAK,MAAM;AACV,eAAK,sBAAsB;AAE3B,cAAI,QAAQ;AACV,iBAAK,uBAAuB;AAAA,UAC9B,WAAW,KAAK,eAAe;AAC7B,eAAG,KAAK,KAAK,UAAU,KAAK,aAAa,CAAC;AAAA,UAC5C;AAEA,eAAK,MAAM,MAAM;AACjB,eAAK,MAAM,WAAW;AACtB,kBAAQ;AAAA,QACV,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,eAAK,MAAM,SAAS,GAAG;AACvB,kBAAQ;AAAA,QACV,CAAC;AAAA,MACL;AAEA,SAAG,YAAY,CAAC,UAA8B;AAC5C,YAAI,KAAK,QAAQ,GAAI;AACrB,YAAI;AACJ,YAAI;AACF,gBAAM,KAAK,MAAM,MAAM,IAAc;AAAA,QACvC,QAAQ;AACN;AAAA,QACF;AAEA,YAAI,IAAI,SAAS,OAAQ;AAMzB,YAAI,IAAI,SAAS,cAAc;AAC7B,eAAK,MAAM,cAAc,MAAS;AAClC;AAAA,QACF;AAEA,YAAI,IAAI,SAAS,iBAAiB;AAChC,eAAK,uBAAuB;AAC5B,cAAI,KAAK,eAAe;AACtB,eAAG,KAAK,KAAK,UAAU,KAAK,aAAa,CAAC;AAAA,UAC5C;AACA;AAAA,QACF;AAEA,YAAI,IAAI,SAAS,SAAS;AACxB,eAAK,MAAM,SAAS,IAAI,MAAM,IAAI,OAAiB,CAAC;AACpD;AAAA,QACF;AAEA,aAAK,KAAK,SAAS,IAAI,MAAM,IAAI,YAAY,EAAE;AAG/C,YAAI,IAAI,YAAY,IAAI,QAAQ,OAAO,IAAI,SAAS,UAAU;AAC5D,cAAI,SAAS;AAAA,YACX,IAAI,IAAI;AAAA,YACR,MAAM,IAAI;AAAA,YACV,GAAI,IAAI;AAAA,UACV;AAAA,QACF;AAEA,aAAK,MAAM,IAAI,MAAgB,GAAG;AAClC,aAAK,MAAM,WAAW,GAAG;AAAA,MAC3B;AAEA,SAAG,UAAU,CAAC,UAA4B;AACxC,YAAI,KAAK,QAAQ,GAAI;AACrB,aAAK,aAAa;AAElB,YAAI,KAAK,qBAAqB;AAC5B,eAAK,MAAM,SAAS,KAAK;AACzB;AAAA,QACF;AAEA,aAAK,MAAM,SAAS,KAAK;AACzB,aAAK,MAAM,cAAc;AACzB,aAAK,eAAe;AAAA,MACtB;AAEA,SAAG,UAAU,CAAC,QAAqB;AACjC,YAAI,KAAK,QAAQ,GAAI;AACrB,aAAK,MAAM,SAAS,GAAG;AACvB,YAAI,GAAG,eAAe,UAAU,MAAM;AACpC,iBAAO,IAAI,MAAM,6BAA6B,CAAC;AAAA,QACjD;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,wBAA8B;AACpC,QAAI,KAAK,mBAAoB,cAAa,KAAK,kBAAkB;AAEjE,UAAM,YAAY,KAAK,MAAM,eAAe,KAAK,IAAI;AACrD,UAAM,YAAY,KAAK;AAAA,MACrB,YAAY;AAAA,MACZ;AAAA,IACF;AAEA,SAAK,qBAAqB,WAAW,MAAM;AACzC,YAAM,QAAQ,KAAK;AACnB,WAAK,MAAM,SAAS;AACpB,WAAK,MACF,YAAY,EACZ,KAAK,CAAC,aAAa;AAClB,YAAI,CAAC,SAAS,MAAM,eAAe,UAAU,KAAM;AAEnD,aAAK,sBAAsB;AAC3B,cAAM,MAAM,KAAM,eAAe;AAEjC,aAAK,sBAAsB;AAC3B,eAAO,KAAK,eAAe,QAAQ;AAAA,MACrC,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,aAAK,MAAM,SAAS,GAAG;AACvB,aAAK,eAAe;AAAA,MACtB,CAAC;AAAA,IACL,GAAG,SAAS;AAAA,EACd;AAAA,EAEQ,iBAAuB;AAC7B,QAAI,KAAK,oBAAqB;AAC9B,QACE,KAAK,QACJ,KAAK,IAAI,eAAe,UAAU,QACjC,KAAK,IAAI,eAAe,UAAU,aACpC;AACA;AAAA,IACF;AAEA,QAAI,KAAK,sBAAsB,wBAAwB;AACrD,WAAK,MAAM,SAAS,IAAI,MAAM,mCAAmC,CAAC;AAClE;AAAA,IACF;AAEA,UAAM,QAAQ,KAAK;AAAA,MACjB,oBAAoB,KAAK,IAAI,GAAG,KAAK,kBAAkB;AAAA,MACvD;AAAA,IACF;AACA,SAAK;AACL,SAAK,MAAM,gBAAgB;AAAA,MACzB,SAAS,KAAK;AAAA,MACd;AAAA,IACF,CAAC;AAED,SAAK,kBAAkB,WAAW,MAAM;AACtC,WAAK,MAAM,SAAS;AACpB,WAAK,MACF,YAAY,EACZ,KAAK,CAAC,UAAU,KAAK,eAAe,KAAK,CAAC,EAC1C,MAAM,MAAM,KAAK,eAAe,CAAC;AAAA,IACtC,GAAG,KAAK;AAAA,EACV;AAAA,EAEQ,eAAqB;AAC3B,QAAI,KAAK,oBAAoB;AAC3B,mBAAa,KAAK,kBAAkB;AACpC,WAAK,qBAAqB;AAAA,IAC5B;AACA,QAAI,KAAK,iBAAiB;AACxB,mBAAa,KAAK,eAAe;AACjC,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AACF;;;AC3UO,IAAM,4BACX,OAAQ,WAAuC,cAAc,eAC7D,OAAO,oBAAoB;AAG7B,IAAM,YAAY;AAGlB,IAAM,kBAAkB;AA4BxB,SAAS,cAA4B;AACnC,SAAO,IAAI,QAAa,CAAC,SAAS,WAAW;AAC3C,UAAM,MAAY,WAAmB,UAAU;AAAA,MAC7C;AAAA,MACA;AAAA,IACF;AACA,QAAI,kBAAkB,MAAM;AAC1B,YAAM,KAAK,IAAI;AACf,UAAI,CAAC,GAAG,iBAAiB,SAAS,mBAAmB,GAAG;AACtD,WAAG,kBAAkB,qBAAqB,EAAE,SAAS,KAAK,CAAC;AAAA,MAC7D;AAAA,IACF;AACA,QAAI,YAAY,MAAM;AAAE,cAAQ,IAAI,MAAM;AAAA,IAAG;AAC7C,QAAI,UAAU,MAAM;AAAE,aAAO,IAAI,SAAS,IAAI,MAAM,uBAAuB,CAAC;AAAA,IAAG;AAI/E,QAAI,YAAY,MAAM;AAAE,aAAO,IAAI,SAAS,IAAI,MAAM,wBAAwB,CAAC;AAAA,IAAG;AAAA,EACpF,CAAC;AACH;AAEA,SAAS,SAAS,OAAe,KAAqB;AACpD,SAAO,QAAQ,YAAY;AAC7B;AAIO,IAAM,sBAAN,MAAsD;AAAA,EAC3D,MAAM,UACJ,OACA,OACoD;AACpD,QAAI,KAAU;AACd,QAAI;AACF,WAAK,MAAM,YAAY;AACvB,YAAM,UAAU,MAAM,IAAI,QAAgC,CAAC,SAAS,WAAW;AAC7E,cAAM,KAAK,GAAI,YAAY,qBAAqB,UAAU;AAC1D,cAAM,QAAS,WAAmB,YAAY;AAAA,UAC5C,QAAQ;AAAA,UACR,QAAQ,YAAY;AAAA,QACtB;AACA,cAAM,MAAM,GAAG,YAAY,mBAAmB,EAAE,OAAO,KAAK;AAC5D,YAAI,YAAY,MAAM;AAAE,kBAAQ,IAAI,UAAU,CAAC,CAAC;AAAA,QAAG;AACnD,YAAI,UAAU,MAAM;AAAE,iBAAO,IAAI,SAAS,IAAI,MAAM,yBAAyB,CAAC;AAAA,QAAG;AAAA,MACnF,CAAC;AACD,aAAO,QACJ,OAAO,CAAC,MAAM,KAAK,EAAE,UAAU,SAAS,OAAO,EAAE,QAAQ,YAAY,EAAE,KAAK,EAC5E,KAAK,CAAC,GAAG,OAAO,EAAE,MAAM,YAAY,MAAM,EAAE,MAAM,YAAY,EAAE,EAChE,MAAM,CAAC,KAAK,EACZ,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,OAAO,EAAE,MAAM,EAAE;AAAA,IAChD,QAAQ;AAGN,aAAO,CAAC;AAAA,IACV,UAAE;AACA,UAAI,MAAM;AAAA,IACZ;AAAA,EACF;AAAA,EAEA,MAAM,IAAI,OAAe,KAAa,OAAkC;AACtE,QAAI,KAAU;AACd,QAAI;AACF,WAAK,MAAM,YAAY;AACvB,YAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,cAAM,KAAK,GAAI,YAAY,qBAAqB,WAAW;AAC3D,cAAM,SAA+B,EAAE,IAAI,SAAS,OAAO,GAAG,GAAG,OAAO,KAAK,MAAM;AACnF,WAAG,YAAY,mBAAmB,EAAE,IAAI,MAAM;AAC9C,WAAG,aAAa,MAAM;AAAE,kBAAQ;AAAA,QAAG;AACnC,WAAG,UAAU,MAAM;AAAE,iBAAO,GAAG,SAAS,IAAI,MAAM,sBAAsB,CAAC;AAAA,QAAG;AAC5E,WAAG,UAAU,MAAM;AAAE,iBAAO,GAAG,SAAS,IAAI,MAAM,+BAA+B,CAAC;AAAA,QAAG;AAAA,MACvF,CAAC;AAAA,IACH,QAAQ;AAAA,IAIR,UAAE;AACA,UAAI,MAAM;AAAA,IACZ;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,OAAe,MAA+B;AACzD,QAAI,KAAK,WAAW,EAAG;AACvB,QAAI,KAAU;AACd,QAAI;AACF,WAAK,MAAM,YAAY;AACvB,YAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,cAAM,KAAK,GAAI,YAAY,qBAAqB,WAAW;AAC3D,cAAM,QAAQ,GAAG,YAAY,mBAAmB;AAChD,mBAAW,OAAO,KAAM,OAAM,OAAO,SAAS,OAAO,GAAG,CAAC;AACzD,WAAG,aAAa,MAAM;AAAE,kBAAQ;AAAA,QAAG;AACnC,WAAG,UAAU,MAAM;AAAE,iBAAO,GAAG,SAAS,IAAI,MAAM,yBAAyB,CAAC;AAAA,QAAG;AAC/E,WAAG,UAAU,MAAM;AAAE,iBAAO,GAAG,SAAS,IAAI,MAAM,+BAA+B,CAAC;AAAA,QAAG;AAAA,MACvF,CAAC;AAAA,IACH,QAAQ;AAAA,IAER,UAAE;AACA,UAAI,MAAM;AAAA,IACZ;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,OAA8B;AAC7C,QAAI,KAAU;AACd,QAAI;AACF,WAAK,MAAM,YAAY;AACvB,YAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,cAAM,KAAK,GAAI,YAAY,qBAAqB,WAAW;AAC3D,cAAM,QAAS,WAAmB,YAAY;AAAA,UAC5C,QAAQ;AAAA,UACR,QAAQ,YAAY;AAAA,QACtB;AACA,WAAG,YAAY,mBAAmB,EAAE,OAAO,KAAK;AAChD,WAAG,aAAa,MAAM;AAAE,kBAAQ;AAAA,QAAG;AACnC,WAAG,UAAU,MAAM;AAAE,iBAAO,GAAG,SAAS,IAAI,MAAM,wBAAwB,CAAC;AAAA,QAAG;AAC9E,WAAG,UAAU,MAAM;AAAE,iBAAO,GAAG,SAAS,IAAI,MAAM,+BAA+B,CAAC;AAAA,QAAG;AAAA,MACvF,CAAC;AAAA,IACH,QAAQ;AAAA,IAER,UAAE;AACA,UAAI,MAAM;AAAA,IACZ;AAAA,EACF;AACF;AAcO,SAAS,yBAAkD;AAChE,SAAO,4BAA4B,IAAI,oBAAoB,IAAI;AACjE;;;ACxHO,SAAS,mCACd,SACA,QACA,UACQ;AACR,SACE,yCACA,MACA,mBAAmB,OAAO,IAC1B,MACA,mBAAmB,MAAM,IACzB,MACA;AAEJ;AAcA,SAAS,uBAAuB,KAA0B;AACxD,QAAM,QAAQ,IAAI,WAAW,GAAG;AAChC,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAU,OAAO,aAAa,MAAM,CAAC,CAAC;AAAA,EACxC;AACA,SAAO,KAAK,MAAM,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE;AAC/E;AAEA,SAAS,YAAY,cAGnB;AACA,QAAM,WAAW,aAAa;AAC9B,MAAI,SAAS,aAAa,MAAM;AAChC,MAAI,OAAO,aAAa,MAAM;AAI9B,OAAK,CAAC,UAAU,CAAC,SAAS,OAAO,aAAa,WAAW,YAAY;AACnE,UAAM,YAAY,aAAa,OAAO,QAAQ;AAC9C,UAAM,UAAU,aAAa,OAAO,MAAM;AAC1C,QAAI,CAAC,UAAU,UAAW,UAAS,uBAAuB,SAAS;AACnE,QAAI,CAAC,QAAQ,QAAS,QAAO,uBAAuB,OAAO;AAAA,EAC7D;AACA,MAAI,CAAC,YAAY,CAAC,UAAU,CAAC,MAAM;AACjC,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,UAAU,MAAM,EAAE,QAAQ,KAAK,EAAE;AAC5C;AAyBO,SAAS,4BACd,cACA,SAC6D;AAC7D,QAAM,EAAE,UAAU,KAAK,IAAI,YAAY,YAAY;AACnD,SAAO,EAAE,KAAK,UAAU,MAAM,GAAG,QAAQ;AAC3C;AA0BO,SAAS,wBACd,cACA,SAC0E;AAC1E,QAAM,EAAE,UAAU,KAAK,IAAI,YAAY,YAAY;AACnD,SAAO,EAAE,KAAK,UAAU,SAAS,EAAE,MAAM,GAAG,QAAQ,EAAE;AACxD;AAOO,SAAS,sBAAsB,WAA4C;AAChF,QAAM,UAAU,IAAI,QAAQ,IAAK,UAAU,SAAS,KAAM,CAAC;AAC3D,QAAM,UAAU,YAAY,SAAS,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AACzE,QAAM,MAAM,KAAK,MAAM;AACvB,QAAM,SAAS,IAAI,WAAW,IAAI,MAAM;AACxC,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,WAAO,CAAC,IAAI,IAAI,WAAW,CAAC;AAAA,EAC9B;AACA,SAAO;AACT;AAaO,SAAS,4BACd,cACA,YACS;AACT,QAAM,cAAc,aAAa,SAAS;AAC1C,MAAI,CAAC,YAAa,QAAO;AACzB,QAAM,gBAAgB,IAAI,WAAW,WAAW;AAChD,MAAI,cAAc,WAAW,WAAW,OAAQ,QAAO;AACvD,WAAS,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK;AAC7C,QAAI,cAAc,CAAC,MAAM,WAAW,CAAC,EAAG,QAAO;AAAA,EACjD;AACA,SAAO;AACT;;;AVxIO,IAAM,WAAN,cAAuB,aAAa;AAAA,EAoBzC,YAAY,SAA2B;AACrC,UAAM;AApBR,wBAAQ;AACR,wBAAQ;AAMR;AAAA;AAAA;AAAA;AAAA;AAAA,wBAAQ,4BAA2B,oBAAI,IAGrC;AAOF;AAAA;AAAA;AAAA;AAAA;AAAA,0CAAiE;AAI/D,UAAM,OAAO,WAAW,CAAC;AAGzB,QAAI,SAAS,KAAK,UAAU;AAC5B,QAAI,SAAS,KAAK,UAAU;AAC5B,QAAI,UAAU,KAAK,WAAW;AAM9B,QAAI,OAAO,aAAa,eAAe,SAAS,eAAe;AAC7D,YAAM,SAAS,SAAS;AACxB,UAAI,CAAC,OAAQ,UAAS,QAAQ,eAAe,cAAc,KAAK;AAChE,UAAI,CAAC,OAAQ,UAAS,QAAQ,eAAe,aAAa,KAAK;AAC/D,UAAI,CAAC,QAAS,WAAU,QAAQ,eAAe,eAAe,KAAK;AAAA,IACrE;AASA,cAAU,QAAQ,QAAQ,QAAQ,EAAE;AAEpC,SAAK,QAAQ,IAAI,YAAY,SAAS,QAAQ,QAAQ,KAAK,OAAO,KAAK,SAAS;AAChF,SAAK,MAAM,iBAAiB,CAAC,UAAgB;AAC3C,WAAK,iBAAiB,KAAK;AAC3B,WAAK,KAAK,gBAAgB,KAAK;AAAA,IACjC;AAGA,QAAI,KAAK,UAAU,OAAO;AACxB,YAAM,QAAQ,IAAI,SAAS,KAAK,mBAAmB,6BAA6B;AAGhF,YAAM,cAAc,uBAAuB;AAC3C,UAAI,YAAa,OAAM,kBAAkB,WAAW;AACpD,WAAK,MAAM,SAAS,KAAK;AACzB,WAAK,MAAM,qBAAqB,CAAC,MAAM,YAAY,KAAK,KAAK,MAAM,OAAO,CAAC;AAAA,IAC7E;AAEA,SAAK,MAAM,IAAI;AAAA,MACb,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA,CAAC,OAAO,SAAS;AACf,aAAK,KAAK,OAAO,IAAI;AAAA,MASvB;AAAA,MACA,KAAK;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,aAAmB;AACjB,UAAM,QAAQ,KAAK,MAAM,SAAS;AAClC,QAAI,CAAC,MAAO;AACZ,UAAM,UAAU,MAAM,YAAY,MAAM,IAAI;AAC5C,eAAW,OAAO,SAAS;AACzB,WAAK,MAAM,eAAe,oBAAoB,EAAE,KAAK,MAAM,IAAI,MAAM,GAAG,EAAE,MAAM,CAAC,EAAE,KAAK,GAAG,EAAE,CAAC;AAAA,IAChG;AAGA,SAAK,MAAM,eAAe;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBA,MAAM,MACJ,OACA,UACA,SACwB;AACxB,WAAO,KAAK,MAAM,MAAM,OAAO,UAAU,OAAO;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,MAAM,iBAAmC;AACvC,WAAO,KAAK,MAAM,eAAe;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAuB;AACrB,WAAO,KAAK,MAAM,YAAY;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAe,MAAgC;AAC7C,SAAK,MAAM,eAAe,IAAI;AAC9B,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,SAAe;AACb,SAAK,IAAI,WAAW;AACpB,SAAK,MAAM,OAAO;AAAA,EACpB;AAAA;AAAA;AAAA,EAKA,MAAM,aACJ,QAC4B;AAC5B,UAAM,MAAM,MAAM,KAAK,MAAM,QAAQ,QAAQ,wBAAwB,MAAM;AAC3E,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC3C,YAAM,wBAAwB,IAAI,QAAQ,GAAG,eAAe;AAAA,IAC9D;AACA,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AAAA;AAAA,EAGA,MAAM,UAAU,UAAoD;AAClE,UAAM,MAAM,MAAM,KAAK,MAAM;AAAA,MAC3B;AAAA,MACA,0BAA0B,mBAAmB,QAAQ;AAAA,IACvD;AACA,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC3C,YAAM,wBAAwB,IAAI,QAAQ,GAAG,WAAW;AAAA,IAC1D;AACA,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB;AAAA;AAAA,EAGA,MAAM,YACJ,QACoC;AACpC,UAAM,MAAM,MAAM,KAAK,MAAM,QAAQ,OAAO,kBAAkB,MAAM,CAAC;AACrE,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC3C,YAAM,wBAAwB,IAAI,QAAQ,GAAG,cAAc;AAAA,IAC7D;AACA,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBA,KACE,QACA,SACY;AACZ,UAAM,WAAW,QAAQ,YAAY;AACrC,QAAI,CAAC,OAAO,SAAS,QAAQ,KAAK,YAAY,GAAG;AAC/C,YAAM,IAAI,MAAM,gDAAgD,OAAO,QAAQ,CAAC,GAAG;AAAA,IACrF;AACA,QAAI,UAAU;AACd,QAAI,QAA8C;AAClD,QAAI;AACJ,QAAI;AAEJ,UAAM,YAAY,MAAc,kBAAkB,MAAM;AAExD,UAAM,OAAO,YAA2B;AACtC,UAAI,QAAS;AACb,UAAI;AACF,cAAM,OAAQ,wBAAa,UAAU;AACrC,cAAM,MAAM,MAAM,KAAK,MAAM,QAAQ,OAAO,IAAI;AAChD,YAAI,QAAS;AACb,cAAM,OAAO,IAAI,QAAQ,IAAI,MAAM,KAAK;AACxC,YAAI,aAAa,UAAa,SAAS,UAAa,aAAa,MAAM;AACrE,kBAAQ,aAAa;AAAA,QACvB,OAAO;AACL,cAAI,CAAC,IAAI,IAAI;AACX,kBAAM,wBAAwB,IAAI,QAAQ,CAAC,GAAG,qBAAqB;AAAA,UACrE;AACA,gBAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,qBAAW;AACX,kBAAQ,SAAS,IAAI;AAAA,QACvB;AAAA,MACF,SAAS,KAAK;AACZ,gBAAQ,UAAU,GAAG;AAAA,MACvB,UAAE;AACA,YAAI,CAAC,SAAS;AACZ,kBAAQ,WAAW,MAAM;AAAE,iBAAK,KAAK;AAAA,UAAG,GAAG,QAAQ;AAAA,QACrD;AAAA,MACF;AAAA,IACF;AAEA,SAAK,KAAK;AAEV,WAAO;AAAA,MACL,OAAa;AACX,kBAAU;AACV,YAAI,UAAU,MAAM;AAClB,uBAAa,KAAK;AAClB,kBAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,MACJ,QACiB;AAGjB,UAAM,OAAO,oBAAoB,wBAAwB;AAAA,MACvD,OAAO;AAAA,MACP,OAAO;AAAA,MACP,MAAM,QAAQ;AAAA,MACd,GAAG,QAAQ;AAAA,MACX,QAAQ,QAAQ;AAAA;AAAA,MAEhB,OAAO,QAAQ;AAAA,IACjB,CAAC;AACD,UAAM,MAAM,MAAM,KAAK,MAAM,QAAQ,OAAO,IAAI;AAChD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC3C,YAAM,wBAAwB,IAAI,QAAQ,GAAG,cAAc;AAAA,IAC7D;AACA,UAAM,cAAc,IAAI,QAAQ,IAAI,sBAAsB;AAC1D,WAAO,cAAc,SAAS,aAAa,EAAE,IAAI;AAAA,EACnD;AAAA;AAAA,EAGA,MAAM,aACJ,UACA,OAC4B;AAC5B,UAAM,MAAM,MAAM,KAAK,MAAM;AAAA,MAC3B;AAAA,MACA,0BAA0B,mBAAmB,QAAQ,IAAI;AAAA,MACzD;AAAA,IACF;AACA,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC3C,YAAM,wBAAwB,IAAI,QAAQ,GAAG,eAAe;AAAA,IAC9D;AACA,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AAAA;AAAA,EAGA,MAAM,aAAa,UAA8C;AAC/D,UAAM,MAAM,MAAM,KAAK,MAAM;AAAA,MAC3B;AAAA,MACA,0BAA0B,mBAAmB,QAAQ;AAAA,IACvD;AACA,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC3C,YAAM,wBAAwB,IAAI,QAAQ,GAAG,eAAe;AAAA,IAC9D;AACA,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,WAAoC;AACxC,WAAO,KAAK,SAAS,qBAAqB,oBAAoB;AAAA,EAChE;AAAA;AAAA,EAGA,MAAM,QAAQ,UAAoD;AAChE,WAAO,KAAK;AAAA,MACV,uBAAuB,mBAAmB,QAAQ;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,gBAAwC;AAC5C,WAAO,KAAK,SAAS,0BAA0B,yBAAyB;AAAA,EAC1E;AAAA;AAAA,EAGA,MAAM,aAAa,UAAoD;AACrE,WAAO,KAAK;AAAA,MACV,4BAA4B,mBAAmB,QAAQ;AAAA,MACvD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA,EAKA,MAAM,oBACJ,QACoC;AAGpC,WAAO,KAAK;AAAA,MACV;AAAA,QACE;AAAA,QACA;AAAA,MACF;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,kBAAkB,UAAoD;AAC1E,WAAO,KAAK;AAAA,MACV,mCAAmC,mBAAmB,QAAQ;AAAA,MAC9D;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA,EAKA,MAAM,YACJ,UACkC;AAClC,WAAO,KAAK;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,YACJ,UAC6C;AAC7C,WAAO,KAAK;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,YACJ,UACkC;AAClC,WAAO,KAAK;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,YAAY,WAAuD;AACvE,WAAO,KAAK;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA,EAKA,MAAc,SAAY,MAAc,eAAmC;AACzE,UAAM,MAAM,MAAM,KAAK,MAAM,QAAQ,OAAO,IAAI;AAChD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC3C,YAAM,wBAAwB,IAAI,QAAQ,GAAG,aAAa;AAAA,IAC5D;AACA,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB;AAAA;AAAA,EAGA,MAAc,UACZ,MACA,MACA,eACY;AACZ,UAAM,MAAM,MAAM,KAAK,MAAM,QAAQ,QAAQ,MAAM,IAAI;AACvD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC3C,YAAM,wBAAwB,IAAI,QAAQ,GAAG,aAAa;AAAA,IAC5D;AACA,QAAI,IAAI,WAAW,IAAK,QAAO,CAAC;AAChC,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,QACJ,QACA,MACA,MACkB;AAClB,UAAM,MAAM,MAAM,KAAK,MAAM,QAAQ,QAAQ,MAAM,IAAI;AACvD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC3C,YAAM,wBAAwB,IAAI,QAAQ,GAAG,qBAAqB,IAAI,MAAM;AAAA,IAC9E;AACA,QAAI,IAAI,WAAW,IAAK,QAAO;AAC/B,QAAI,IAAI,QAAQ,IAAI,gBAAgB,MAAM,IAAK,QAAO;AACtD,UAAM,KAAK,IAAI,QAAQ,IAAI,cAAc,KAAK;AAC9C,QAAI,CAAC,GAAI,QAAO;AAChB,QAAI,GAAG,QAAQ,MAAM,MAAM,IAAI;AAG7B,YAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,UAAI,CAAC,KAAM,QAAO;AAClB,aAAO,KAAK,MAAM,IAAI;AAAA,IACxB;AACA,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,WACJ,QACA,MACA,MACmB;AACnB,WAAO,KAAK,MAAM,QAAQ,QAAQ,MAAM,IAAI;AAAA,EAC9C;AAAA;AAAA;AAAA,EAKA,MAAM,UAAyB;AAC7B,WAAO,KAAK,IAAI,QAAQ;AAAA,EAC1B;AAAA;AAAA,EAGA,MAAM,YAA2B;AAC/B,WAAO,KAAK,IAAI,UAAU;AAAA,EAC5B;AAAA;AAAA,EAGA,aAAmB;AACjB,SAAK,IAAI,WAAW;AAAA,EACtB;AAAA;AAAA,EAGA,cAAuB;AACrB,WAAO,KAAK,IAAI,YAAY;AAAA,EAC9B;AAAA;AAAA,EAGA,UAAU,SAAkC;AAC1C,SAAK,IAAI,UAAU,OAAO;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAM,qBAAsC;AAC1C,UAAM,MAAM,MAAM,MAAM,KAAK,MAAM,UAAU,sBAAsB;AACnE,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAK,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC5C,YAAM;AAAA,QACJ,IAAI;AAAA,QACJ;AAAA,QACA,yCAAyC,IAAI;AAAA,MAC/C;AAAA,IACF;AACA,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA8BA,MAAM,iBACJ,SAC2B;AAE3B,QACE,OAAO,cAAc,eACrB,CAAC,UAAU,iBACX,OAAO,WAAW,eAClB,CAAC,OAAO,aACR;AACA,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM,eACJ,SAAS,6BAA8B,MAAM,UAAU,cAAc;AACvE,UAAM,YAAY,SAAS,wBAAyB,MAAM,KAAK,mBAAmB;AAClF,UAAM,uBAAuB,sBAAsB,SAAS;AAC5D,UAAM,WAAoC,MAAM,aAAa,YAAY,gBAAgB;AACzF,QAAI,UAAU;AACZ,UAAI,4BAA4B,UAAU,oBAAoB,EAAG,QAAO;AACxE,YAAM,SAAS,YAAY;AAAA,IAC7B;AACA,WAAO,aAAa,YAAY,UAAU,EAAE,iBAAiB,MAAM,qBAAqB,CAAC;AAAA,EAE3F;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiCA,MAAM,4BACJ,SAC4C;AAC5C,QAAI,CAAC,WAAY,QAAQ,aAAa,YAAY,QAAQ,aAAa,WAAY;AACjF,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,QAAI,QAAQ,aAAa,YAAY,CAAC,QAAQ,SAAS,UAAU,QAAQ;AACvE,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,QAAI,QAAQ,aAAa,aAAa,CAAC,QAAQ,UAAU,QAAQ;AAC/D,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,aACJ,QAAQ,cACR;AAAA,MACE,KAAK,MAAM;AAAA,MACX,KAAK,MAAM;AAAA,MACX,QAAQ;AAAA,IACV;AAIF,6CAAyC,UAAU;AACnD,UAAM,WAAW,KAAK,yBAAyB,IAAI,UAAU;AAC7D,QAAI,SAAU,QAAO;AAErB,UAAM,MAAM,KAAK,iCAAiC,SAAS,UAAU,EAAE,QAAQ,MAAM;AACnF,UAAI,KAAK,yBAAyB,IAAI,UAAU,MAAM,KAAK;AACzD,aAAK,yBAAyB,OAAO,UAAU;AAAA,MACjD;AAAA,IACF,CAAC;AACD,SAAK,yBAAyB,IAAI,YAAY,GAAG;AACjD,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,iCACZ,SACA,YAC4C;AAC5C,UAAM,mBAAmB,MAAM,KAAK,iBAAiB;AAAA,MACnD,sBAAsB,QAAQ;AAAA,MAC9B,2BAA2B,QAAQ;AAAA,IACrC,CAAC;AACD,UAAM,WAAmC;AAAA,MACvC,GAAI,QAAQ,QAAQ,UAAa,EAAE,KAAK,QAAQ,IAAI;AAAA,MACpD,GAAI,QAAQ,YAAY,UAAa,EAAE,SAAS,QAAQ,QAAQ;AAAA,MAChE,GAAI,QAAQ,UAAU,UAAa,EAAE,OAAO,QAAQ,MAAM;AAAA,IAC5D;AACA,UAAM,UAAU,0BAA0B,UAAU;AACpD,QAAI,SAAS;AACX,YAAM,iBAAiB,kCAAkC,QAAQ,QAAQ;AACzE,YAAM,WAAW,GAAG,cAAc,IAAI,mBAAmB,OAAO,CAAC;AACjE,UAAI;AACF,cAAM,WAAY,MAAM,KAAK,QAAQ,OAAO,QAAQ;AACpD,cAAM,iBAAiB,0BAA0B,QAAQ,UAAU,QAAQ;AAC3E,YAAI,kBAAkB,mBAAmB,iBAAiB,UAAU;AAClE,gBAAM,KAAK;AAAA,YACT;AAAA,YACA;AAAA,YACA,sBAAsB,QAAQ,UAAU,kBAAkB,QAAQ;AAAA,UACpE;AAAA,QACF;AACA,eAAO,EAAE,gBAAgB,SAAS,SAAS,MAAM;AAAA,MACnD,SAAS,KAAK;AACZ,YAAI,EAAE,eAAe,eAAgB,OAAM;AAC3C,mCAA2B,UAAU;AAAA,MACvC;AAAA,IACF;AAEA,UAAM,aAAa,kCAAkC,QAAQ,QAAQ;AACrE,UAAM,OAAO,uBAAuB,SAAS,kBAAkB,QAAQ;AACvE,UAAM,MAAM,MAAM,KAAK,WAAW,QAAQ,YAAY,IAAI;AAC1D,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAK,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC5C,YAAM,wBAAwB,IAAI,QAAQ,GAAG,qBAAqB,IAAI,MAAM;AAAA,IAC9E;AACA,UAAM,iBAAiB;AAAA,MACrB,IAAI,QAAQ,IAAI,UAAU;AAAA,MAC1B,QAAQ;AAAA,IACV;AACA,+BAA2B,YAAY,cAAc;AACrD,WAAO,EAAE,gBAAgB,SAAS,KAAK;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,mBAAmB,cAA4D;AAEnF,QAAI,OAAO,cAAc,eAAe,CAAC,UAAU,cAAe,QAAO;AACzE,UAAM,MAAM,gBAAiB,MAAM,UAAU,cAAc;AAC3D,UAAM,WAAoC,MAAM,IAAI,YAAY,gBAAgB;AAChF,QAAI,CAAC,SAAU,QAAO;AACtB,WAAO,SAAS,YAAY;AAAA,EAE9B;AACF;AAGA,IAAO,gBAAQ;AAMf,IAAI,OAAO,WAAW,aAAa;AAEjC,SAAO,WAAW;AACpB;AAYA,SAAS,kBAAkB,QAAoC;AAC7D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;AAQO,SAAS,oBACd,MACA,QACQ;AACR,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,KAAK,IAAI,gBAAgB;AAC/B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,QAAI,UAAU,UAAa,UAAU,KAAM;AAC3C,QAAI,OAAO,UAAU,WAAW;AAC9B,SAAG,OAAO,KAAK,QAAQ,SAAS,OAAO;AAAA,IACzC,WAAW,OAAO,UAAU,UAAU;AACpC,SAAG,OAAO,KAAK,OAAO,KAAK,CAAC;AAAA,IAC9B,WAAW,OAAO,UAAU,UAAU;AACpC,UAAI,UAAU,GAAI;AAClB,SAAG,OAAO,KAAK,KAAK;AAAA,IACtB,OAAO;AAEL;AAAA,IACF;AAAA,EACF;AACA,QAAM,KAAK,GAAG,SAAS;AACvB,SAAO,KAAK,GAAG,IAAI,IAAI,EAAE,KAAK;AAChC;AAEA,SAAS,kCAAkC,UAA2C;AACpF,SAAO,aAAa,WAAW,sBAAsB;AACvD;AAEA,SAAS,2BACP,UACA,UACQ;AACR,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,cAAc,4DAA4D;AAAA,EACtF;AACA,QAAM,SACJ,aAAa,WAAW,uBAAuB;AACjD,QAAM,MAAM,SAAS,QAAQ,MAAM;AACnC,QAAM,MAAM,OAAO,IAAI,SAAS,MAAM,MAAM,OAAO,MAAM,IAAI,SAAS,MAAM,GAAG,EAAE,IAAI,KAAK;AAE1F,QAAM,KAAK,mBAAmB,IAAI,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC,CAAC;AAC7D,MAAI,CAAC,IAAI;AACP,UAAM,IAAI,cAAc,oDAAoD,QAAQ;AAAA,EACtF;AACA,SAAO;AACT;AAEA,SAAS,0BACP,UACA,MACoB;AACpB,QAAM,eAAe,KAAK;AAC1B,MAAI,CAAC,aAAc,QAAO;AAC1B,MAAI,aAAa,UAAU;AACzB,UAAM,UAAU,aAAa;AAC7B,WAAO,SAAS;AAAA,EAClB;AACA,QAAM,WAAW,aAAa;AAC9B,SAAO,UAAU;AACnB;AAEA,SAAS,uBACP,SACA,kBACA,UACyB;AACzB,QAAM,cAAc,QAAQ,eAAe;AAC3C,MAAI,QAAQ,aAAa,UAAU;AACjC,WAAO;AAAA,MACL;AAAA,MACA,SAAS,QAAQ;AAAA,MACjB,cAAc;AAAA,QACZ,SAAS,4BAA4B,kBAAkB,QAAQ;AAAA,MACjE;AAAA,IACF;AAAA,EACF;AACA,QAAM,EAAE,KAAK,QAAQ,IAAI,wBAAwB,kBAAkB,QAAQ;AAC3E,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,UAAU,QAAQ;AAAA,IAClB,GAAI,QAAQ,qBAAqB,EAAE,mBAAmB,QAAQ,kBAAkB;AAAA,IAChF,GAAI,QAAQ,KAAK,EAAE,GAAG,QAAQ,EAAE;AAAA,IAChC,cAAc;AAAA,MACZ,UAAU,EAAE,KAAK,QAAQ,oBAAoB,UAAU,WAAW,QAAQ;AAAA,IAC5E;AAAA,EACF;AACF;AAEA,SAAS,sBACP,UACA,kBACA,UACyB;AACzB,MAAI,aAAa,UAAU;AACzB,WAAO;AAAA,MACL,cAAc;AAAA,QACZ,SAAS,4BAA4B,kBAAkB,QAAQ;AAAA,MACjE;AAAA,IACF;AAAA,EACF;AACA,QAAM,EAAE,KAAK,QAAQ,IAAI,wBAAwB,kBAAkB,QAAQ;AAC3E,SAAO;AAAA,IACL,cAAc;AAAA,MACZ,UAAU,EAAE,KAAK,QAAQ,oBAAoB,UAAU,WAAW,QAAQ;AAAA,IAC5E;AAAA,EACF;AACF;AAEA,SAAS,0BAA0B,YAAmC;AACpE,MAAI;AACF,QAAI,OAAO,iBAAiB,YAAa,QAAO;AAChD,WAAO,aAAa,QAAQ,UAAU;AAAA,EACxC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,2BAA2B,YAAoB,gBAA8B;AACpF,MAAI;AACF,QAAI,OAAO,iBAAiB,aAAa;AACvC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,iBAAa,QAAQ,YAAY,cAAc;AAAA,EACjD,SAAS,KAAK;AACZ,QAAI,eAAe,cAAe,OAAM;AACxC,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;AAOA,SAAS,yCAAyC,YAA0B;AAC1E,QAAM,WAAW,aAAa;AAC9B,MAAI;AACF,QAAI,OAAO,iBAAiB,aAAa;AACvC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,iBAAa,QAAQ,UAAU,GAAG;AAClC,QAAI,aAAa,QAAQ,QAAQ,MAAM,KAAK;AAC1C,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,iBAAa,WAAW,QAAQ;AAAA,EAClC,SAAS,KAAK;AACZ,QAAI;AACF,UAAI,OAAO,iBAAiB,YAAa,cAAa,WAAW,QAAQ;AAAA,IAC3E,QAAQ;AAAA,IAER;AACA,QAAI,eAAe,cAAe,OAAM;AACxC,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,2BAA2B,YAA0B;AAC5D,MAAI;AACF,QAAI,OAAO,iBAAiB,YAAa;AACzC,iBAAa,WAAW,UAAU;AAAA,EACpC,QAAQ;AAAA,EAER;AACF;",
|
|
6
6
|
"names": ["token"]
|
|
7
7
|
}
|