@polyester/sdk 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/dist/catalogs/readers.d.ts.map +1 -1
- package/dist/catalogs/readers.js +22 -0
- package/dist/catalogs/readers.js.map +1 -1
- package/dist/core-client.d.ts.map +1 -1
- package/dist/core-client.js +49 -15
- package/dist/core-client.js.map +1 -1
- package/dist/environment.d.ts.map +1 -1
- package/dist/environment.js +3 -2
- package/dist/environment.js.map +1 -1
- package/dist/realtime/client.d.ts.map +1 -1
- package/dist/realtime/client.js +13 -5
- package/dist/realtime/client.js.map +1 -1
- package/dist/realtime/ready-gated-subscription.js +68 -0
- package/dist/realtime/ready-gated-subscription.js.map +1 -0
- package/dist/realtime/snapshot-then-stream.js +19 -10
- package/dist/realtime/snapshot-then-stream.js.map +1 -1
- package/dist/server-client.d.ts +3 -1
- package/dist/server-client.d.ts.map +1 -1
- package/dist/server-client.js +9 -4
- package/dist/server-client.js.map +1 -1
- package/dist/services/auth/session.d.ts.map +1 -1
- package/dist/services/auth/session.js +4 -1
- package/dist/services/auth/session.js.map +1 -1
- package/dist/services/auth/token-storage.js +2 -2
- package/dist/services/auth/token-storage.js.map +1 -1
- package/dist/services/balances/balances.js +5 -8
- package/dist/services/balances/balances.js.map +1 -1
- package/dist/services/candles/candles.js +9 -12
- package/dist/services/candles/candles.js.map +1 -1
- package/dist/services/heatmap/heatmap.js +5 -8
- package/dist/services/heatmap/heatmap.js.map +1 -1
- package/dist/services/lifecycle/lifecycle.schemas.d.ts +42 -42
- package/dist/services/lifecycle/lifecycle.schemas.js +1 -1
- package/dist/services/market-data/market-data.d.ts.map +1 -1
- package/dist/services/market-data/market-data.js +5 -8
- package/dist/services/market-data/market-data.js.map +1 -1
- package/dist/services/market-data/market-data.schemas.d.ts.map +1 -1
- package/dist/services/market-data/market-data.schemas.js +3 -2
- package/dist/services/market-data/market-data.schemas.js.map +1 -1
- package/dist/services/market-overview/market-overview.d.ts.map +1 -1
- package/dist/services/market-overview/market-overview.js +7 -13
- package/dist/services/market-overview/market-overview.js.map +1 -1
- package/dist/services/orderbook/orderbook.d.ts.map +1 -1
- package/dist/services/orderbook/orderbook.js +18 -11
- package/dist/services/orderbook/orderbook.js.map +1 -1
- package/dist/services/orders/orders-batch.schemas.d.ts +6 -22
- package/dist/services/orders/orders-batch.schemas.d.ts.map +1 -1
- package/dist/services/orders/orders-batch.schemas.js +23 -20
- package/dist/services/orders/orders-batch.schemas.js.map +1 -1
- package/dist/services/orders/orders-identifiers.schemas.js +6 -1
- package/dist/services/orders/orders-identifiers.schemas.js.map +1 -1
- package/dist/services/orders/orders-input.schemas.d.ts +11 -43
- package/dist/services/orders/orders-input.schemas.d.ts.map +1 -1
- package/dist/services/orders/orders-input.schemas.js +10 -11
- package/dist/services/orders/orders-input.schemas.js.map +1 -1
- package/dist/services/orders/orders-modify.schemas.d.ts +1 -25
- package/dist/services/orders/orders-modify.schemas.d.ts.map +1 -1
- package/dist/services/orders/orders-modify.schemas.js +2 -4
- package/dist/services/orders/orders-modify.schemas.js.map +1 -1
- package/dist/services/orders/orders-output.schemas.d.ts +165 -27
- package/dist/services/orders/orders-output.schemas.d.ts.map +1 -1
- package/dist/services/orders/orders-output.schemas.js +1 -1
- package/dist/services/orders/orders-output.schemas.js.map +1 -1
- package/dist/services/orders/orders-risk.schemas.d.ts +1 -20
- package/dist/services/orders/orders-risk.schemas.d.ts.map +1 -1
- package/dist/services/orders/orders-risk.schemas.js +52 -15
- package/dist/services/orders/orders-risk.schemas.js.map +1 -1
- package/dist/services/orders/orders.codecs.js +12 -1
- package/dist/services/orders/orders.codecs.js.map +1 -1
- package/dist/services/orders/orders.d.ts.map +1 -1
- package/dist/services/orders/orders.js +6 -9
- package/dist/services/orders/orders.js.map +1 -1
- package/dist/services/subaccounts/subaccounts.schemas.d.ts +3 -3
- package/dist/services/trades/trades.js +5 -8
- package/dist/services/trades/trades.js.map +1 -1
- package/dist/services/trailing-oneof-inputs.js +3 -3
- package/dist/services/trailing-oneof-inputs.js.map +1 -1
- package/dist/services/transfers/index.d.ts +2 -2
- package/dist/services/transfers/transfers.d.ts +1 -2
- package/dist/services/transfers/transfers.d.ts.map +1 -1
- package/dist/services/transfers/transfers.js +8 -13
- package/dist/services/transfers/transfers.js.map +1 -1
- package/dist/services/transfers/transfers.schemas.d.ts +5 -5
- package/dist/services/transfers/transfers.schemas.d.ts.map +1 -1
- package/dist/services/transfers/transfers.schemas.js +31 -33
- package/dist/services/transfers/transfers.schemas.js.map +1 -1
- package/dist/services/triggers/trigger-input.schemas.d.ts +21 -8
- package/dist/services/triggers/trigger-input.schemas.d.ts.map +1 -1
- package/dist/services/triggers/trigger-input.schemas.js +20 -43
- package/dist/services/triggers/trigger-input.schemas.js.map +1 -1
- package/dist/services/triggers/triggers-output.schemas.d.ts +13 -13
- package/dist/services/triggers/triggers-output.schemas.js +5 -5
- package/dist/services/triggers/triggers-output.schemas.js.map +1 -1
- package/dist/services/triggers/triggers.d.ts.map +1 -1
- package/dist/services/triggers/triggers.js +10 -23
- package/dist/services/triggers/triggers.js.map +1 -1
- package/dist/services/zipper/zipper.d.ts.map +1 -1
- package/dist/services/zipper/zipper.js +15 -18
- package/dist/services/zipper/zipper.js.map +1 -1
- package/dist/shared/connect-error-mapping.d.ts.map +1 -1
- package/dist/shared/connect-error-mapping.js +9 -2
- package/dist/shared/connect-error-mapping.js.map +1 -1
- package/dist/shared/decimal-surface.d.ts.map +1 -1
- package/dist/shared/decimal-surface.js +40 -17
- package/dist/shared/decimal-surface.js.map +1 -1
- package/dist/shared/transports.d.ts.map +1 -1
- package/dist/shared/transports.js +33 -11
- package/dist/shared/transports.js.map +1 -1
- package/dist/utils/base58-id.js +1 -2
- package/dist/utils/base58-id.js.map +1 -1
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +4 -3
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/event-emitter.d.ts.map +1 -1
- package/dist/utils/event-emitter.js +6 -3
- package/dist/utils/event-emitter.js.map +1 -1
- package/dist/utils/jwt.d.ts.map +1 -1
- package/dist/utils/jwt.js +2 -2
- package/dist/utils/jwt.js.map +1 -1
- package/dist/utils/numbers.js +20 -3
- package/dist/utils/numbers.js.map +1 -1
- package/dist/utils/timestamp.js +27 -16
- package/dist/utils/timestamp.js.map +1 -1
- package/dist/utils/u128.js +6 -18
- package/dist/utils/u128.js.map +1 -1
- package/package.json +1 -1
package/dist/environment.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"environment.js","names":[],"sources":["../src/environment.ts"],"sourcesContent":["import type { SafeVersion } from \"permissionless/accounts\";\nimport { ConfigurationError } from \"./shared/errors.js\";\nimport type { Address, Chain } from \"viem\";\nimport { checksumEvmAddress, evmUtf8ToBytes, isEvmAddress, keccak256Hex } from \"./utils/evm.js\";\n\nexport interface PolyesterEntryPointConfig {\n readonly address: Address;\n readonly version: \"0.7\";\n}\n\nexport interface PolyesterSafeDeploymentConfig {\n readonly version: SafeVersion;\n readonly safeModuleSetupAddress: Address;\n readonly safe4337ModuleAddress: Address;\n readonly safeProxyFactoryAddress: Address;\n readonly safeSingletonAddress: Address;\n readonly multiSendAddress: Address;\n readonly multiSendCallOnlyAddress?: Address;\n}\n\nexport interface PolyesterAccountAbstractionEnvironment {\n readonly bundlerUrl: string;\n readonly paymasterUrl: string;\n readonly entryPoint: PolyesterEntryPointConfig;\n readonly safe: PolyesterSafeDeploymentConfig;\n}\n\nexport interface PolyesterContractsEnvironment {\n readonly tradingGatewayAddress: Address;\n}\n\nexport interface PolyesterEnvironment {\n readonly name: string;\n readonly fingerprint: string;\n readonly apiUrl: string;\n readonly websocketUrl: string;\n readonly rpcUrl: string;\n readonly chain: Chain;\n readonly accountAbstraction: PolyesterAccountAbstractionEnvironment;\n readonly contracts: PolyesterContractsEnvironment;\n}\n\nexport interface CreatePolyesterEnvironmentParams {\n readonly name: string;\n readonly apiUrl: string;\n readonly websocketUrl: string;\n readonly rpcUrl: string;\n readonly chain: Chain;\n readonly accountAbstraction: {\n readonly bundlerUrl: string;\n readonly paymasterUrl: string;\n readonly entryPoint: PolyesterEntryPointConfig;\n readonly safe: PolyesterSafeDeploymentConfig;\n };\n readonly contracts: PolyesterContractsEnvironment;\n}\n\nconst LOCAL_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"::1\", \"[::1]\"]);\n\nfunction requireObject(value: unknown, label: string): asserts value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new ConfigurationError(`${label} must be an object.`);\n }\n}\n\nfunction requireNonEmptyString(value: unknown, label: string): asserts value is string {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new ConfigurationError(`${label} must be a non-empty string.`);\n }\n}\n\nfunction isLocalHost(hostname: string): boolean {\n return LOCAL_HOSTS.has(hostname);\n}\n\nfunction normalizeUrl(value: string, label: string, allowedProtocols: readonly string[]): string {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n throw new ConfigurationError(`${label} must be a valid URL.`);\n }\n\n if (!allowedProtocols.includes(url.protocol)) {\n throw new ConfigurationError(`${label} must use ${allowedProtocols.join(\" or \")}.`);\n }\n\n const insecureRemote =\n (url.protocol === \"http:\" || url.protocol === \"ws:\") && !isLocalHost(url.hostname);\n if (insecureRemote) {\n throw new ConfigurationError(`${label} must use a secure protocol for remote hosts.`);\n }\n\n url.hash = \"\";\n return url.toString().replace(/\\/+$/u, \"\");\n}\n\nfunction normalizeAddress(value: Address, label: string): Address {\n if (!isEvmAddress(value)) {\n throw new ConfigurationError(`${label} must be a valid address.`);\n }\n return checksumEvmAddress(value);\n}\n\nfunction normalizeEntryPoint(entryPoint: PolyesterEntryPointConfig): PolyesterEntryPointConfig {\n requireObject(entryPoint, \"accountAbstraction.entryPoint\");\n if (entryPoint.version !== \"0.7\") {\n throw new ConfigurationError(\"accountAbstraction.entryPoint.version must be 0.7.\");\n }\n return Object.freeze({\n address: normalizeAddress(entryPoint.address, \"accountAbstraction.entryPoint.address\"),\n version: entryPoint.version,\n });\n}\n\nfunction normalizeSafeConfig(safe: PolyesterSafeDeploymentConfig): PolyesterSafeDeploymentConfig {\n requireObject(safe, \"accountAbstraction.safe\");\n if (safe.version !== \"1.4.1\" && safe.version !== \"1.5.0\") {\n throw new ConfigurationError(\n 'accountAbstraction.safe.version must be either \"1.4.1\" or \"1.5.0\".',\n );\n }\n return Object.freeze({\n version: safe.version,\n safeModuleSetupAddress: normalizeAddress(\n safe.safeModuleSetupAddress,\n \"accountAbstraction.safe.safeModuleSetupAddress\",\n ),\n safe4337ModuleAddress: normalizeAddress(\n safe.safe4337ModuleAddress,\n \"accountAbstraction.safe.safe4337ModuleAddress\",\n ),\n safeProxyFactoryAddress: normalizeAddress(\n safe.safeProxyFactoryAddress,\n \"accountAbstraction.safe.safeProxyFactoryAddress\",\n ),\n safeSingletonAddress: normalizeAddress(\n safe.safeSingletonAddress,\n \"accountAbstraction.safe.safeSingletonAddress\",\n ),\n multiSendAddress: normalizeAddress(\n safe.multiSendAddress,\n \"accountAbstraction.safe.multiSendAddress\",\n ),\n multiSendCallOnlyAddress: safe.multiSendCallOnlyAddress\n ? normalizeAddress(\n safe.multiSendCallOnlyAddress,\n \"accountAbstraction.safe.multiSendCallOnlyAddress\",\n )\n : undefined,\n });\n}\n\nfunction normalizeChain(chain: Chain, rpcUrl: string): Chain {\n requireObject(chain, \"chain\");\n if (!Number.isInteger(chain.id) || chain.id <= 0) {\n throw new ConfigurationError(\"chain.id must be a positive integer.\");\n }\n requireNonEmptyString(chain.name, \"chain.name\");\n requireObject(chain.nativeCurrency, \"chain.nativeCurrency\");\n if (!Number.isInteger(chain.nativeCurrency.decimals) || chain.nativeCurrency.decimals < 0) {\n throw new ConfigurationError(\n \"chain.nativeCurrency.decimals must be a non-negative integer.\",\n );\n }\n requireNonEmptyString(chain.nativeCurrency.name, \"chain.nativeCurrency.name\");\n requireNonEmptyString(chain.nativeCurrency.symbol, \"chain.nativeCurrency.symbol\");\n requireObject(chain.rpcUrls, \"chain.rpcUrls\");\n requireObject(chain.rpcUrls.default, \"chain.rpcUrls.default\");\n if (!Array.isArray(chain.rpcUrls.default.http)) {\n throw new ConfigurationError(\"chain.rpcUrls.default.http must be an array.\");\n }\n\n // Shape parity with viem's defineChain: spread over undefined defaults.\n return Object.freeze({\n formatters: undefined,\n fees: undefined,\n serializers: undefined,\n ...chain,\n rpcUrls: {\n ...chain.rpcUrls,\n default: {\n ...chain.rpcUrls.default,\n http: [rpcUrl],\n },\n },\n });\n}\n\nfunction environmentFingerprint(input: {\n apiUrl: string;\n websocketUrl: string;\n rpcUrl: string;\n chainId: number;\n accountAbstraction: PolyesterAccountAbstractionEnvironment;\n contracts: PolyesterContractsEnvironment;\n}): string {\n return keccak256Hex(\n evmUtf8ToBytes(\n JSON.stringify({\n apiUrl: input.apiUrl,\n websocketUrl: input.websocketUrl,\n rpcUrl: input.rpcUrl,\n chainId: input.chainId,\n bundlerUrl: input.accountAbstraction.bundlerUrl,\n paymasterUrl: input.accountAbstraction.paymasterUrl,\n entryPoint: input.accountAbstraction.entryPoint,\n safe: input.accountAbstraction.safe,\n contracts: input.contracts,\n }),\n ),\n );\n}\n\n/**\n * Creates a complete SDK environment configuration from entrypoint and contract settings.\n */\nexport function createPolyesterEnvironment(\n params: CreatePolyesterEnvironmentParams,\n): PolyesterEnvironment {\n requireObject(params, \"Environment configuration\");\n requireNonEmptyString(params.name, \"name\");\n requireObject(params.accountAbstraction, \"accountAbstraction\");\n requireObject(params.contracts, \"contracts\");\n const apiUrl = normalizeUrl(params.apiUrl, \"apiUrl\", [\"https:\", \"http:\"]);\n const websocketUrl = normalizeUrl(params.websocketUrl, \"websocketUrl\", [\"wss:\", \"ws:\"]);\n const rpcUrl = normalizeUrl(params.rpcUrl, \"rpcUrl\", [\"https:\", \"http:\"]);\n const bundlerUrl = normalizeUrl(params.accountAbstraction.bundlerUrl, \"bundlerUrl\", [\n \"https:\",\n \"http:\",\n ]);\n const paymasterUrl = normalizeUrl(params.accountAbstraction.paymasterUrl, \"paymasterUrl\", [\n \"https:\",\n \"http:\",\n ]);\n const chain = normalizeChain(params.chain, rpcUrl);\n const accountAbstraction = Object.freeze({\n bundlerUrl,\n paymasterUrl,\n entryPoint: normalizeEntryPoint(params.accountAbstraction.entryPoint),\n safe: normalizeSafeConfig(params.accountAbstraction.safe),\n });\n const contracts = Object.freeze({\n tradingGatewayAddress: normalizeAddress(\n params.contracts.tradingGatewayAddress,\n \"contracts.tradingGatewayAddress\",\n ),\n });\n const fingerprint = environmentFingerprint({\n apiUrl,\n websocketUrl,\n rpcUrl,\n chainId: chain.id,\n accountAbstraction,\n contracts,\n });\n\n return Object.freeze({\n name: params.name,\n fingerprint,\n apiUrl,\n websocketUrl,\n rpcUrl,\n chain,\n accountAbstraction,\n contracts,\n });\n}\n\n/**\n * Parses a complete environment supplied to a public SDK client constructor.\n */\nexport function parsePolyesterEnvironment(environment: PolyesterEnvironment): PolyesterEnvironment {\n requireObject(environment, \"environment\");\n const parsed = createPolyesterEnvironment(environment);\n if (environment.fingerprint !== parsed.fingerprint) {\n throw new ConfigurationError(\n \"environment.fingerprint must match the environment configuration.\",\n );\n }\n return parsed;\n}\n\nexport const POLYESTER_TESTNET_ENVIRONMENT = createPolyesterEnvironment({\n name: \"polyester-testnet\",\n apiUrl: \"https://api-devnet.polyester.ai\",\n websocketUrl: \"wss://api-devnet.polyester.ai\",\n rpcUrl: \"https://rpc.polyester.tech\",\n chain: {\n id: 888168,\n name: \"Polyester Chain Testnet\",\n nativeCurrency: {\n decimals: 18,\n name: \"POL\",\n symbol: \"POL\",\n },\n rpcUrls: {\n default: {\n http: [\"https://rpc.polyester.tech\"],\n },\n },\n blockExplorers: {\n default: {\n name: \"Polyester Scan\",\n url: \"https://polyesterscan.com\",\n },\n },\n contracts: {\n multicall3: {\n address: \"0xF35A6AE5408fa1356064849D0BC3855f801aa6aC\",\n blockCreated: 563457,\n },\n },\n },\n accountAbstraction: {\n bundlerUrl: \"https://bundler.polyester.tech\",\n paymasterUrl: \"https://paymaster.polyester.tech\",\n entryPoint: {\n address: \"0x59a4B77766509c4507D79eFF8089474eC3daC174\",\n version: \"0.7\",\n },\n safe: {\n version: \"1.4.1\",\n safeModuleSetupAddress: \"0x80791683D9C079A37Debc67EaDdbFcBC6f0FF2bB\",\n safe4337ModuleAddress: \"0x0713FF3d4c1b4f177833a372b1e3cb977540EA11\",\n safeProxyFactoryAddress: \"0xF8F0F649Dd3bFa9095206691E9fb2356c26216dE\",\n safeSingletonAddress: \"0x92abEa238FEA8908c397cE65366ea9278f0AeC7A\",\n multiSendAddress: \"0x70C8a8CcB45a8E2589B0f019374fc923dA34E4c7\",\n multiSendCallOnlyAddress: \"0x375C86a08DA98d1944D7B3c736307A72186CcAf1\",\n },\n },\n contracts: {\n tradingGatewayAddress: \"0xD3fecf5D39131e23b6B0f872cA0a21c8A5a30932\",\n },\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"environment.js","names":[],"sources":["../src/environment.ts"],"sourcesContent":["import type { SafeVersion } from \"permissionless/accounts\";\nimport { ConfigurationError } from \"./shared/errors.js\";\nimport type { Address, Chain } from \"viem\";\nimport { checksumEvmAddress, evmUtf8ToBytes, isEvmAddress, keccak256Hex } from \"./utils/evm.js\";\n\nexport interface PolyesterEntryPointConfig {\n readonly address: Address;\n readonly version: \"0.7\";\n}\n\nexport interface PolyesterSafeDeploymentConfig {\n readonly version: SafeVersion;\n readonly safeModuleSetupAddress: Address;\n readonly safe4337ModuleAddress: Address;\n readonly safeProxyFactoryAddress: Address;\n readonly safeSingletonAddress: Address;\n readonly multiSendAddress: Address;\n readonly multiSendCallOnlyAddress?: Address;\n}\n\nexport interface PolyesterAccountAbstractionEnvironment {\n readonly bundlerUrl: string;\n readonly paymasterUrl: string;\n readonly entryPoint: PolyesterEntryPointConfig;\n readonly safe: PolyesterSafeDeploymentConfig;\n}\n\nexport interface PolyesterContractsEnvironment {\n readonly tradingGatewayAddress: Address;\n}\n\nexport interface PolyesterEnvironment {\n readonly name: string;\n readonly fingerprint: string;\n readonly apiUrl: string;\n readonly websocketUrl: string;\n readonly rpcUrl: string;\n readonly chain: Chain;\n readonly accountAbstraction: PolyesterAccountAbstractionEnvironment;\n readonly contracts: PolyesterContractsEnvironment;\n}\n\nexport interface CreatePolyesterEnvironmentParams {\n readonly name: string;\n readonly apiUrl: string;\n readonly websocketUrl: string;\n readonly rpcUrl: string;\n readonly chain: Chain;\n readonly accountAbstraction: {\n readonly bundlerUrl: string;\n readonly paymasterUrl: string;\n readonly entryPoint: PolyesterEntryPointConfig;\n readonly safe: PolyesterSafeDeploymentConfig;\n };\n readonly contracts: PolyesterContractsEnvironment;\n}\n\nconst LOCAL_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"::1\", \"[::1]\"]);\n\nfunction requireObject(value: unknown, label: string): asserts value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new ConfigurationError(`${label} must be an object.`);\n }\n}\n\nfunction requireNonEmptyString(value: unknown, label: string): asserts value is string {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new ConfigurationError(`${label} must be a non-empty string.`);\n }\n}\n\nfunction isLocalHost(hostname: string): boolean {\n return LOCAL_HOSTS.has(hostname);\n}\n\nfunction normalizeUrl(\n value: string,\n label: string,\n allowedProtocols: readonly string[],\n options?: { allowSearch?: boolean },\n): string {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n throw new ConfigurationError(`${label} must be a valid URL.`);\n }\n\n if (!allowedProtocols.includes(url.protocol)) {\n throw new ConfigurationError(`${label} must use ${allowedProtocols.join(\" or \")}.`);\n }\n\n const insecureRemote =\n (url.protocol === \"http:\" || url.protocol === \"ws:\") && !isLocalHost(url.hostname);\n if (insecureRemote) {\n throw new ConfigurationError(`${label} must use a secure protocol for remote hosts.`);\n }\n\n if (options?.allowSearch === false && url.search) {\n throw new ConfigurationError(`${label} must not include query parameters.`);\n }\n\n url.hash = \"\";\n return url.toString().replace(/\\/+$/u, \"\");\n}\n\nfunction normalizeAddress(value: Address, label: string): Address {\n if (!isEvmAddress(value)) {\n throw new ConfigurationError(`${label} must be a valid address.`);\n }\n return checksumEvmAddress(value);\n}\n\nfunction normalizeEntryPoint(entryPoint: PolyesterEntryPointConfig): PolyesterEntryPointConfig {\n requireObject(entryPoint, \"accountAbstraction.entryPoint\");\n if (entryPoint.version !== \"0.7\") {\n throw new ConfigurationError(\"accountAbstraction.entryPoint.version must be 0.7.\");\n }\n return Object.freeze({\n address: normalizeAddress(entryPoint.address, \"accountAbstraction.entryPoint.address\"),\n version: entryPoint.version,\n });\n}\n\nfunction normalizeSafeConfig(safe: PolyesterSafeDeploymentConfig): PolyesterSafeDeploymentConfig {\n requireObject(safe, \"accountAbstraction.safe\");\n if (safe.version !== \"1.4.1\" && safe.version !== \"1.5.0\") {\n throw new ConfigurationError(\n 'accountAbstraction.safe.version must be either \"1.4.1\" or \"1.5.0\".',\n );\n }\n return Object.freeze({\n version: safe.version,\n safeModuleSetupAddress: normalizeAddress(\n safe.safeModuleSetupAddress,\n \"accountAbstraction.safe.safeModuleSetupAddress\",\n ),\n safe4337ModuleAddress: normalizeAddress(\n safe.safe4337ModuleAddress,\n \"accountAbstraction.safe.safe4337ModuleAddress\",\n ),\n safeProxyFactoryAddress: normalizeAddress(\n safe.safeProxyFactoryAddress,\n \"accountAbstraction.safe.safeProxyFactoryAddress\",\n ),\n safeSingletonAddress: normalizeAddress(\n safe.safeSingletonAddress,\n \"accountAbstraction.safe.safeSingletonAddress\",\n ),\n multiSendAddress: normalizeAddress(\n safe.multiSendAddress,\n \"accountAbstraction.safe.multiSendAddress\",\n ),\n multiSendCallOnlyAddress: safe.multiSendCallOnlyAddress\n ? normalizeAddress(\n safe.multiSendCallOnlyAddress,\n \"accountAbstraction.safe.multiSendCallOnlyAddress\",\n )\n : undefined,\n });\n}\n\nfunction normalizeChain(chain: Chain, rpcUrl: string): Chain {\n requireObject(chain, \"chain\");\n if (!Number.isInteger(chain.id) || chain.id <= 0) {\n throw new ConfigurationError(\"chain.id must be a positive integer.\");\n }\n requireNonEmptyString(chain.name, \"chain.name\");\n requireObject(chain.nativeCurrency, \"chain.nativeCurrency\");\n if (!Number.isInteger(chain.nativeCurrency.decimals) || chain.nativeCurrency.decimals < 0) {\n throw new ConfigurationError(\n \"chain.nativeCurrency.decimals must be a non-negative integer.\",\n );\n }\n requireNonEmptyString(chain.nativeCurrency.name, \"chain.nativeCurrency.name\");\n requireNonEmptyString(chain.nativeCurrency.symbol, \"chain.nativeCurrency.symbol\");\n requireObject(chain.rpcUrls, \"chain.rpcUrls\");\n requireObject(chain.rpcUrls.default, \"chain.rpcUrls.default\");\n if (!Array.isArray(chain.rpcUrls.default.http)) {\n throw new ConfigurationError(\"chain.rpcUrls.default.http must be an array.\");\n }\n\n // Shape parity with viem's defineChain: spread over undefined defaults.\n return Object.freeze({\n formatters: undefined,\n fees: undefined,\n serializers: undefined,\n ...chain,\n rpcUrls: {\n ...chain.rpcUrls,\n default: {\n ...chain.rpcUrls.default,\n http: [rpcUrl],\n },\n },\n });\n}\n\nfunction environmentFingerprint(input: {\n apiUrl: string;\n websocketUrl: string;\n rpcUrl: string;\n chainId: number;\n accountAbstraction: PolyesterAccountAbstractionEnvironment;\n contracts: PolyesterContractsEnvironment;\n}): string {\n return keccak256Hex(\n evmUtf8ToBytes(\n JSON.stringify({\n apiUrl: input.apiUrl,\n websocketUrl: input.websocketUrl,\n rpcUrl: input.rpcUrl,\n chainId: input.chainId,\n bundlerUrl: input.accountAbstraction.bundlerUrl,\n paymasterUrl: input.accountAbstraction.paymasterUrl,\n entryPoint: input.accountAbstraction.entryPoint,\n safe: input.accountAbstraction.safe,\n contracts: input.contracts,\n }),\n ),\n );\n}\n\n/**\n * Creates a complete SDK environment configuration from entrypoint and contract settings.\n */\nexport function createPolyesterEnvironment(\n params: CreatePolyesterEnvironmentParams,\n): PolyesterEnvironment {\n requireObject(params, \"Environment configuration\");\n requireNonEmptyString(params.name, \"name\");\n requireObject(params.accountAbstraction, \"accountAbstraction\");\n requireObject(params.contracts, \"contracts\");\n const apiUrl = normalizeUrl(params.apiUrl, \"apiUrl\", [\"https:\", \"http:\"], {\n allowSearch: false,\n });\n const websocketUrl = normalizeUrl(params.websocketUrl, \"websocketUrl\", [\"wss:\", \"ws:\"]);\n const rpcUrl = normalizeUrl(params.rpcUrl, \"rpcUrl\", [\"https:\", \"http:\"]);\n const bundlerUrl = normalizeUrl(params.accountAbstraction.bundlerUrl, \"bundlerUrl\", [\n \"https:\",\n \"http:\",\n ]);\n const paymasterUrl = normalizeUrl(params.accountAbstraction.paymasterUrl, \"paymasterUrl\", [\n \"https:\",\n \"http:\",\n ]);\n const chain = normalizeChain(params.chain, rpcUrl);\n const accountAbstraction = Object.freeze({\n bundlerUrl,\n paymasterUrl,\n entryPoint: normalizeEntryPoint(params.accountAbstraction.entryPoint),\n safe: normalizeSafeConfig(params.accountAbstraction.safe),\n });\n const contracts = Object.freeze({\n tradingGatewayAddress: normalizeAddress(\n params.contracts.tradingGatewayAddress,\n \"contracts.tradingGatewayAddress\",\n ),\n });\n const fingerprint = environmentFingerprint({\n apiUrl,\n websocketUrl,\n rpcUrl,\n chainId: chain.id,\n accountAbstraction,\n contracts,\n });\n\n return Object.freeze({\n name: params.name,\n fingerprint,\n apiUrl,\n websocketUrl,\n rpcUrl,\n chain,\n accountAbstraction,\n contracts,\n });\n}\n\n/**\n * Parses a complete environment supplied to a public SDK client constructor.\n */\nexport function parsePolyesterEnvironment(environment: PolyesterEnvironment): PolyesterEnvironment {\n requireObject(environment, \"environment\");\n const parsed = createPolyesterEnvironment(environment);\n if (environment.fingerprint !== parsed.fingerprint) {\n throw new ConfigurationError(\n \"environment.fingerprint must match the environment configuration.\",\n );\n }\n return parsed;\n}\n\nexport const POLYESTER_TESTNET_ENVIRONMENT = createPolyesterEnvironment({\n name: \"polyester-testnet\",\n apiUrl: \"https://api-devnet.polyester.ai\",\n websocketUrl: \"wss://api-devnet.polyester.ai\",\n rpcUrl: \"https://rpc.polyester.tech\",\n chain: {\n id: 888168,\n name: \"Polyester Chain Testnet\",\n nativeCurrency: {\n decimals: 18,\n name: \"POL\",\n symbol: \"POL\",\n },\n rpcUrls: {\n default: {\n http: [\"https://rpc.polyester.tech\"],\n },\n },\n blockExplorers: {\n default: {\n name: \"Polyester Scan\",\n url: \"https://polyesterscan.com\",\n },\n },\n contracts: {\n multicall3: {\n address: \"0xF35A6AE5408fa1356064849D0BC3855f801aa6aC\",\n blockCreated: 563457,\n },\n },\n },\n accountAbstraction: {\n bundlerUrl: \"https://bundler.polyester.tech\",\n paymasterUrl: \"https://paymaster.polyester.tech\",\n entryPoint: {\n address: \"0x59a4B77766509c4507D79eFF8089474eC3daC174\",\n version: \"0.7\",\n },\n safe: {\n version: \"1.4.1\",\n safeModuleSetupAddress: \"0x80791683D9C079A37Debc67EaDdbFcBC6f0FF2bB\",\n safe4337ModuleAddress: \"0x0713FF3d4c1b4f177833a372b1e3cb977540EA11\",\n safeProxyFactoryAddress: \"0xF8F0F649Dd3bFa9095206691E9fb2356c26216dE\",\n safeSingletonAddress: \"0x92abEa238FEA8908c397cE65366ea9278f0AeC7A\",\n multiSendAddress: \"0x70C8a8CcB45a8E2589B0f019374fc923dA34E4c7\",\n multiSendCallOnlyAddress: \"0x375C86a08DA98d1944D7B3c736307A72186CcAf1\",\n },\n },\n contracts: {\n tradingGatewayAddress: \"0xD3fecf5D39131e23b6B0f872cA0a21c8A5a30932\",\n },\n});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","names":[],"sources":["../../src/realtime/client.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"client.d.ts","names":[],"sources":["../../src/realtime/client.ts"],"mappings":";;;;UAsEiB;EACb,cAAc;EACd;;UAGa;EACb;EACA;EACA;EACA,kBAAkB,SAAS,wBAAwB,QAAQ,eAAe;EAC1E"}
|
package/dist/realtime/client.js
CHANGED
|
@@ -5,14 +5,22 @@ import { decodeProtoFrame } from "../utils/streams.js";
|
|
|
5
5
|
import { fromBinary } from "@bufbuild/protobuf";
|
|
6
6
|
//#region src/realtime/client.ts
|
|
7
7
|
const realtimeFetch = makeFetch();
|
|
8
|
+
const defaultCentrifugeModuleLoader = () => import("centrifuge/build/protobuf");
|
|
8
9
|
let centrifugeCtorPromise = null;
|
|
9
10
|
let loadedCentrifugeCtor = null;
|
|
11
|
+
let centrifugeModuleLoader = defaultCentrifugeModuleLoader;
|
|
10
12
|
function loadCentrifuge() {
|
|
11
|
-
if (import.meta.env?.SSR) return Promise.reject(/* @__PURE__ */ new Error("Realtime subscriptions are browser-only during SSR."));
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
if (centrifugeModuleLoader === defaultCentrifugeModuleLoader && import.meta.env?.SSR) return Promise.reject(/* @__PURE__ */ new Error("Realtime subscriptions are browser-only during SSR."));
|
|
14
|
+
if (!centrifugeCtorPromise) {
|
|
15
|
+
const retryableLoad = Promise.resolve().then(centrifugeModuleLoader).then((mod) => {
|
|
16
|
+
loadedCentrifugeCtor = mod.Centrifuge;
|
|
17
|
+
return mod.Centrifuge;
|
|
18
|
+
}, (error) => {
|
|
19
|
+
if (centrifugeCtorPromise === retryableLoad) centrifugeCtorPromise = null;
|
|
20
|
+
throw error;
|
|
21
|
+
});
|
|
22
|
+
centrifugeCtorPromise = retryableLoad;
|
|
23
|
+
}
|
|
16
24
|
return centrifugeCtorPromise;
|
|
17
25
|
}
|
|
18
26
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["#config","#getAuthHeaders","#sharedSubs","#channelKind","#emitSubscriptionError","#publicClient","#connectionHandlers","#privateClient","#emitConnectionTokenError","#createPublicClient","#hasAuth","#createPrivateClient","#assertChannelAuth","#ensurePrivateClient","#ensurePublicClient","#attachSubscriptionNow","#attachSubscriptionWhenLoaded","#ensureClientForChannel","#subscriptionOpts","#pendingTeardowns","#attachSubscription","#callErrorHandler","#disconnectClientIfIdle","#hasChannelsForKind","#teardownSubscription","#getOrCreateSubscription","#callConsumerHandler","#disconnect","#disconnectPrivate"],"sources":["../../src/realtime/client.ts"],"sourcesContent":["import { fromBinary, type DescMessage, type MessageShape } from \"@bufbuild/protobuf\";\nimport type {\n Centrifuge,\n SubscriptionErrorContext,\n PublicationContext,\n Subscription,\n} from \"centrifuge/build/protobuf\";\nimport type * as CentrifugeModule from \"centrifuge/build/protobuf\";\nimport {\n createSdkSubscriptionErrorContext,\n fromCentrifugeSubscriptionError,\n type SdkSubscriptionErrorContext,\n} from \"../shared/subscription-errors.js\";\nimport { AuthenticationError, errorFromHttpStatus, InternalServerError } from \"../shared/errors.js\";\nimport { makeFetch } from \"../shared/transports.js\";\nimport { decodeProtoFrame } from \"../utils/streams.js\";\nimport type { ConnectChannelParams, PolyesterRealtime, SubscribeHandlers } from \"./types.js\";\n\nconst realtimeFetch = makeFetch();\n\ntype CentrifugeCtor = typeof CentrifugeModule.Centrifuge;\n\n// Centrifuge's protobuf build embeds the protobuf.js runtime (~300 KB minified).\n// Loading it lazily keeps it out of the eager module graph on both the server\n// (Cloudflare isolate cold start — SSR never opens a websocket) and the client\n// app shell; it is only fetched when the first subscription attaches.\nlet centrifugeCtorPromise: Promise<CentrifugeCtor> | null = null;\nlet loadedCentrifugeCtor: CentrifugeCtor | null = null;\nexport function __setRealtimeCentrifugeForTests(Centrifuge: CentrifugeCtor | null): void {\n loadedCentrifugeCtor = Centrifuge;\n centrifugeCtorPromise = Centrifuge ? Promise.resolve(Centrifuge) : null;\n}\n\nfunction loadCentrifuge(): Promise<CentrifugeCtor> {\n if ((import.meta as { env?: { SSR?: boolean } }).env?.SSR) {\n return Promise.reject(new Error(\"Realtime subscriptions are browser-only during SSR.\"));\n }\n\n centrifugeCtorPromise ??= import(\"centrifuge/build/protobuf\").then((mod) => {\n loadedCentrifugeCtor = mod.Centrifuge;\n return mod.Centrifuge;\n });\n return centrifugeCtorPromise;\n}\n\nexport interface RealtimeAuthRequest {\n url: string | URL;\n method: string;\n}\n\nexport interface RealtimeConfig {\n wsUrl: string;\n tokenEndpoint: string;\n subscribeEndpoint: string;\n getAuthHeaders?: (request: RealtimeAuthRequest) => Promise<HeadersInit> | HeadersInit;\n hasAuth?: () => boolean;\n}\n\ntype ResolvedRealtimeConfig = Pick<\n RealtimeConfig,\n \"wsUrl\" | \"tokenEndpoint\" | \"subscribeEndpoint\"\n> & {\n getAuthHeaders: (request: RealtimeAuthRequest) => Promise<HeadersInit> | HeadersInit;\n hasAuth: () => boolean;\n};\n\nexport type { ConnectChannelParams, PolyesterRealtime, SubscribeHandlers } from \"./types.js\";\n\ntype ConnectionHandler = { onConnected?: () => void; onDisconnected?: () => void };\ntype PublicationHandler<T = unknown> = (data: T) => void;\ntype ErrorHandler = (ctx: SdkSubscriptionErrorContext) => void;\ntype RealtimeClientKind = \"public\" | \"private\";\n\ninterface SharedSubscription {\n channel: string;\n sub: Subscription | null;\n client: Centrifuge | null;\n attachmentEpoch: number;\n consumers: number;\n subscriptionEpoch: number;\n publicationHandlers: Set<PublicationHandler>;\n subscribedHandlers: Set<(epoch: number) => void>;\n unsubscribedHandlers: Set<() => void>;\n errorHandlers: Set<ErrorHandler>;\n}\n\n/**\n * Shared Centrifuge realtime client that multiplexes public and private protobuf subscriptions across SDK services.\n */\nexport class RealtimeClient implements PolyesterRealtime {\n #publicClient: Centrifuge | null = null;\n #privateClient: Centrifuge | null = null;\n #connectionHandlers = new Set<ConnectionHandler>();\n #sharedSubs = new Map<string, SharedSubscription>();\n #pendingTeardowns = new Map<string, SharedSubscription>();\n readonly #config: ResolvedRealtimeConfig;\n\n constructor(config: RealtimeConfig) {\n this.#config = {\n wsUrl: config.wsUrl,\n tokenEndpoint: config.tokenEndpoint,\n subscribeEndpoint: config.subscribeEndpoint,\n getAuthHeaders: config.getAuthHeaders ?? (() => ({})),\n hasAuth: config.hasAuth ?? (() => false),\n };\n }\n\n async #getAuthHeaders(request: RealtimeAuthRequest): Promise<HeadersInit> {\n return this.#config.getAuthHeaders(request);\n }\n\n #emitSubscriptionError(shared: SharedSubscription, type: string, error: unknown): void {\n const ctx = createSdkSubscriptionErrorContext(shared.channel, type, error);\n for (const handler of shared.errorHandlers) {\n handler(ctx);\n }\n }\n\n #emitConnectionTokenError(error: unknown): void {\n for (const shared of this.#sharedSubs.values()) {\n if (this.#channelKind(shared.channel) === \"private\") {\n this.#emitSubscriptionError(shared, \"connection_token\", error);\n }\n }\n }\n\n #subscriptionOpts(shared: SharedSubscription, attachmentEpoch: number) {\n if (this.#channelKind(shared.channel) !== \"private\") return undefined;\n return {\n getToken: async () => {\n try {\n const url = new URL(this.#config.subscribeEndpoint);\n url.searchParams.set(\"channel\", shared.channel);\n const headers = await this.#getAuthHeaders({ url, method: \"GET\" });\n const res = await realtimeFetch(url, { headers });\n if (!res.ok) {\n throw errorFromHttpStatus(\n res.status,\n `Failed to fetch subscription token: ${res.status}`,\n );\n }\n const json = (await res.json()) as { token?: string };\n if (!json?.token) {\n throw new InternalServerError(\"Subscription token response had no token\");\n }\n return json.token;\n } catch (error) {\n if (shared.attachmentEpoch === attachmentEpoch) {\n this.#emitSubscriptionError(shared, \"subscription_token\", error);\n }\n throw error;\n }\n },\n };\n }\n\n #hasAuth(): boolean {\n return this.#config.hasAuth();\n }\n\n #channelKind(channel: string): RealtimeClientKind {\n return channel.startsWith(\"private:\") ? \"private\" : \"public\";\n }\n\n #createPublicClient(Centrifuge: CentrifugeCtor): Centrifuge {\n const client = new Centrifuge(this.#config.wsUrl);\n this.#publicClient = client;\n\n client.on(\"connected\", () => {\n if (this.#publicClient !== client) return;\n for (const h of this.#connectionHandlers) h.onConnected?.();\n });\n client.on(\"disconnected\", () => {\n if (this.#publicClient !== client) return;\n for (const h of this.#connectionHandlers) h.onDisconnected?.();\n });\n\n client.connect();\n return client;\n }\n\n #createPrivateClient(Centrifuge: CentrifugeCtor): Centrifuge {\n let client: Centrifuge;\n const opts = {\n getToken: async () => {\n try {\n const headers = await this.#getAuthHeaders({\n url: this.#config.tokenEndpoint,\n method: \"GET\",\n });\n const res = await realtimeFetch(this.#config.tokenEndpoint, {\n headers,\n });\n if (!res.ok) {\n throw errorFromHttpStatus(\n res.status,\n `Failed to fetch connection token: ${res.status}`,\n );\n }\n const json = (await res.json()) as { token?: string };\n if (!json?.token) {\n throw new InternalServerError(\"Connection token response had no token\");\n }\n return json.token;\n } catch (error) {\n if (this.#privateClient === client) {\n this.#emitConnectionTokenError(error);\n }\n throw error;\n }\n },\n };\n\n client = new Centrifuge(this.#config.wsUrl, opts);\n this.#privateClient = client;\n\n client.on(\"connected\", () => {\n if (this.#privateClient !== client) return;\n for (const h of this.#connectionHandlers) h.onConnected?.();\n });\n client.on(\"disconnected\", () => {\n if (this.#privateClient !== client) return;\n for (const h of this.#connectionHandlers) h.onDisconnected?.();\n });\n\n client.connect();\n return client;\n }\n\n #ensurePublicClient(Centrifuge: CentrifugeCtor): Centrifuge {\n return this.#publicClient ?? this.#createPublicClient(Centrifuge);\n }\n\n #ensurePrivateClient(Centrifuge: CentrifugeCtor): Centrifuge {\n if (!this.#hasAuth()) {\n throw new AuthenticationError(\n \"Cannot create authenticated realtime client without authentication\",\n );\n }\n return this.#privateClient ?? this.#createPrivateClient(Centrifuge);\n }\n\n #assertChannelAuth(channel: string): void {\n if (this.#channelKind(channel) === \"private\" && !this.#hasAuth()) {\n throw new AuthenticationError(\n `Cannot subscribe to private channel \"${channel}\" without authentication`,\n );\n }\n }\n\n #ensureClientForChannel(Centrifuge: CentrifugeCtor, channel: string): Centrifuge {\n this.#assertChannelAuth(channel);\n if (this.#channelKind(channel) === \"private\") {\n return this.#ensurePrivateClient(Centrifuge);\n }\n\n return this.#ensurePublicClient(Centrifuge);\n }\n\n #attachSubscription(shared: SharedSubscription): void {\n if (shared.sub) return;\n\n // Auth failures must surface synchronously to subscribe() callers, as\n // they did when centrifuge was imported statically.\n this.#assertChannelAuth(shared.channel);\n\n const attachmentEpoch = shared.attachmentEpoch + 1;\n shared.attachmentEpoch = attachmentEpoch;\n\n // Once the transport module is loaded, attachment stays fully\n // synchronous — only the very first attach pays the dynamic import.\n if (loadedCentrifugeCtor) {\n this.#attachSubscriptionNow(loadedCentrifugeCtor, shared, attachmentEpoch);\n return;\n }\n void this.#attachSubscriptionWhenLoaded(shared, attachmentEpoch);\n }\n\n async #attachSubscriptionWhenLoaded(\n shared: SharedSubscription,\n attachmentEpoch: number,\n ): Promise<void> {\n let Centrifuge: CentrifugeCtor;\n try {\n Centrifuge = await loadCentrifuge();\n } catch (error) {\n if (shared.attachmentEpoch !== attachmentEpoch) return;\n if (this.#sharedSubs.get(shared.channel) === shared) {\n this.#sharedSubs.delete(shared.channel);\n }\n this.#emitSubscriptionError(shared, \"transport_load\", error);\n return;\n }\n\n // The subscription may have been torn down or re-attached while the\n // transport module was loading.\n if (shared.attachmentEpoch !== attachmentEpoch || shared.sub) return;\n if (this.#sharedSubs.get(shared.channel) !== shared) return;\n\n try {\n this.#attachSubscriptionNow(Centrifuge, shared, attachmentEpoch);\n } catch (error) {\n this.#sharedSubs.delete(shared.channel);\n this.#emitSubscriptionError(shared, \"auth\", error);\n }\n }\n\n #attachSubscriptionNow(\n Centrifuge: CentrifugeCtor,\n shared: SharedSubscription,\n attachmentEpoch: number,\n ): void {\n const client = this.#ensureClientForChannel(Centrifuge, shared.channel);\n\n const sub = client.newSubscription(\n shared.channel,\n this.#subscriptionOpts(shared, attachmentEpoch),\n );\n shared.sub = sub;\n shared.client = client;\n\n sub.on(\"publication\", (ctx: PublicationContext) => {\n if (shared.sub !== sub || shared.attachmentEpoch !== attachmentEpoch) return;\n for (const handler of shared.publicationHandlers) handler(ctx.data);\n });\n sub.on(\"subscribed\", () => {\n if (shared.sub !== sub || shared.attachmentEpoch !== attachmentEpoch) return;\n const subscriptionEpoch = ++shared.subscriptionEpoch;\n for (const handler of shared.subscribedHandlers) handler(subscriptionEpoch);\n });\n sub.on(\"unsubscribed\", () => {\n if (shared.sub !== sub || shared.attachmentEpoch !== attachmentEpoch) return;\n for (const handler of shared.unsubscribedHandlers) handler();\n });\n sub.on(\"error\", (ctx: SubscriptionErrorContext) => {\n if (shared.sub !== sub || shared.attachmentEpoch !== attachmentEpoch) return;\n const sdkCtx = fromCentrifugeSubscriptionError(ctx);\n for (const handler of shared.errorHandlers) handler(sdkCtx);\n });\n\n sub.subscribe();\n }\n\n #getOrCreateSubscription(channel: string): SharedSubscription {\n const existing = this.#sharedSubs.get(channel);\n if (existing) return existing;\n\n const pending = this.#pendingTeardowns.get(channel);\n if (pending) {\n this.#pendingTeardowns.delete(channel);\n this.#sharedSubs.set(channel, pending);\n if (!pending.sub) {\n this.#attachSubscription(pending);\n } else if (pending.sub.state !== \"subscribed\") {\n pending.sub.subscribe();\n }\n return pending;\n }\n\n const shared: SharedSubscription = {\n channel,\n sub: null,\n client: null,\n attachmentEpoch: 0,\n consumers: 0,\n subscriptionEpoch: 0,\n publicationHandlers: new Set(),\n subscribedHandlers: new Set(),\n unsubscribedHandlers: new Set(),\n errorHandlers: new Set(),\n };\n\n this.#sharedSubs.set(channel, shared);\n try {\n this.#attachSubscription(shared);\n } catch (error) {\n this.#sharedSubs.delete(channel);\n throw error;\n }\n return shared;\n }\n\n #callErrorHandler(handler: ErrorHandler | undefined, ctx: SdkSubscriptionErrorContext): void {\n if (!handler) return;\n\n try {\n handler(ctx);\n } catch {\n // Keep error reporting isolated from other subscription consumers.\n }\n }\n\n #callConsumerHandler(\n channel: string,\n type: string,\n handler: () => void,\n onError?: ErrorHandler,\n ): void {\n try {\n handler();\n } catch (error) {\n this.#callErrorHandler(\n onError,\n createSdkSubscriptionErrorContext(channel, type, error),\n );\n }\n }\n\n #teardownSubscription(shared: SharedSubscription): void {\n const sub = shared.sub;\n const client = shared.client;\n const kind = this.#channelKind(shared.channel);\n shared.sub = null;\n shared.client = null;\n shared.attachmentEpoch++;\n if (!sub) return;\n\n try {\n if (sub.state !== \"unsubscribed\") {\n sub.unsubscribe();\n }\n } catch {\n // noop\n }\n try {\n client?.removeSubscription?.(sub);\n } catch {\n // noop\n }\n this.#disconnectClientIfIdle(kind);\n }\n\n #hasChannelsForKind(kind: RealtimeClientKind): boolean {\n for (const shared of this.#sharedSubs.values()) {\n if (this.#channelKind(shared.channel) === kind) return true;\n }\n for (const shared of this.#pendingTeardowns.values()) {\n if (this.#channelKind(shared.channel) === kind) return true;\n }\n return false;\n }\n\n #disconnectClientIfIdle(kind: RealtimeClientKind): void {\n if (this.#hasChannelsForKind(kind)) return;\n\n const client = kind === \"private\" ? this.#privateClient : this.#publicClient;\n if (kind === \"private\") {\n this.#privateClient = null;\n } else {\n this.#publicClient = null;\n }\n\n try {\n client?.disconnect();\n } catch {\n // noop\n }\n }\n\n #disconnect(): void {\n const publicClient = this.#publicClient;\n const privateClient = this.#privateClient;\n\n this.#pendingTeardowns.clear();\n this.#sharedSubs.clear();\n this.#publicClient = null;\n this.#privateClient = null;\n\n try {\n publicClient?.disconnect();\n } catch {\n // noop\n }\n try {\n privateClient?.disconnect();\n } catch {\n // noop\n }\n this.#connectionHandlers.clear();\n }\n\n #disconnectPrivate(): void {\n const privateClient = this.#privateClient;\n this.#privateClient = null;\n\n for (const [channel, shared] of this.#sharedSubs) {\n if (this.#channelKind(channel) !== \"private\") continue;\n this.#sharedSubs.delete(channel);\n this.#teardownSubscription(shared);\n }\n for (const [channel, shared] of this.#pendingTeardowns) {\n if (this.#channelKind(channel) !== \"private\") continue;\n this.#pendingTeardowns.delete(channel);\n this.#teardownSubscription(shared);\n }\n\n try {\n privateClient?.disconnect();\n } catch {\n // noop\n }\n }\n\n /**\n * Subscribes to a realtime channel and returns an unsubscribe function. Missing\n * authentication is reported to `onError`, or thrown synchronously when no error\n * observer is provided.\n */\n subscribe<T>(channel: string, handlers: SubscribeHandlers<T>): () => void {\n let shared: SharedSubscription;\n try {\n shared = this.#getOrCreateSubscription(channel);\n } catch (error) {\n if (!(error instanceof AuthenticationError)) throw error;\n if (!handlers.onError) throw error;\n\n const ctx = createSdkSubscriptionErrorContext(channel, \"auth\", error);\n queueMicrotask(() => this.#callErrorHandler(handlers.onError, ctx));\n return () => {};\n }\n shared.consumers++;\n\n const publicationHandler = handlers.onPublication;\n const errorHandler = handlers.onError;\n const subscribedHandler = handlers.onSubscribed;\n const unsubscribedHandler = handlers.onUnsubscribed;\n\n const onError: ErrorHandler | undefined = errorHandler\n ? (ctx) => this.#callErrorHandler(errorHandler, ctx)\n : undefined;\n const onPub: PublicationHandler = (data) => {\n this.#callConsumerHandler(\n channel,\n \"publication_handler\",\n () => publicationHandler(data as T),\n onError,\n );\n };\n let lastSubscribedEpoch = -1;\n const onSubscribed = subscribedHandler\n ? (subscriptionEpoch: number) => {\n if (subscriptionEpoch <= lastSubscribedEpoch) return;\n lastSubscribedEpoch = subscriptionEpoch;\n this.#callConsumerHandler(\n channel,\n \"subscribed_handler\",\n subscribedHandler,\n onError,\n );\n }\n : undefined;\n const onUnsubscribed = unsubscribedHandler\n ? () =>\n this.#callConsumerHandler(\n channel,\n \"unsubscribed_handler\",\n unsubscribedHandler,\n onError,\n )\n : undefined;\n\n shared.publicationHandlers.add(onPub);\n\n if (onSubscribed) shared.subscribedHandlers.add(onSubscribed);\n if (onUnsubscribed) shared.unsubscribedHandlers.add(onUnsubscribed);\n if (onError) shared.errorHandlers.add(onError);\n\n let closed = false;\n if (onSubscribed && shared.sub?.state === \"subscribed\") {\n const subscriptionEpoch = shared.subscriptionEpoch;\n queueMicrotask(() => {\n if (closed || shared.sub?.state !== \"subscribed\") return;\n onSubscribed(subscriptionEpoch);\n });\n }\n\n return () => {\n if (closed) return;\n closed = true;\n\n shared.publicationHandlers.delete(onPub);\n if (onSubscribed) shared.subscribedHandlers.delete(onSubscribed);\n if (onUnsubscribed) shared.unsubscribedHandlers.delete(onUnsubscribed);\n if (onError) shared.errorHandlers.delete(onError);\n\n shared.consumers--;\n if (shared.consumers <= 0) {\n const currentShared = this.#sharedSubs.get(channel);\n if (currentShared !== shared) return;\n\n this.#sharedSubs.delete(channel);\n this.#pendingTeardowns.set(channel, shared);\n\n queueMicrotask(() => {\n if (this.#pendingTeardowns.get(channel) !== shared) return;\n this.#pendingTeardowns.delete(channel);\n\n this.#teardownSubscription(shared);\n\n if (this.#sharedSubs.size === 0 && this.#pendingTeardowns.size === 0) {\n this.#disconnect();\n }\n });\n }\n };\n }\n\n /**\n * Connects to a realtime channel with a custom message decoder.\n */\n connectChannel<T extends DescMessage>(params: ConnectChannelParams<T>): () => void {\n return this.subscribe<Uint8Array>(params.channel, {\n onPublication: (data) => {\n let decoded: MessageShape<T>;\n try {\n decoded = fromBinary(params.schema, data);\n } catch (error) {\n this.#callErrorHandler(\n params.onError,\n createSdkSubscriptionErrorContext(params.channel, \"decode\", error),\n );\n return;\n }\n params.onPublication(decoded);\n },\n onSubscribed: params.onConnected,\n onUnsubscribed: params.onDisconnected,\n onError: params.onError,\n });\n }\n\n /**\n * Connects to a realtime channel that emits protobuf messages.\n */\n connectProtoChannel<T extends DescMessage>(params: ConnectChannelParams<T>): () => void {\n return this.subscribe<Uint8Array | MessageShape<T>>(params.channel, {\n onPublication: (data) => {\n let msg: MessageShape<T> | null;\n try {\n msg = decodeProtoFrame(params.schema, data);\n } catch (error) {\n this.#callErrorHandler(\n params.onError,\n createSdkSubscriptionErrorContext(params.channel, \"decode\", error),\n );\n return;\n }\n if (!msg) {\n this.#callErrorHandler(\n params.onError,\n createSdkSubscriptionErrorContext(\n params.channel,\n \"decode\",\n \"Unable to decode protobuf frame\",\n ),\n );\n return;\n }\n params.onPublication(msg);\n },\n onSubscribed: params.onConnected,\n onUnsubscribed: params.onDisconnected,\n onError: params.onError,\n });\n }\n\n /**\n * Disconnects the realtime client from all active channels.\n */\n disconnect(): void {\n this.#disconnect();\n }\n\n /**\n * Disconnects authenticated realtime state without touching public channels.\n */\n disconnectPrivate(): void {\n this.#disconnectPrivate();\n }\n\n get isConnected(): boolean {\n return this.#publicClient !== null || this.#privateClient !== null;\n }\n\n get activeChannels(): number {\n return this.#sharedSubs.size;\n }\n\n get totalConsumers(): number {\n let total = 0;\n for (const shared of this.#sharedSubs.values()) {\n total += shared.consumers;\n }\n return total;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["#config","#getAuthHeaders","#sharedSubs","#channelKind","#emitSubscriptionError","#publicClient","#connectionHandlers","#privateClient","#emitConnectionTokenError","#createPublicClient","#hasAuth","#createPrivateClient","#assertChannelAuth","#ensurePrivateClient","#ensurePublicClient","#attachSubscriptionNow","#attachSubscriptionWhenLoaded","#ensureClientForChannel","#subscriptionOpts","#pendingTeardowns","#attachSubscription","#callErrorHandler","#disconnectClientIfIdle","#hasChannelsForKind","#teardownSubscription","#getOrCreateSubscription","#callConsumerHandler","#disconnect","#disconnectPrivate"],"sources":["../../src/realtime/client.ts"],"sourcesContent":["import { fromBinary, type DescMessage, type MessageShape } from \"@bufbuild/protobuf\";\nimport type {\n Centrifuge,\n SubscriptionErrorContext,\n PublicationContext,\n Subscription,\n} from \"centrifuge/build/protobuf\";\nimport type * as CentrifugeModule from \"centrifuge/build/protobuf\";\nimport {\n createSdkSubscriptionErrorContext,\n fromCentrifugeSubscriptionError,\n type SdkSubscriptionErrorContext,\n} from \"../shared/subscription-errors.js\";\nimport { AuthenticationError, errorFromHttpStatus, InternalServerError } from \"../shared/errors.js\";\nimport { makeFetch } from \"../shared/transports.js\";\nimport { decodeProtoFrame } from \"../utils/streams.js\";\nimport type { ConnectChannelParams, PolyesterRealtime, SubscribeHandlers } from \"./types.js\";\n\nconst realtimeFetch = makeFetch();\n\ntype CentrifugeCtor = typeof CentrifugeModule.Centrifuge;\ntype CentrifugeModuleLoader = () => Promise<typeof CentrifugeModule>;\n\nconst defaultCentrifugeModuleLoader: CentrifugeModuleLoader = () =>\n import(\"centrifuge/build/protobuf\");\n\n// Centrifuge's protobuf build embeds the protobuf.js runtime (~300 KB minified).\n// Loading it lazily keeps it out of the eager module graph on both the server\n// (Cloudflare isolate cold start — SSR never opens a websocket) and the client\n// app shell; it is only fetched when the first subscription attaches.\nlet centrifugeCtorPromise: Promise<CentrifugeCtor> | null = null;\nlet loadedCentrifugeCtor: CentrifugeCtor | null = null;\nlet centrifugeModuleLoader = defaultCentrifugeModuleLoader;\nexport function __setRealtimeCentrifugeForTests(Centrifuge: CentrifugeCtor | null): void {\n loadedCentrifugeCtor = Centrifuge;\n centrifugeCtorPromise = Centrifuge ? Promise.resolve(Centrifuge) : null;\n}\n\n/** Replaces the lazy Centrifuge module loader for isolated transport-load tests. */\nexport function __setRealtimeCentrifugeLoaderForTests(loader: CentrifugeModuleLoader | null): void {\n centrifugeModuleLoader = loader ?? defaultCentrifugeModuleLoader;\n loadedCentrifugeCtor = null;\n centrifugeCtorPromise = null;\n}\n\nfunction loadCentrifuge(): Promise<CentrifugeCtor> {\n if (\n centrifugeModuleLoader === defaultCentrifugeModuleLoader &&\n (import.meta as { env?: { SSR?: boolean } }).env?.SSR\n ) {\n return Promise.reject(new Error(\"Realtime subscriptions are browser-only during SSR.\"));\n }\n\n if (!centrifugeCtorPromise) {\n const loadAttempt = Promise.resolve().then(centrifugeModuleLoader);\n const retryableLoad = loadAttempt.then(\n (mod) => {\n loadedCentrifugeCtor = mod.Centrifuge;\n return mod.Centrifuge;\n },\n (error) => {\n if (centrifugeCtorPromise === retryableLoad) centrifugeCtorPromise = null;\n throw error;\n },\n );\n centrifugeCtorPromise = retryableLoad;\n }\n return centrifugeCtorPromise;\n}\n\nexport interface RealtimeAuthRequest {\n url: string | URL;\n method: string;\n}\n\nexport interface RealtimeConfig {\n wsUrl: string;\n tokenEndpoint: string;\n subscribeEndpoint: string;\n getAuthHeaders?: (request: RealtimeAuthRequest) => Promise<HeadersInit> | HeadersInit;\n hasAuth?: () => boolean;\n}\n\ntype ResolvedRealtimeConfig = Pick<\n RealtimeConfig,\n \"wsUrl\" | \"tokenEndpoint\" | \"subscribeEndpoint\"\n> & {\n getAuthHeaders: (request: RealtimeAuthRequest) => Promise<HeadersInit> | HeadersInit;\n hasAuth: () => boolean;\n};\n\nexport type { ConnectChannelParams, PolyesterRealtime, SubscribeHandlers } from \"./types.js\";\n\ntype ConnectionHandler = { onConnected?: () => void; onDisconnected?: () => void };\ntype PublicationHandler<T = unknown> = (data: T) => void;\ntype ErrorHandler = (ctx: SdkSubscriptionErrorContext) => void;\ntype RealtimeClientKind = \"public\" | \"private\";\n\ninterface SharedSubscription {\n channel: string;\n sub: Subscription | null;\n client: Centrifuge | null;\n attachmentEpoch: number;\n consumers: number;\n subscriptionEpoch: number;\n publicationHandlers: Set<PublicationHandler>;\n subscribedHandlers: Set<(epoch: number) => void>;\n unsubscribedHandlers: Set<() => void>;\n errorHandlers: Set<ErrorHandler>;\n}\n\n/**\n * Shared Centrifuge realtime client that multiplexes public and private protobuf subscriptions across SDK services.\n */\nexport class RealtimeClient implements PolyesterRealtime {\n #publicClient: Centrifuge | null = null;\n #privateClient: Centrifuge | null = null;\n #connectionHandlers = new Set<ConnectionHandler>();\n #sharedSubs = new Map<string, SharedSubscription>();\n #pendingTeardowns = new Map<string, SharedSubscription>();\n readonly #config: ResolvedRealtimeConfig;\n\n constructor(config: RealtimeConfig) {\n this.#config = {\n wsUrl: config.wsUrl,\n tokenEndpoint: config.tokenEndpoint,\n subscribeEndpoint: config.subscribeEndpoint,\n getAuthHeaders: config.getAuthHeaders ?? (() => ({})),\n hasAuth: config.hasAuth ?? (() => false),\n };\n }\n\n async #getAuthHeaders(request: RealtimeAuthRequest): Promise<HeadersInit> {\n return this.#config.getAuthHeaders(request);\n }\n\n #emitSubscriptionError(shared: SharedSubscription, type: string, error: unknown): void {\n const ctx = createSdkSubscriptionErrorContext(shared.channel, type, error);\n for (const handler of shared.errorHandlers) {\n handler(ctx);\n }\n }\n\n #emitConnectionTokenError(error: unknown): void {\n for (const shared of this.#sharedSubs.values()) {\n if (this.#channelKind(shared.channel) === \"private\") {\n this.#emitSubscriptionError(shared, \"connection_token\", error);\n }\n }\n }\n\n #subscriptionOpts(shared: SharedSubscription, attachmentEpoch: number) {\n if (this.#channelKind(shared.channel) !== \"private\") return undefined;\n return {\n getToken: async () => {\n try {\n const url = new URL(this.#config.subscribeEndpoint);\n url.searchParams.set(\"channel\", shared.channel);\n const headers = await this.#getAuthHeaders({ url, method: \"GET\" });\n const res = await realtimeFetch(url, { headers });\n if (!res.ok) {\n throw errorFromHttpStatus(\n res.status,\n `Failed to fetch subscription token: ${res.status}`,\n );\n }\n const json = (await res.json()) as { token?: string };\n if (!json?.token) {\n throw new InternalServerError(\"Subscription token response had no token\");\n }\n return json.token;\n } catch (error) {\n if (shared.attachmentEpoch === attachmentEpoch) {\n this.#emitSubscriptionError(shared, \"subscription_token\", error);\n }\n throw error;\n }\n },\n };\n }\n\n #hasAuth(): boolean {\n return this.#config.hasAuth();\n }\n\n #channelKind(channel: string): RealtimeClientKind {\n return channel.startsWith(\"private:\") ? \"private\" : \"public\";\n }\n\n #createPublicClient(Centrifuge: CentrifugeCtor): Centrifuge {\n const client = new Centrifuge(this.#config.wsUrl);\n this.#publicClient = client;\n\n client.on(\"connected\", () => {\n if (this.#publicClient !== client) return;\n for (const h of this.#connectionHandlers) h.onConnected?.();\n });\n client.on(\"disconnected\", () => {\n if (this.#publicClient !== client) return;\n for (const h of this.#connectionHandlers) h.onDisconnected?.();\n });\n\n client.connect();\n return client;\n }\n\n #createPrivateClient(Centrifuge: CentrifugeCtor): Centrifuge {\n let client: Centrifuge;\n const opts = {\n getToken: async () => {\n try {\n const headers = await this.#getAuthHeaders({\n url: this.#config.tokenEndpoint,\n method: \"GET\",\n });\n const res = await realtimeFetch(this.#config.tokenEndpoint, {\n headers,\n });\n if (!res.ok) {\n throw errorFromHttpStatus(\n res.status,\n `Failed to fetch connection token: ${res.status}`,\n );\n }\n const json = (await res.json()) as { token?: string };\n if (!json?.token) {\n throw new InternalServerError(\"Connection token response had no token\");\n }\n return json.token;\n } catch (error) {\n if (this.#privateClient === client) {\n this.#emitConnectionTokenError(error);\n }\n throw error;\n }\n },\n };\n\n client = new Centrifuge(this.#config.wsUrl, opts);\n this.#privateClient = client;\n\n client.on(\"connected\", () => {\n if (this.#privateClient !== client) return;\n for (const h of this.#connectionHandlers) h.onConnected?.();\n });\n client.on(\"disconnected\", () => {\n if (this.#privateClient !== client) return;\n for (const h of this.#connectionHandlers) h.onDisconnected?.();\n });\n\n client.connect();\n return client;\n }\n\n #ensurePublicClient(Centrifuge: CentrifugeCtor): Centrifuge {\n return this.#publicClient ?? this.#createPublicClient(Centrifuge);\n }\n\n #ensurePrivateClient(Centrifuge: CentrifugeCtor): Centrifuge {\n if (!this.#hasAuth()) {\n throw new AuthenticationError(\n \"Cannot create authenticated realtime client without authentication\",\n );\n }\n return this.#privateClient ?? this.#createPrivateClient(Centrifuge);\n }\n\n #assertChannelAuth(channel: string): void {\n if (this.#channelKind(channel) === \"private\" && !this.#hasAuth()) {\n throw new AuthenticationError(\n `Cannot subscribe to private channel \"${channel}\" without authentication`,\n );\n }\n }\n\n #ensureClientForChannel(Centrifuge: CentrifugeCtor, channel: string): Centrifuge {\n this.#assertChannelAuth(channel);\n if (this.#channelKind(channel) === \"private\") {\n return this.#ensurePrivateClient(Centrifuge);\n }\n\n return this.#ensurePublicClient(Centrifuge);\n }\n\n #attachSubscription(shared: SharedSubscription): void {\n if (shared.sub) return;\n\n // Auth failures must surface synchronously to subscribe() callers, as\n // they did when centrifuge was imported statically.\n this.#assertChannelAuth(shared.channel);\n\n const attachmentEpoch = shared.attachmentEpoch + 1;\n shared.attachmentEpoch = attachmentEpoch;\n\n // Once the transport module is loaded, attachment stays fully\n // synchronous — only the very first attach pays the dynamic import.\n if (loadedCentrifugeCtor) {\n this.#attachSubscriptionNow(loadedCentrifugeCtor, shared, attachmentEpoch);\n return;\n }\n void this.#attachSubscriptionWhenLoaded(shared, attachmentEpoch);\n }\n\n async #attachSubscriptionWhenLoaded(\n shared: SharedSubscription,\n attachmentEpoch: number,\n ): Promise<void> {\n let Centrifuge: CentrifugeCtor;\n try {\n Centrifuge = await loadCentrifuge();\n } catch (error) {\n if (shared.attachmentEpoch !== attachmentEpoch) return;\n if (this.#sharedSubs.get(shared.channel) === shared) {\n this.#sharedSubs.delete(shared.channel);\n }\n this.#emitSubscriptionError(shared, \"transport_load\", error);\n return;\n }\n\n // The subscription may have been torn down or re-attached while the\n // transport module was loading.\n if (shared.attachmentEpoch !== attachmentEpoch || shared.sub) return;\n if (this.#sharedSubs.get(shared.channel) !== shared) return;\n\n try {\n this.#attachSubscriptionNow(Centrifuge, shared, attachmentEpoch);\n } catch (error) {\n this.#sharedSubs.delete(shared.channel);\n this.#emitSubscriptionError(shared, \"auth\", error);\n }\n }\n\n #attachSubscriptionNow(\n Centrifuge: CentrifugeCtor,\n shared: SharedSubscription,\n attachmentEpoch: number,\n ): void {\n const client = this.#ensureClientForChannel(Centrifuge, shared.channel);\n\n const sub = client.newSubscription(\n shared.channel,\n this.#subscriptionOpts(shared, attachmentEpoch),\n );\n shared.sub = sub;\n shared.client = client;\n\n sub.on(\"publication\", (ctx: PublicationContext) => {\n if (shared.sub !== sub || shared.attachmentEpoch !== attachmentEpoch) return;\n for (const handler of shared.publicationHandlers) handler(ctx.data);\n });\n sub.on(\"subscribed\", () => {\n if (shared.sub !== sub || shared.attachmentEpoch !== attachmentEpoch) return;\n const subscriptionEpoch = ++shared.subscriptionEpoch;\n for (const handler of shared.subscribedHandlers) handler(subscriptionEpoch);\n });\n sub.on(\"unsubscribed\", () => {\n if (shared.sub !== sub || shared.attachmentEpoch !== attachmentEpoch) return;\n for (const handler of shared.unsubscribedHandlers) handler();\n });\n sub.on(\"error\", (ctx: SubscriptionErrorContext) => {\n if (shared.sub !== sub || shared.attachmentEpoch !== attachmentEpoch) return;\n const sdkCtx = fromCentrifugeSubscriptionError(ctx);\n for (const handler of shared.errorHandlers) handler(sdkCtx);\n });\n\n sub.subscribe();\n }\n\n #getOrCreateSubscription(channel: string): SharedSubscription {\n const existing = this.#sharedSubs.get(channel);\n if (existing) return existing;\n\n const pending = this.#pendingTeardowns.get(channel);\n if (pending) {\n this.#pendingTeardowns.delete(channel);\n this.#sharedSubs.set(channel, pending);\n if (!pending.sub) {\n this.#attachSubscription(pending);\n } else if (pending.sub.state !== \"subscribed\") {\n pending.sub.subscribe();\n }\n return pending;\n }\n\n const shared: SharedSubscription = {\n channel,\n sub: null,\n client: null,\n attachmentEpoch: 0,\n consumers: 0,\n subscriptionEpoch: 0,\n publicationHandlers: new Set(),\n subscribedHandlers: new Set(),\n unsubscribedHandlers: new Set(),\n errorHandlers: new Set(),\n };\n\n this.#sharedSubs.set(channel, shared);\n try {\n this.#attachSubscription(shared);\n } catch (error) {\n this.#sharedSubs.delete(channel);\n throw error;\n }\n return shared;\n }\n\n #callErrorHandler(handler: ErrorHandler | undefined, ctx: SdkSubscriptionErrorContext): void {\n if (!handler) return;\n\n try {\n handler(ctx);\n } catch {\n // Keep error reporting isolated from other subscription consumers.\n }\n }\n\n #callConsumerHandler(\n channel: string,\n type: string,\n handler: () => void,\n onError?: ErrorHandler,\n ): void {\n try {\n handler();\n } catch (error) {\n this.#callErrorHandler(\n onError,\n createSdkSubscriptionErrorContext(channel, type, error),\n );\n }\n }\n\n #teardownSubscription(shared: SharedSubscription): void {\n const sub = shared.sub;\n const client = shared.client;\n const kind = this.#channelKind(shared.channel);\n shared.sub = null;\n shared.client = null;\n shared.attachmentEpoch++;\n if (!sub) return;\n\n try {\n if (sub.state !== \"unsubscribed\") {\n sub.unsubscribe();\n }\n } catch {\n // noop\n }\n try {\n client?.removeSubscription?.(sub);\n } catch {\n // noop\n }\n this.#disconnectClientIfIdle(kind);\n }\n\n #hasChannelsForKind(kind: RealtimeClientKind): boolean {\n for (const shared of this.#sharedSubs.values()) {\n if (this.#channelKind(shared.channel) === kind) return true;\n }\n for (const shared of this.#pendingTeardowns.values()) {\n if (this.#channelKind(shared.channel) === kind) return true;\n }\n return false;\n }\n\n #disconnectClientIfIdle(kind: RealtimeClientKind): void {\n if (this.#hasChannelsForKind(kind)) return;\n\n const client = kind === \"private\" ? this.#privateClient : this.#publicClient;\n if (kind === \"private\") {\n this.#privateClient = null;\n } else {\n this.#publicClient = null;\n }\n\n try {\n client?.disconnect();\n } catch {\n // noop\n }\n }\n\n #disconnect(): void {\n const publicClient = this.#publicClient;\n const privateClient = this.#privateClient;\n\n this.#pendingTeardowns.clear();\n this.#sharedSubs.clear();\n this.#publicClient = null;\n this.#privateClient = null;\n\n try {\n publicClient?.disconnect();\n } catch {\n // noop\n }\n try {\n privateClient?.disconnect();\n } catch {\n // noop\n }\n this.#connectionHandlers.clear();\n }\n\n #disconnectPrivate(): void {\n const privateClient = this.#privateClient;\n this.#privateClient = null;\n\n for (const [channel, shared] of this.#sharedSubs) {\n if (this.#channelKind(channel) !== \"private\") continue;\n this.#sharedSubs.delete(channel);\n this.#teardownSubscription(shared);\n }\n for (const [channel, shared] of this.#pendingTeardowns) {\n if (this.#channelKind(channel) !== \"private\") continue;\n this.#pendingTeardowns.delete(channel);\n this.#teardownSubscription(shared);\n }\n\n try {\n privateClient?.disconnect();\n } catch {\n // noop\n }\n }\n\n /**\n * Subscribes to a realtime channel and returns an unsubscribe function. Missing\n * authentication is reported to `onError`, or thrown synchronously when no error\n * observer is provided.\n */\n subscribe<T>(channel: string, handlers: SubscribeHandlers<T>): () => void {\n let shared: SharedSubscription;\n try {\n shared = this.#getOrCreateSubscription(channel);\n } catch (error) {\n if (!(error instanceof AuthenticationError)) throw error;\n if (!handlers.onError) throw error;\n\n const ctx = createSdkSubscriptionErrorContext(channel, \"auth\", error);\n queueMicrotask(() => this.#callErrorHandler(handlers.onError, ctx));\n return () => {};\n }\n shared.consumers++;\n\n const publicationHandler = handlers.onPublication;\n const errorHandler = handlers.onError;\n const subscribedHandler = handlers.onSubscribed;\n const unsubscribedHandler = handlers.onUnsubscribed;\n\n const onError: ErrorHandler | undefined = errorHandler\n ? (ctx) => this.#callErrorHandler(errorHandler, ctx)\n : undefined;\n const onPub: PublicationHandler = (data) => {\n this.#callConsumerHandler(\n channel,\n \"publication_handler\",\n () => publicationHandler(data as T),\n onError,\n );\n };\n let lastSubscribedEpoch = -1;\n const onSubscribed = subscribedHandler\n ? (subscriptionEpoch: number) => {\n if (subscriptionEpoch <= lastSubscribedEpoch) return;\n lastSubscribedEpoch = subscriptionEpoch;\n this.#callConsumerHandler(\n channel,\n \"subscribed_handler\",\n subscribedHandler,\n onError,\n );\n }\n : undefined;\n const onUnsubscribed = unsubscribedHandler\n ? () =>\n this.#callConsumerHandler(\n channel,\n \"unsubscribed_handler\",\n unsubscribedHandler,\n onError,\n )\n : undefined;\n\n shared.publicationHandlers.add(onPub);\n\n if (onSubscribed) shared.subscribedHandlers.add(onSubscribed);\n if (onUnsubscribed) shared.unsubscribedHandlers.add(onUnsubscribed);\n if (onError) shared.errorHandlers.add(onError);\n\n let closed = false;\n if (onSubscribed && shared.sub?.state === \"subscribed\") {\n const subscriptionEpoch = shared.subscriptionEpoch;\n queueMicrotask(() => {\n if (closed || shared.sub?.state !== \"subscribed\") return;\n onSubscribed(subscriptionEpoch);\n });\n }\n\n return () => {\n if (closed) return;\n closed = true;\n\n shared.publicationHandlers.delete(onPub);\n if (onSubscribed) shared.subscribedHandlers.delete(onSubscribed);\n if (onUnsubscribed) shared.unsubscribedHandlers.delete(onUnsubscribed);\n if (onError) shared.errorHandlers.delete(onError);\n\n shared.consumers--;\n if (shared.consumers <= 0) {\n const currentShared = this.#sharedSubs.get(channel);\n if (currentShared !== shared) return;\n\n this.#sharedSubs.delete(channel);\n this.#pendingTeardowns.set(channel, shared);\n\n queueMicrotask(() => {\n if (this.#pendingTeardowns.get(channel) !== shared) return;\n this.#pendingTeardowns.delete(channel);\n\n this.#teardownSubscription(shared);\n\n if (this.#sharedSubs.size === 0 && this.#pendingTeardowns.size === 0) {\n this.#disconnect();\n }\n });\n }\n };\n }\n\n /**\n * Connects to a realtime channel with a custom message decoder.\n */\n connectChannel<T extends DescMessage>(params: ConnectChannelParams<T>): () => void {\n return this.subscribe<Uint8Array>(params.channel, {\n onPublication: (data) => {\n let decoded: MessageShape<T>;\n try {\n decoded = fromBinary(params.schema, data);\n } catch (error) {\n this.#callErrorHandler(\n params.onError,\n createSdkSubscriptionErrorContext(params.channel, \"decode\", error),\n );\n return;\n }\n params.onPublication(decoded);\n },\n onSubscribed: params.onConnected,\n onUnsubscribed: params.onDisconnected,\n onError: params.onError,\n });\n }\n\n /**\n * Connects to a realtime channel that emits protobuf messages.\n */\n connectProtoChannel<T extends DescMessage>(params: ConnectChannelParams<T>): () => void {\n return this.subscribe<Uint8Array | MessageShape<T>>(params.channel, {\n onPublication: (data) => {\n let msg: MessageShape<T> | null;\n try {\n msg = decodeProtoFrame(params.schema, data);\n } catch (error) {\n this.#callErrorHandler(\n params.onError,\n createSdkSubscriptionErrorContext(params.channel, \"decode\", error),\n );\n return;\n }\n if (!msg) {\n this.#callErrorHandler(\n params.onError,\n createSdkSubscriptionErrorContext(\n params.channel,\n \"decode\",\n \"Unable to decode protobuf frame\",\n ),\n );\n return;\n }\n params.onPublication(msg);\n },\n onSubscribed: params.onConnected,\n onUnsubscribed: params.onDisconnected,\n onError: params.onError,\n });\n }\n\n /**\n * Disconnects the realtime client from all active channels.\n */\n disconnect(): void {\n this.#disconnect();\n }\n\n /**\n * Disconnects authenticated realtime state without touching public channels.\n */\n disconnectPrivate(): void {\n this.#disconnectPrivate();\n }\n\n get isConnected(): boolean {\n return this.#publicClient !== null || this.#privateClient !== null;\n }\n\n get activeChannels(): number {\n return this.#sharedSubs.size;\n }\n\n get totalConsumers(): number {\n let total = 0;\n for (const shared of this.#sharedSubs.values()) {\n total += shared.consumers;\n }\n return total;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { createReadyGate } from "../shared/decimal-surface.js";
|
|
2
|
+
import { publicationHandlerErrorContext } from "../shared/subscription-errors.js";
|
|
3
|
+
//#region src/realtime/ready-gated-subscription.ts
|
|
4
|
+
/**
|
|
5
|
+
* Connects a transition feed whose publications require asynchronous catalog
|
|
6
|
+
* readiness. If readiness fails or its bounded pre-ready queue overflows, the
|
|
7
|
+
* consumer is unsubscribed after receiving one error and one close callback;
|
|
8
|
+
* continuing after a dropped transition would expose incomplete state.
|
|
9
|
+
*/
|
|
10
|
+
function connectReadyGatedProtoChannel(realtime, params) {
|
|
11
|
+
let unsubscribeRealtime;
|
|
12
|
+
let unsubscribeAfterConnect = false;
|
|
13
|
+
let state = "active";
|
|
14
|
+
let terminalCloseNotified = false;
|
|
15
|
+
function reportPublicationError(error) {
|
|
16
|
+
try {
|
|
17
|
+
params.onError?.(publicationHandlerErrorContext(params.channel, error));
|
|
18
|
+
} catch {}
|
|
19
|
+
}
|
|
20
|
+
function notifyTerminalClose() {
|
|
21
|
+
if (terminalCloseNotified) return;
|
|
22
|
+
terminalCloseNotified = true;
|
|
23
|
+
try {
|
|
24
|
+
params.onDisconnected?.();
|
|
25
|
+
} catch {}
|
|
26
|
+
}
|
|
27
|
+
function terminate(error) {
|
|
28
|
+
if (state !== "active") return;
|
|
29
|
+
state = "terminal";
|
|
30
|
+
reportPublicationError(error);
|
|
31
|
+
if (unsubscribeRealtime) unsubscribeRealtime();
|
|
32
|
+
else unsubscribeAfterConnect = true;
|
|
33
|
+
notifyTerminalClose();
|
|
34
|
+
}
|
|
35
|
+
const gate = createReadyGate(params.ready, {
|
|
36
|
+
onDeliveryError: reportPublicationError,
|
|
37
|
+
onTerminalError: terminate
|
|
38
|
+
});
|
|
39
|
+
try {
|
|
40
|
+
unsubscribeRealtime = realtime.connectProtoChannel({
|
|
41
|
+
channel: params.channel,
|
|
42
|
+
schema: params.schema,
|
|
43
|
+
onPublication: (data) => gate.run(() => params.onPublication(data)),
|
|
44
|
+
onConnected: () => {
|
|
45
|
+
if (state === "active") params.onConnected?.();
|
|
46
|
+
},
|
|
47
|
+
onDisconnected: () => {
|
|
48
|
+
if (state === "terminal") notifyTerminalClose();
|
|
49
|
+
else if (state === "active") params.onDisconnected?.();
|
|
50
|
+
},
|
|
51
|
+
onError: params.onError
|
|
52
|
+
});
|
|
53
|
+
} catch (error) {
|
|
54
|
+
gate.close();
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
if (unsubscribeAfterConnect) unsubscribeRealtime();
|
|
58
|
+
return () => {
|
|
59
|
+
if (state !== "active") return;
|
|
60
|
+
state = "disposed";
|
|
61
|
+
gate.close();
|
|
62
|
+
unsubscribeRealtime?.();
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { connectReadyGatedProtoChannel };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=ready-gated-subscription.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ready-gated-subscription.js","names":[],"sources":["../../src/realtime/ready-gated-subscription.ts"],"sourcesContent":["import type { DescMessage } from \"@bufbuild/protobuf\";\nimport { createReadyGate } from \"../shared/decimal-surface.js\";\nimport { publicationHandlerErrorContext } from \"../shared/subscription-errors.js\";\nimport type { ConnectChannelParams, PolyesterRealtime } from \"./types.js\";\n\ntype ReadyGatedProtoChannelParams<TSchema extends DescMessage> = ConnectChannelParams<TSchema> & {\n ready: () => Promise<void>;\n};\n\n/**\n * Connects a transition feed whose publications require asynchronous catalog\n * readiness. If readiness fails or its bounded pre-ready queue overflows, the\n * consumer is unsubscribed after receiving one error and one close callback;\n * continuing after a dropped transition would expose incomplete state.\n */\nexport function connectReadyGatedProtoChannel<TSchema extends DescMessage>(\n realtime: PolyesterRealtime,\n params: ReadyGatedProtoChannelParams<TSchema>,\n): () => void {\n let unsubscribeRealtime: (() => void) | undefined;\n let unsubscribeAfterConnect = false;\n let state: \"active\" | \"terminal\" | \"disposed\" = \"active\";\n let terminalCloseNotified = false;\n\n function reportPublicationError(error: unknown): void {\n try {\n params.onError?.(publicationHandlerErrorContext(params.channel, error));\n } catch {\n // Keep consumer error-handler failures isolated from subscription teardown.\n }\n }\n\n function notifyTerminalClose(): void {\n if (terminalCloseNotified) return;\n terminalCloseNotified = true;\n try {\n params.onDisconnected?.();\n } catch {\n // Keep consumer close-handler failures isolated from subscription teardown.\n }\n }\n\n function terminate(error: unknown): void {\n if (state !== \"active\") return;\n state = \"terminal\";\n reportPublicationError(error);\n\n if (unsubscribeRealtime) unsubscribeRealtime();\n else unsubscribeAfterConnect = true;\n notifyTerminalClose();\n }\n\n const gate = createReadyGate(params.ready, {\n onDeliveryError: reportPublicationError,\n onTerminalError: terminate,\n });\n\n try {\n unsubscribeRealtime = realtime.connectProtoChannel({\n channel: params.channel,\n schema: params.schema,\n onPublication: (data) => gate.run(() => params.onPublication(data)),\n onConnected: () => {\n if (state === \"active\") params.onConnected?.();\n },\n onDisconnected: () => {\n if (state === \"terminal\") notifyTerminalClose();\n else if (state === \"active\") params.onDisconnected?.();\n },\n onError: params.onError,\n });\n } catch (error) {\n gate.close();\n throw error;\n }\n\n if (unsubscribeAfterConnect) unsubscribeRealtime();\n\n return () => {\n if (state !== \"active\") return;\n state = \"disposed\";\n gate.close();\n unsubscribeRealtime?.();\n };\n}\n"],"mappings":";;;;;;;;;AAeA,SAAgB,8BACZ,UACA,QACU;CACV,IAAI;CACJ,IAAI,0BAA0B;CAC9B,IAAI,QAA4C;CAChD,IAAI,wBAAwB;CAE5B,SAAS,uBAAuB,OAAsB;EAClD,IAAI;GACA,OAAO,UAAU,+BAA+B,OAAO,SAAS,KAAK,CAAC;EAC1E,QAAQ,CAER;CACJ;CAEA,SAAS,sBAA4B;EACjC,IAAI,uBAAuB;EAC3B,wBAAwB;EACxB,IAAI;GACA,OAAO,iBAAiB;EAC5B,QAAQ,CAER;CACJ;CAEA,SAAS,UAAU,OAAsB;EACrC,IAAI,UAAU,UAAU;EACxB,QAAQ;EACR,uBAAuB,KAAK;EAE5B,IAAI,qBAAqB,oBAAoB;OACxC,0BAA0B;EAC/B,oBAAoB;CACxB;CAEA,MAAM,OAAO,gBAAgB,OAAO,OAAO;EACvC,iBAAiB;EACjB,iBAAiB;CACrB,CAAC;CAED,IAAI;EACA,sBAAsB,SAAS,oBAAoB;GAC/C,SAAS,OAAO;GAChB,QAAQ,OAAO;GACf,gBAAgB,SAAS,KAAK,UAAU,OAAO,cAAc,IAAI,CAAC;GAClE,mBAAmB;IACf,IAAI,UAAU,UAAU,OAAO,cAAc;GACjD;GACA,sBAAsB;IAClB,IAAI,UAAU,YAAY,oBAAoB;SACzC,IAAI,UAAU,UAAU,OAAO,iBAAiB;GACzD;GACA,SAAS,OAAO;EACpB,CAAC;CACL,SAAS,OAAO;EACZ,KAAK,MAAM;EACX,MAAM;CACV;CAEA,IAAI,yBAAyB,oBAAoB;CAEjD,aAAa;EACT,IAAI,UAAU,UAAU;EACxB,QAAQ;EACR,KAAK,MAAM;EACX,sBAAsB;CAC1B;AACJ"}
|
|
@@ -32,7 +32,7 @@ function snapshotThenStream(params) {
|
|
|
32
32
|
function applyBufferedAsLive() {
|
|
33
33
|
const buffered = takePendingPublications();
|
|
34
34
|
if (buffered.length === 0) return;
|
|
35
|
-
|
|
35
|
+
applyLivePublications(buffered);
|
|
36
36
|
}
|
|
37
37
|
function bufferPublications(publications) {
|
|
38
38
|
if (params.bufferPublicationKey) {
|
|
@@ -57,6 +57,20 @@ function snapshotThenStream(params) {
|
|
|
57
57
|
function reportBufferedPublicationError(error) {
|
|
58
58
|
params.onError?.(createSdkSubscriptionErrorContext(params.channel, "publication_handler", error));
|
|
59
59
|
}
|
|
60
|
+
function applyLivePublications(publications) {
|
|
61
|
+
try {
|
|
62
|
+
params.applyLivePublications(publications);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
reportBufferedPublicationError(error);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function applySnapshot(snapshot, bufferedPublications) {
|
|
68
|
+
try {
|
|
69
|
+
params.applySnapshot(snapshot, bufferedPublications);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
reportBufferedPublicationError(error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
60
74
|
function scheduleSnapshotRetry() {
|
|
61
75
|
const retry = params.snapshotRetry;
|
|
62
76
|
if (!retry || retryAttempts >= retry.maxAttempts || disposed) return;
|
|
@@ -75,21 +89,16 @@ function snapshotThenStream(params) {
|
|
|
75
89
|
ready = false;
|
|
76
90
|
Promise.resolve().then(() => params.fetchSnapshot()).then((snapshot) => {
|
|
77
91
|
if (disposed || generation !== refreshGeneration) return;
|
|
78
|
-
|
|
79
|
-
params.applySnapshot(snapshot, buffered);
|
|
92
|
+
applySnapshot(snapshot, takePendingPublications());
|
|
80
93
|
if (disposed || generation !== refreshGeneration) return;
|
|
81
94
|
ready = true;
|
|
82
95
|
retryAttempts = 0;
|
|
83
96
|
}).catch((error) => {
|
|
84
97
|
if (disposed || generation !== refreshGeneration) return;
|
|
85
98
|
reportSnapshotError(error);
|
|
86
|
-
if (params.snapshotFailureMode === "live") {
|
|
99
|
+
if ((typeof params.snapshotFailureMode === "function" ? params.snapshotFailureMode(error) : params.snapshotFailureMode) === "live") {
|
|
87
100
|
ready = true;
|
|
88
|
-
|
|
89
|
-
applyBufferedAsLive();
|
|
90
|
-
} catch (publicationError) {
|
|
91
|
-
reportBufferedPublicationError(publicationError);
|
|
92
|
-
}
|
|
101
|
+
applyBufferedAsLive();
|
|
93
102
|
}
|
|
94
103
|
scheduleSnapshotRetry();
|
|
95
104
|
});
|
|
@@ -104,7 +113,7 @@ function snapshotThenStream(params) {
|
|
|
104
113
|
bufferPublications(publications);
|
|
105
114
|
return;
|
|
106
115
|
}
|
|
107
|
-
|
|
116
|
+
applyLivePublications(publications);
|
|
108
117
|
},
|
|
109
118
|
onConnected: () => {
|
|
110
119
|
if (disposed) return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot-then-stream.js","names":[],"sources":["../../src/realtime/snapshot-then-stream.ts"],"sourcesContent":["import type { DescMessage, MessageShape } from \"@bufbuild/protobuf\";\nimport {\n createSdkSubscriptionErrorContext,\n type SdkSubscriptionErrorContext,\n} from \"../shared/subscription-errors.js\";\nimport type { PolyesterRealtime } from \"./types.js\";\nimport { formatConnectError } from \"../utils/errors.js\";\nimport { isDev } from \"../utils/is-dev.js\";\n\nexport interface SnapshotThenStreamSubscription {\n unsubscribe: () => void;\n refreshSnapshot: () => void;\n isReady: () => boolean;\n isDisposed: () => boolean;\n}\n\nexport interface SnapshotThenStreamInput<TSchema extends DescMessage, TSnapshot, TPublication> {\n realtime: PolyesterRealtime;\n channel: string;\n schema: TSchema;\n maxBufferedPublications?: number;\n bufferPublicationKey?: (publication: TPublication) => PropertyKey;\n snapshotRetry?: {\n maxAttempts: number;\n delayMs: number;\n };\n snapshotFailureMode?: \"wait\" | \"live\";\n snapshotErrorLog?: string;\n fetchSnapshot: () => Promise<TSnapshot>;\n readPublication: (message: MessageShape<TSchema>) => readonly TPublication[];\n applySnapshot: (snapshot: TSnapshot, bufferedPublications: readonly TPublication[]) => void;\n applyLivePublications: (publications: readonly TPublication[]) => void;\n onOpen?: () => void;\n onClose?: () => void;\n onError?: (ctx: SdkSubscriptionErrorContext) => void;\n}\n\n/**\n * Snapshot-then-stream for feed state. By default, the buffer caps at\n * `maxBufferedPublications` and drops the OLDEST publications, which is correct for\n * ephemeral feeds where a later publication supersedes earlier ones. Keyed state can\n * provide `bufferPublicationKey` to retain the latest value for every key without FIFO\n * loss. Entity transitions (orders, triggers, balances, trades) must NOT use this —\n * their ordering contract lives in the app-side `EntityStreamStore`, which buffers\n * unbounded and replays because dropping an event loses a row transition.\n */\nexport function snapshotThenStream<TSchema extends DescMessage, TSnapshot, TPublication>(\n params: SnapshotThenStreamInput<TSchema, TSnapshot, TPublication>,\n): SnapshotThenStreamSubscription {\n const maxBufferedPublications = params.maxBufferedPublications ?? 200;\n let droppedPublications = 0;\n\n let ready = false;\n let disposed = false;\n let refreshGeneration = 0;\n let pendingPublications: TPublication[] = [];\n let retryAttempts = 0;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n\n function reportSnapshotError(error: unknown): void {\n if (isDev() && params.snapshotErrorLog) {\n console.error(params.snapshotErrorLog, error);\n }\n params.onError?.(\n createSdkSubscriptionErrorContext(\n params.channel,\n \"snapshot\",\n formatConnectError(error, \"snapshot failed\"),\n ),\n );\n }\n\n function takePendingPublications(): TPublication[] {\n const buffered = pendingPublications;\n pendingPublications = [];\n return buffered;\n }\n\n function applyBufferedAsLive(): void {\n const buffered = takePendingPublications();\n if (buffered.length === 0) return;\n params.applyLivePublications(buffered);\n }\n\n function bufferPublications(publications: readonly TPublication[]): void {\n if (params.bufferPublicationKey) {\n const byKey = new Map<PropertyKey, TPublication>();\n for (const publication of pendingPublications) {\n byKey.set(params.bufferPublicationKey(publication), publication);\n }\n for (const publication of publications) {\n byKey.set(params.bufferPublicationKey(publication), publication);\n }\n pendingPublications = Array.from(byKey.values());\n return;\n }\n\n const next = pendingPublications.concat(publications);\n if (next.length > maxBufferedPublications) {\n droppedPublications += next.length - maxBufferedPublications;\n if (isDev()) {\n console.warn(\n `[snapshot-then-stream] ${params.channel}: buffer cap dropped ${droppedPublications} publication(s) while snapshot in flight`,\n );\n }\n }\n pendingPublications = next.slice(-maxBufferedPublications);\n }\n\n function clearRetryTimer(): void {\n if (retryTimer === null) return;\n clearTimeout(retryTimer);\n retryTimer = null;\n }\n\n function reportBufferedPublicationError(error: unknown): void {\n params.onError?.(\n createSdkSubscriptionErrorContext(params.channel, \"publication_handler\", error),\n );\n }\n\n function scheduleSnapshotRetry(): void {\n const retry = params.snapshotRetry;\n if (!retry || retryAttempts >= retry.maxAttempts || disposed) return;\n\n const delayMs = retry.delayMs * 2 ** retryAttempts;\n retryAttempts++;\n retryTimer = setTimeout(() => {\n retryTimer = null;\n refreshSnapshot(false);\n }, delayMs);\n }\n\n function refreshSnapshot(resetRetries = true): void {\n if (disposed) return;\n clearRetryTimer();\n if (resetRetries) retryAttempts = 0;\n\n const generation = ++refreshGeneration;\n ready = false;\n\n Promise.resolve()\n .then(() => params.fetchSnapshot())\n .then((snapshot) => {\n if (disposed || generation !== refreshGeneration) return;\n\n const buffered = takePendingPublications();\n params.applySnapshot(snapshot, buffered);\n\n if (disposed || generation !== refreshGeneration) return;\n ready = true;\n retryAttempts = 0;\n })\n .catch((error: unknown) => {\n if (disposed || generation !== refreshGeneration) return;\n reportSnapshotError(error);\n if (params.snapshotFailureMode === \"live\") {\n ready = true;\n try {\n applyBufferedAsLive();\n } catch (publicationError) {\n reportBufferedPublicationError(publicationError);\n }\n }\n scheduleSnapshotRetry();\n });\n }\n\n const unsubscribeRealtime = params.realtime.connectProtoChannel({\n channel: params.channel,\n schema: params.schema,\n onPublication: (message) => {\n const publications = params.readPublication(message);\n if (publications.length === 0) return;\n\n if (!ready) {\n bufferPublications(publications);\n return;\n }\n\n params.applyLivePublications(publications);\n },\n onConnected: () => {\n if (disposed) return;\n pendingPublications = [];\n refreshSnapshot();\n params.onOpen?.();\n },\n onDisconnected: () => {\n if (disposed) return;\n clearRetryTimer();\n refreshGeneration++;\n ready = false;\n pendingPublications = [];\n params.onClose?.();\n },\n onError: params.onError,\n });\n\n function unsubscribe(): void {\n disposed = true;\n clearRetryTimer();\n refreshGeneration++;\n pendingPublications = [];\n try {\n unsubscribeRealtime();\n } catch {\n // noop\n }\n }\n\n return {\n unsubscribe,\n refreshSnapshot,\n isReady: () => ready,\n isDisposed: () => disposed,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"snapshot-then-stream.js","names":[],"sources":["../../src/realtime/snapshot-then-stream.ts"],"sourcesContent":["import type { DescMessage, MessageShape } from \"@bufbuild/protobuf\";\nimport {\n createSdkSubscriptionErrorContext,\n type SdkSubscriptionErrorContext,\n} from \"../shared/subscription-errors.js\";\nimport type { PolyesterRealtime } from \"./types.js\";\nimport { formatConnectError } from \"../utils/errors.js\";\nimport { isDev } from \"../utils/is-dev.js\";\n\nexport interface SnapshotThenStreamSubscription {\n unsubscribe: () => void;\n refreshSnapshot: () => void;\n isReady: () => boolean;\n isDisposed: () => boolean;\n}\n\nexport interface SnapshotThenStreamInput<TSchema extends DescMessage, TSnapshot, TPublication> {\n realtime: PolyesterRealtime;\n channel: string;\n schema: TSchema;\n maxBufferedPublications?: number;\n bufferPublicationKey?: (publication: TPublication) => PropertyKey;\n snapshotRetry?: {\n maxAttempts: number;\n delayMs: number;\n };\n snapshotFailureMode?: \"wait\" | \"live\" | ((error: unknown) => \"wait\" | \"live\");\n snapshotErrorLog?: string;\n fetchSnapshot: () => Promise<TSnapshot>;\n readPublication: (message: MessageShape<TSchema>) => readonly TPublication[];\n applySnapshot: (snapshot: TSnapshot, bufferedPublications: readonly TPublication[]) => void;\n applyLivePublications: (publications: readonly TPublication[]) => void;\n onOpen?: () => void;\n onClose?: () => void;\n onError?: (ctx: SdkSubscriptionErrorContext) => void;\n}\n\n/**\n * Snapshot-then-stream for feed state. By default, the buffer caps at\n * `maxBufferedPublications` and drops the OLDEST publications, which is correct for\n * ephemeral feeds where a later publication supersedes earlier ones. Keyed state can\n * provide `bufferPublicationKey` to retain the latest value for every key without FIFO\n * loss. Entity transitions (orders, triggers, balances, trades) must NOT use this —\n * their ordering contract lives in the app-side `EntityStreamStore`, which buffers\n * unbounded and replays because dropping an event loses a row transition.\n */\nexport function snapshotThenStream<TSchema extends DescMessage, TSnapshot, TPublication>(\n params: SnapshotThenStreamInput<TSchema, TSnapshot, TPublication>,\n): SnapshotThenStreamSubscription {\n const maxBufferedPublications = params.maxBufferedPublications ?? 200;\n let droppedPublications = 0;\n\n let ready = false;\n let disposed = false;\n let refreshGeneration = 0;\n let pendingPublications: TPublication[] = [];\n let retryAttempts = 0;\n let retryTimer: ReturnType<typeof setTimeout> | null = null;\n\n function reportSnapshotError(error: unknown): void {\n if (isDev() && params.snapshotErrorLog) {\n console.error(params.snapshotErrorLog, error);\n }\n params.onError?.(\n createSdkSubscriptionErrorContext(\n params.channel,\n \"snapshot\",\n formatConnectError(error, \"snapshot failed\"),\n ),\n );\n }\n\n function takePendingPublications(): TPublication[] {\n const buffered = pendingPublications;\n pendingPublications = [];\n return buffered;\n }\n\n function applyBufferedAsLive(): void {\n const buffered = takePendingPublications();\n if (buffered.length === 0) return;\n applyLivePublications(buffered);\n }\n\n function bufferPublications(publications: readonly TPublication[]): void {\n if (params.bufferPublicationKey) {\n const byKey = new Map<PropertyKey, TPublication>();\n for (const publication of pendingPublications) {\n byKey.set(params.bufferPublicationKey(publication), publication);\n }\n for (const publication of publications) {\n byKey.set(params.bufferPublicationKey(publication), publication);\n }\n pendingPublications = Array.from(byKey.values());\n return;\n }\n\n const next = pendingPublications.concat(publications);\n if (next.length > maxBufferedPublications) {\n droppedPublications += next.length - maxBufferedPublications;\n if (isDev()) {\n console.warn(\n `[snapshot-then-stream] ${params.channel}: buffer cap dropped ${droppedPublications} publication(s) while snapshot in flight`,\n );\n }\n }\n pendingPublications = next.slice(-maxBufferedPublications);\n }\n\n function clearRetryTimer(): void {\n if (retryTimer === null) return;\n clearTimeout(retryTimer);\n retryTimer = null;\n }\n\n function reportBufferedPublicationError(error: unknown): void {\n params.onError?.(\n createSdkSubscriptionErrorContext(params.channel, \"publication_handler\", error),\n );\n }\n\n function applyLivePublications(publications: readonly TPublication[]): void {\n try {\n params.applyLivePublications(publications);\n } catch (error) {\n reportBufferedPublicationError(error);\n }\n }\n\n function applySnapshot(\n snapshot: TSnapshot,\n bufferedPublications: readonly TPublication[],\n ): void {\n try {\n params.applySnapshot(snapshot, bufferedPublications);\n } catch (error) {\n reportBufferedPublicationError(error);\n }\n }\n\n function scheduleSnapshotRetry(): void {\n const retry = params.snapshotRetry;\n if (!retry || retryAttempts >= retry.maxAttempts || disposed) return;\n\n const delayMs = retry.delayMs * 2 ** retryAttempts;\n retryAttempts++;\n retryTimer = setTimeout(() => {\n retryTimer = null;\n refreshSnapshot(false);\n }, delayMs);\n }\n\n function refreshSnapshot(resetRetries = true): void {\n if (disposed) return;\n clearRetryTimer();\n if (resetRetries) retryAttempts = 0;\n\n const generation = ++refreshGeneration;\n ready = false;\n\n Promise.resolve()\n .then(() => params.fetchSnapshot())\n .then((snapshot) => {\n if (disposed || generation !== refreshGeneration) return;\n\n const buffered = takePendingPublications();\n applySnapshot(snapshot, buffered);\n\n if (disposed || generation !== refreshGeneration) return;\n ready = true;\n retryAttempts = 0;\n })\n .catch((error: unknown) => {\n if (disposed || generation !== refreshGeneration) return;\n reportSnapshotError(error);\n const failureMode =\n typeof params.snapshotFailureMode === \"function\"\n ? params.snapshotFailureMode(error)\n : params.snapshotFailureMode;\n if (failureMode === \"live\") {\n ready = true;\n applyBufferedAsLive();\n }\n scheduleSnapshotRetry();\n });\n }\n\n const unsubscribeRealtime = params.realtime.connectProtoChannel({\n channel: params.channel,\n schema: params.schema,\n onPublication: (message) => {\n const publications = params.readPublication(message);\n if (publications.length === 0) return;\n\n if (!ready) {\n bufferPublications(publications);\n return;\n }\n\n applyLivePublications(publications);\n },\n onConnected: () => {\n if (disposed) return;\n pendingPublications = [];\n refreshSnapshot();\n params.onOpen?.();\n },\n onDisconnected: () => {\n if (disposed) return;\n clearRetryTimer();\n refreshGeneration++;\n ready = false;\n pendingPublications = [];\n params.onClose?.();\n },\n onError: params.onError,\n });\n\n function unsubscribe(): void {\n disposed = true;\n clearRetryTimer();\n refreshGeneration++;\n pendingPublications = [];\n try {\n unsubscribeRealtime();\n } catch {\n // noop\n }\n }\n\n return {\n unsubscribe,\n refreshSnapshot,\n isReady: () => ready,\n isDisposed: () => disposed,\n };\n}\n"],"mappings":"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"}
|
package/dist/server-client.d.ts
CHANGED
|
@@ -39,7 +39,9 @@ declare class PolyesterServerClient extends PolyesterClient {
|
|
|
39
39
|
get hasDisplaySession(): boolean;
|
|
40
40
|
get session(): ServerSessionSnapshot;
|
|
41
41
|
/**
|
|
42
|
-
* Verifies the current server session
|
|
42
|
+
* Verifies the current server session. Returns null only when credentials
|
|
43
|
+
* are absent or rejected as unauthenticated. Transport, configuration, and
|
|
44
|
+
* other backend failures are rethrown so callers do not treat outages as logout.
|
|
43
45
|
*/
|
|
44
46
|
verifySession(): Promise<Me | null>;
|
|
45
47
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-client.d.ts","names":[],"sources":["../src/server-client.ts"],"mappings":";;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"server-client.d.ts","names":[],"sources":["../src/server-client.ts"],"mappings":";;;;;;;;;;KAkCY,8BAA8B;;EAEtC,OAAO,kBAAkB;;EAEzB,UAAU;;;;;;;EAOV;;;;;cAMS,8BAA8B;;EAK3B,YAAA,QAAQ;;;;;;YAyBD,4BAA4B;MAc3C;MAIA;MAIA;MAIA;MAIA,WAAW;;;;;;EAST,iBAAiB,QAAQ;;KAa9B,8BAA8B,WAAW,gBAAgB,4BACxD,KACI;;KAcE,sCACR,8BAA8B;;;;;;EAM1B,SAAS;;;;;;EAMT;;;KAII,sCACR,8BAA8B;EAC1B,SAAS;;;;;;EAMT;;;;;iBAMQ,uCACZ,QAAQ,sCACT;;;;iBA0Ba,uCACZ,QAAQ,sCACT"}
|
package/dist/server-client.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ConfigurationError } from "./shared/errors.js";
|
|
1
|
+
import { AuthenticationError, ConfigurationError } from "./shared/errors.js";
|
|
2
|
+
import { toPolyesterError } from "./shared/connect-error-mapping.js";
|
|
2
3
|
import { PolyesterClient, parsePolyesterClientConfig, pickPolyesterCatalogConfig } from "./core-client.js";
|
|
3
4
|
import "./services/auth/cookie-constants.js";
|
|
4
5
|
import { emptyServerSessionSnapshot, parseServerSessionSnapshot } from "./services/auth/session.js";
|
|
@@ -67,14 +68,18 @@ var PolyesterServerClient = class extends PolyesterClient {
|
|
|
67
68
|
return this.#session;
|
|
68
69
|
}
|
|
69
70
|
/**
|
|
70
|
-
* Verifies the current server session
|
|
71
|
+
* Verifies the current server session. Returns null only when credentials
|
|
72
|
+
* are absent or rejected as unauthenticated. Transport, configuration, and
|
|
73
|
+
* other backend failures are rethrown so callers do not treat outages as logout.
|
|
71
74
|
*/
|
|
72
75
|
async verifySession() {
|
|
73
76
|
if (!this.#hasAuthProvider) return null;
|
|
74
77
|
try {
|
|
75
78
|
return await this.auth.me();
|
|
76
|
-
} catch {
|
|
77
|
-
|
|
79
|
+
} catch (error) {
|
|
80
|
+
const mappedError = toPolyesterError(error);
|
|
81
|
+
if (mappedError instanceof AuthenticationError) return null;
|
|
82
|
+
throw mappedError;
|
|
78
83
|
}
|
|
79
84
|
}
|
|
80
85
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-client.js","names":["#hasAuthProvider","#session","#useDisplaySessionActiveAccountAsDefault"],"sources":["../src/server-client.ts"],"sourcesContent":["import {\n parsePolyesterClientConfig,\n pickPolyesterCatalogConfig,\n PolyesterClient,\n type PolyesterClientBaseConfig,\n} from \"./core-client.js\";\nimport {\n POLYESTER_AUTH_TOKEN_COOKIE_NAME,\n POLYESTER_SESSION_COOKIE_NAME,\n} from \"./services/auth/cookie-constants.js\";\nimport type { ServerSessionSnapshot } from \"./services/auth/session.types.js\";\nimport { emptyServerSessionSnapshot, parseServerSessionSnapshot } from \"./services/auth/session.js\";\nimport type { CookieGetter } from \"./utils/cookies.js\";\nimport type { JwtAuthProvider, ApiKeyEd25519AuthProvider } from \"./shared/transports.js\";\nimport type { SubaccountResolver } from \"./services/subaccount-resolver.js\";\nimport { isJwtValid } from \"./utils/jwt.js\";\nimport type { Me } from \"./services/auth/auth.js\";\nimport type { PolyesterEnvironment } from \"./environment.js\";\nimport { ConfigurationError } from \"./shared/errors.js\";\n\nexport type { ServerSessionSnapshot };\n\n/**\n * Parses a serialized session cookie into SDK session data.\n */\nexport function parseSessionCookie(\n cookies: CookieGetter,\n environment: PolyesterEnvironment,\n): ServerSessionSnapshot {\n return parseServerSessionSnapshot(cookies, environment);\n}\n\n/** Configuration for the server Polyester client. */\nexport type PolyesterServerClientConfig = PolyesterClientBaseConfig & {\n /** Auth provider config for HTTP/Connect endpoints. */\n auth?: JwtAuthProvider | ApiKeyEd25519AuthProvider;\n /** Bearer authentication and display-only session data parsed from cookies. */\n session?: ServerSessionSnapshot;\n /**\n * Use unsigned display-session `activeAccount` as the default subaccount for\n * calls that omit `subaccountId`. This preserves account-switcher intent for\n * apps that explicitly opt in; backend authorization must still decide what\n * the authenticated user can access.\n */\n useDisplaySessionActiveAccountAsDefault?: boolean;\n};\n\n/**\n * Server-side SDK client that can parse display-session cookies and verify bearer-token sessions with the backend.\n */\nexport class PolyesterServerClient extends PolyesterClient {\n #hasAuthProvider: boolean;\n #session: ServerSessionSnapshot;\n #useDisplaySessionActiveAccountAsDefault: boolean;\n\n constructor(config: PolyesterServerClientConfig) {\n config = parsePolyesterClientConfig(config);\n const auth = config.auth ?? undefined;\n\n super({\n environment: config.environment,\n interceptors: config.interceptors,\n auth,\n wireFormat: config.wireFormat,\n realtime: config.realtime,\n ...pickPolyesterCatalogConfig(config),\n transports: config.transports,\n realtimeClient: config.realtimeClient,\n });\n this.#hasAuthProvider = !!auth;\n this.#session = config.session ?? emptyServerSessionSnapshot();\n this.#useDisplaySessionActiveAccountAsDefault =\n config.useDisplaySessionActiveAccountAsDefault ?? false;\n }\n\n /**\n * Creates a resolver for server-side subaccount defaults. Display-session\n * `activeAccount` is only used as caller intent when explicitly enabled;\n * backend auth remains authoritative.\n */\n protected override createSubaccountResolver(): SubaccountResolver {\n return {\n getDefaultSubaccountId: () => {\n if (!this.#useDisplaySessionActiveAccountAsDefault) return null;\n\n const activeAccount = this.#session.activeAccount;\n if (activeAccount && !activeAccount.isMain) return activeAccount.accountId;\n return null;\n },\n getActiveAccountId: () => this.#session.activeAccount?.accountId ?? null,\n getMainAccountId: () => this.#session.activeAccount?.mainAccountId ?? null,\n };\n }\n\n get hasAuthProvider(): boolean {\n return this.#hasAuthProvider;\n }\n\n get hasBearerToken(): boolean {\n return !!this.#session.bearerToken;\n }\n\n get hasUsableBearerToken(): boolean {\n return isJwtValid(this.#session.bearerToken);\n }\n\n get hasDisplaySession(): boolean {\n return this.#session.hasDisplaySession;\n }\n\n get session(): ServerSessionSnapshot {\n return this.#session;\n }\n\n /**\n * Verifies the current server session and returns its auth state.\n */\n async verifySession(): Promise<Me | null> {\n if (!this.#hasAuthProvider) return null;\n\n try {\n return await this.auth.me();\n } catch {\n return null;\n }\n }\n}\n\ntype ServerClientFactoryBaseConfig<TConfig> = TConfig extends PolyesterClientBaseConfig\n ? Pick<\n TConfig,\n | \"environment\"\n | \"interceptors\"\n | \"realtime\"\n | \"wireFormat\"\n | \"catalog\"\n | \"catalogSnapshot\"\n | \"catalogCell\"\n | \"transports\"\n | \"realtimeClient\"\n >\n : never;\n\n/** Parameters for creating a server client from cookies. */\nexport type CreateServerClientFromCookiesParams =\n ServerClientFactoryBaseConfig<PolyesterClientBaseConfig> & {\n /**\n * A Request, name-value record, or synchronous cookie store whose `get`\n * method returns either a string or an object with a string `value`.\n * Await asynchronous framework cookie helpers before passing their result.\n */\n cookies: CookieGetter;\n /**\n * Use unsigned display-session `activeAccount` as the default subaccount for\n * calls that omit `subaccountId`. This is caller intent from UI hydration,\n * not proof of authority; backend authorization remains authoritative.\n */\n useDisplaySessionActiveAccountAsDefault?: boolean;\n };\n\n/** Parameters for creating a server client from a request. */\nexport type CreateServerClientFromRequestParams =\n ServerClientFactoryBaseConfig<PolyesterClientBaseConfig> & {\n request: Request;\n /**\n * Use unsigned display-session `activeAccount` as the default subaccount for\n * calls that omit `subaccountId`. This is caller intent from UI hydration,\n * not proof of authority; backend authorization remains authoritative.\n */\n useDisplaySessionActiveAccountAsDefault?: boolean;\n };\n\n/**\n * Creates a server SDK client from a Request, cookie record, or synchronous cookie store.\n */\nexport function createPolyesterServerClientFromCookies(\n params: CreateServerClientFromCookiesParams,\n): PolyesterServerClient {\n const session = parseSessionCookie(params.cookies, params.environment);\n const auth = isJwtValid(session.bearerToken)\n ? ({\n kind: \"jwt\",\n getToken: () => session.bearerToken,\n } satisfies JwtAuthProvider)\n : undefined;\n\n return new PolyesterServerClient({\n environment: params.environment,\n interceptors: params.interceptors,\n session,\n wireFormat: params.wireFormat,\n realtime: params.realtime,\n ...pickPolyesterCatalogConfig(params),\n transports: params.transports,\n realtimeClient: params.realtimeClient,\n auth,\n useDisplaySessionActiveAccountAsDefault: params.useDisplaySessionActiveAccountAsDefault,\n });\n}\n\n/**\n * Creates a server SDK client from a Request object.\n */\nexport function createPolyesterServerClientFromRequest(\n params: CreateServerClientFromRequestParams,\n): PolyesterServerClient {\n if (!(params?.request instanceof Request)) {\n throw new ConfigurationError(\"request is required and must be a Request.\");\n }\n return createPolyesterServerClientFromCookies({\n cookies: params.request,\n environment: params.environment,\n interceptors: params.interceptors,\n wireFormat: params.wireFormat,\n realtime: params.realtime,\n ...pickPolyesterCatalogConfig(params),\n transports: params.transports,\n realtimeClient: params.realtimeClient,\n useDisplaySessionActiveAccountAsDefault: params.useDisplaySessionActiveAccountAsDefault,\n });\n}\n\n/**\n * Reads a bearer token from supported auth cookies.\n */\nexport function getBearerTokenFromCookies(\n cookies: CookieGetter,\n environment: PolyesterEnvironment,\n): string | null {\n return parseSessionCookie(cookies, environment).bearerToken;\n}\n\nexport { POLYESTER_AUTH_TOKEN_COOKIE_NAME, POLYESTER_SESSION_COOKIE_NAME };\n"],"mappings":";;;;;;;;;AAyBA,SAAgB,mBACZ,SACA,aACqB;CACrB,OAAO,2BAA2B,SAAS,WAAW;AAC1D;;;;AAoBA,IAAa,wBAAb,cAA2C,gBAAgB;CACvD;CACA;CACA;CAEA,YAAY,QAAqC;EAC7C,SAAS,2BAA2B,MAAM;EAC1C,MAAM,OAAO,OAAO,QAAQ,KAAA;EAE5B,MAAM;GACF,aAAa,OAAO;GACpB,cAAc,OAAO;GACrB;GACA,YAAY,OAAO;GACnB,UAAU,OAAO;GACjB,GAAG,2BAA2B,MAAM;GACpC,YAAY,OAAO;GACnB,gBAAgB,OAAO;EAC3B,CAAC;EACD,KAAKA,mBAAmB,CAAC,CAAC;EAC1B,KAAKC,WAAW,OAAO,WAAW,2BAA2B;EAC7D,KAAKC,2CACD,OAAO,2CAA2C;CAC1D;;;;;;CAOA,2BAAkE;EAC9D,OAAO;GACH,8BAA8B;IAC1B,IAAI,CAAC,KAAKA,0CAA0C,OAAO;IAE3D,MAAM,gBAAgB,KAAKD,SAAS;IACpC,IAAI,iBAAiB,CAAC,cAAc,QAAQ,OAAO,cAAc;IACjE,OAAO;GACX;GACA,0BAA0B,KAAKA,SAAS,eAAe,aAAa;GACpE,wBAAwB,KAAKA,SAAS,eAAe,iBAAiB;EAC1E;CACJ;CAEA,IAAI,kBAA2B;EAC3B,OAAO,KAAKD;CAChB;CAEA,IAAI,iBAA0B;EAC1B,OAAO,CAAC,CAAC,KAAKC,SAAS;CAC3B;CAEA,IAAI,uBAAgC;EAChC,OAAO,WAAW,KAAKA,SAAS,WAAW;CAC/C;CAEA,IAAI,oBAA6B;EAC7B,OAAO,KAAKA,SAAS;CACzB;CAEA,IAAI,UAAiC;EACjC,OAAO,KAAKA;CAChB;;;;CAKA,MAAM,gBAAoC;EACtC,IAAI,CAAC,KAAKD,kBAAkB,OAAO;EAEnC,IAAI;GACA,OAAO,MAAM,KAAK,KAAK,GAAG;EAC9B,QAAQ;GACJ,OAAO;EACX;CACJ;AACJ;;;;AAiDA,SAAgB,uCACZ,QACqB;CACrB,MAAM,UAAU,mBAAmB,OAAO,SAAS,OAAO,WAAW;CACrE,MAAM,OAAO,WAAW,QAAQ,WAAW,IACpC;EACG,MAAM;EACN,gBAAgB,QAAQ;CAC5B,IACA,KAAA;CAEN,OAAO,IAAI,sBAAsB;EAC7B,aAAa,OAAO;EACpB,cAAc,OAAO;EACrB;EACA,YAAY,OAAO;EACnB,UAAU,OAAO;EACjB,GAAG,2BAA2B,MAAM;EACpC,YAAY,OAAO;EACnB,gBAAgB,OAAO;EACvB;EACA,yCAAyC,OAAO;CACpD,CAAC;AACL;;;;AAKA,SAAgB,uCACZ,QACqB;CACrB,IAAI,EAAE,QAAQ,mBAAmB,UAC7B,MAAM,IAAI,mBAAmB,4CAA4C;CAE7E,OAAO,uCAAuC;EAC1C,SAAS,OAAO;EAChB,aAAa,OAAO;EACpB,cAAc,OAAO;EACrB,YAAY,OAAO;EACnB,UAAU,OAAO;EACjB,GAAG,2BAA2B,MAAM;EACpC,YAAY,OAAO;EACnB,gBAAgB,OAAO;EACvB,yCAAyC,OAAO;CACpD,CAAC;AACL"}
|
|
1
|
+
{"version":3,"file":"server-client.js","names":["#hasAuthProvider","#session","#useDisplaySessionActiveAccountAsDefault"],"sources":["../src/server-client.ts"],"sourcesContent":["import {\n parsePolyesterClientConfig,\n pickPolyesterCatalogConfig,\n PolyesterClient,\n type PolyesterClientBaseConfig,\n} from \"./core-client.js\";\nimport {\n POLYESTER_AUTH_TOKEN_COOKIE_NAME,\n POLYESTER_SESSION_COOKIE_NAME,\n} from \"./services/auth/cookie-constants.js\";\nimport type { ServerSessionSnapshot } from \"./services/auth/session.types.js\";\nimport { emptyServerSessionSnapshot, parseServerSessionSnapshot } from \"./services/auth/session.js\";\nimport type { CookieGetter } from \"./utils/cookies.js\";\nimport type { JwtAuthProvider, ApiKeyEd25519AuthProvider } from \"./shared/transports.js\";\nimport type { SubaccountResolver } from \"./services/subaccount-resolver.js\";\nimport { isJwtValid } from \"./utils/jwt.js\";\nimport type { Me } from \"./services/auth/auth.js\";\nimport type { PolyesterEnvironment } from \"./environment.js\";\nimport { AuthenticationError, ConfigurationError } from \"./shared/errors.js\";\nimport { toPolyesterError } from \"./shared/connect-error-mapping.js\";\n\nexport type { ServerSessionSnapshot };\n\n/**\n * Parses a serialized session cookie into SDK session data.\n */\nexport function parseSessionCookie(\n cookies: CookieGetter,\n environment: PolyesterEnvironment,\n): ServerSessionSnapshot {\n return parseServerSessionSnapshot(cookies, environment);\n}\n\n/** Configuration for the server Polyester client. */\nexport type PolyesterServerClientConfig = PolyesterClientBaseConfig & {\n /** Auth provider config for HTTP/Connect endpoints. */\n auth?: JwtAuthProvider | ApiKeyEd25519AuthProvider;\n /** Bearer authentication and display-only session data parsed from cookies. */\n session?: ServerSessionSnapshot;\n /**\n * Use unsigned display-session `activeAccount` as the default subaccount for\n * calls that omit `subaccountId`. This preserves account-switcher intent for\n * apps that explicitly opt in; backend authorization must still decide what\n * the authenticated user can access.\n */\n useDisplaySessionActiveAccountAsDefault?: boolean;\n};\n\n/**\n * Server-side SDK client that can parse display-session cookies and verify bearer-token sessions with the backend.\n */\nexport class PolyesterServerClient extends PolyesterClient {\n #hasAuthProvider: boolean;\n #session: ServerSessionSnapshot;\n #useDisplaySessionActiveAccountAsDefault: boolean;\n\n constructor(config: PolyesterServerClientConfig) {\n config = parsePolyesterClientConfig(config);\n const auth = config.auth ?? undefined;\n\n super({\n environment: config.environment,\n interceptors: config.interceptors,\n auth,\n wireFormat: config.wireFormat,\n realtime: config.realtime,\n ...pickPolyesterCatalogConfig(config),\n transports: config.transports,\n realtimeClient: config.realtimeClient,\n });\n this.#hasAuthProvider = !!auth;\n this.#session = config.session ?? emptyServerSessionSnapshot();\n this.#useDisplaySessionActiveAccountAsDefault =\n config.useDisplaySessionActiveAccountAsDefault ?? false;\n }\n\n /**\n * Creates a resolver for server-side subaccount defaults. Display-session\n * `activeAccount` is only used as caller intent when explicitly enabled;\n * backend auth remains authoritative.\n */\n protected override createSubaccountResolver(): SubaccountResolver {\n return {\n getDefaultSubaccountId: () => {\n if (!this.#useDisplaySessionActiveAccountAsDefault) return null;\n\n const activeAccount = this.#session.activeAccount;\n if (activeAccount && !activeAccount.isMain) return activeAccount.accountId;\n return null;\n },\n getActiveAccountId: () => this.#session.activeAccount?.accountId ?? null,\n getMainAccountId: () => this.#session.activeAccount?.mainAccountId ?? null,\n };\n }\n\n get hasAuthProvider(): boolean {\n return this.#hasAuthProvider;\n }\n\n get hasBearerToken(): boolean {\n return !!this.#session.bearerToken;\n }\n\n get hasUsableBearerToken(): boolean {\n return isJwtValid(this.#session.bearerToken);\n }\n\n get hasDisplaySession(): boolean {\n return this.#session.hasDisplaySession;\n }\n\n get session(): ServerSessionSnapshot {\n return this.#session;\n }\n\n /**\n * Verifies the current server session. Returns null only when credentials\n * are absent or rejected as unauthenticated. Transport, configuration, and\n * other backend failures are rethrown so callers do not treat outages as logout.\n */\n async verifySession(): Promise<Me | null> {\n if (!this.#hasAuthProvider) return null;\n\n try {\n return await this.auth.me();\n } catch (error) {\n const mappedError = toPolyesterError(error);\n if (mappedError instanceof AuthenticationError) return null;\n throw mappedError;\n }\n }\n}\n\ntype ServerClientFactoryBaseConfig<TConfig> = TConfig extends PolyesterClientBaseConfig\n ? Pick<\n TConfig,\n | \"environment\"\n | \"interceptors\"\n | \"realtime\"\n | \"wireFormat\"\n | \"catalog\"\n | \"catalogSnapshot\"\n | \"catalogCell\"\n | \"transports\"\n | \"realtimeClient\"\n >\n : never;\n\n/** Parameters for creating a server client from cookies. */\nexport type CreateServerClientFromCookiesParams =\n ServerClientFactoryBaseConfig<PolyesterClientBaseConfig> & {\n /**\n * A Request, name-value record, or synchronous cookie store whose `get`\n * method returns either a string or an object with a string `value`.\n * Await asynchronous framework cookie helpers before passing their result.\n */\n cookies: CookieGetter;\n /**\n * Use unsigned display-session `activeAccount` as the default subaccount for\n * calls that omit `subaccountId`. This is caller intent from UI hydration,\n * not proof of authority; backend authorization remains authoritative.\n */\n useDisplaySessionActiveAccountAsDefault?: boolean;\n };\n\n/** Parameters for creating a server client from a request. */\nexport type CreateServerClientFromRequestParams =\n ServerClientFactoryBaseConfig<PolyesterClientBaseConfig> & {\n request: Request;\n /**\n * Use unsigned display-session `activeAccount` as the default subaccount for\n * calls that omit `subaccountId`. This is caller intent from UI hydration,\n * not proof of authority; backend authorization remains authoritative.\n */\n useDisplaySessionActiveAccountAsDefault?: boolean;\n };\n\n/**\n * Creates a server SDK client from a Request, cookie record, or synchronous cookie store.\n */\nexport function createPolyesterServerClientFromCookies(\n params: CreateServerClientFromCookiesParams,\n): PolyesterServerClient {\n const session = parseSessionCookie(params.cookies, params.environment);\n const auth = isJwtValid(session.bearerToken)\n ? ({\n kind: \"jwt\",\n getToken: () => session.bearerToken,\n } satisfies JwtAuthProvider)\n : undefined;\n\n return new PolyesterServerClient({\n environment: params.environment,\n interceptors: params.interceptors,\n session,\n wireFormat: params.wireFormat,\n realtime: params.realtime,\n ...pickPolyesterCatalogConfig(params),\n transports: params.transports,\n realtimeClient: params.realtimeClient,\n auth,\n useDisplaySessionActiveAccountAsDefault: params.useDisplaySessionActiveAccountAsDefault,\n });\n}\n\n/**\n * Creates a server SDK client from a Request object.\n */\nexport function createPolyesterServerClientFromRequest(\n params: CreateServerClientFromRequestParams,\n): PolyesterServerClient {\n if (!(params?.request instanceof Request)) {\n throw new ConfigurationError(\"request is required and must be a Request.\");\n }\n return createPolyesterServerClientFromCookies({\n cookies: params.request,\n environment: params.environment,\n interceptors: params.interceptors,\n wireFormat: params.wireFormat,\n realtime: params.realtime,\n ...pickPolyesterCatalogConfig(params),\n transports: params.transports,\n realtimeClient: params.realtimeClient,\n useDisplaySessionActiveAccountAsDefault: params.useDisplaySessionActiveAccountAsDefault,\n });\n}\n\n/**\n * Reads a bearer token from supported auth cookies.\n */\nexport function getBearerTokenFromCookies(\n cookies: CookieGetter,\n environment: PolyesterEnvironment,\n): string | null {\n return parseSessionCookie(cookies, environment).bearerToken;\n}\n\nexport { POLYESTER_AUTH_TOKEN_COOKIE_NAME, POLYESTER_SESSION_COOKIE_NAME };\n"],"mappings":";;;;;;;;;;AA0BA,SAAgB,mBACZ,SACA,aACqB;CACrB,OAAO,2BAA2B,SAAS,WAAW;AAC1D;;;;AAoBA,IAAa,wBAAb,cAA2C,gBAAgB;CACvD;CACA;CACA;CAEA,YAAY,QAAqC;EAC7C,SAAS,2BAA2B,MAAM;EAC1C,MAAM,OAAO,OAAO,QAAQ,KAAA;EAE5B,MAAM;GACF,aAAa,OAAO;GACpB,cAAc,OAAO;GACrB;GACA,YAAY,OAAO;GACnB,UAAU,OAAO;GACjB,GAAG,2BAA2B,MAAM;GACpC,YAAY,OAAO;GACnB,gBAAgB,OAAO;EAC3B,CAAC;EACD,KAAKA,mBAAmB,CAAC,CAAC;EAC1B,KAAKC,WAAW,OAAO,WAAW,2BAA2B;EAC7D,KAAKC,2CACD,OAAO,2CAA2C;CAC1D;;;;;;CAOA,2BAAkE;EAC9D,OAAO;GACH,8BAA8B;IAC1B,IAAI,CAAC,KAAKA,0CAA0C,OAAO;IAE3D,MAAM,gBAAgB,KAAKD,SAAS;IACpC,IAAI,iBAAiB,CAAC,cAAc,QAAQ,OAAO,cAAc;IACjE,OAAO;GACX;GACA,0BAA0B,KAAKA,SAAS,eAAe,aAAa;GACpE,wBAAwB,KAAKA,SAAS,eAAe,iBAAiB;EAC1E;CACJ;CAEA,IAAI,kBAA2B;EAC3B,OAAO,KAAKD;CAChB;CAEA,IAAI,iBAA0B;EAC1B,OAAO,CAAC,CAAC,KAAKC,SAAS;CAC3B;CAEA,IAAI,uBAAgC;EAChC,OAAO,WAAW,KAAKA,SAAS,WAAW;CAC/C;CAEA,IAAI,oBAA6B;EAC7B,OAAO,KAAKA,SAAS;CACzB;CAEA,IAAI,UAAiC;EACjC,OAAO,KAAKA;CAChB;;;;;;CAOA,MAAM,gBAAoC;EACtC,IAAI,CAAC,KAAKD,kBAAkB,OAAO;EAEnC,IAAI;GACA,OAAO,MAAM,KAAK,KAAK,GAAG;EAC9B,SAAS,OAAO;GACZ,MAAM,cAAc,iBAAiB,KAAK;GAC1C,IAAI,uBAAuB,qBAAqB,OAAO;GACvD,MAAM;EACV;CACJ;AACJ;;;;AAiDA,SAAgB,uCACZ,QACqB;CACrB,MAAM,UAAU,mBAAmB,OAAO,SAAS,OAAO,WAAW;CACrE,MAAM,OAAO,WAAW,QAAQ,WAAW,IACpC;EACG,MAAM;EACN,gBAAgB,QAAQ;CAC5B,IACA,KAAA;CAEN,OAAO,IAAI,sBAAsB;EAC7B,aAAa,OAAO;EACpB,cAAc,OAAO;EACrB;EACA,YAAY,OAAO;EACnB,UAAU,OAAO;EACjB,GAAG,2BAA2B,MAAM;EACpC,YAAY,OAAO;EACnB,gBAAgB,OAAO;EACvB;EACA,yCAAyC,OAAO;CACpD,CAAC;AACL;;;;AAKA,SAAgB,uCACZ,QACqB;CACrB,IAAI,EAAE,QAAQ,mBAAmB,UAC7B,MAAM,IAAI,mBAAmB,4CAA4C;CAE7E,OAAO,uCAAuC;EAC1C,SAAS,OAAO;EAChB,aAAa,OAAO;EACpB,cAAc,OAAO;EACrB,YAAY,OAAO;EACnB,UAAU,OAAO;EACjB,GAAG,2BAA2B,MAAM;EACpC,YAAY,OAAO;EACnB,gBAAgB,OAAO;EACvB,yCAAyC,OAAO;CACpD,CAAC;AACL"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","names":[],"sources":["../../../src/services/auth/session.ts"],"mappings":";;;;;UAyBiB;EACb;;UAGa;EACb;EACA,cAAc;EACd,UAAU;EACV,aAAa;EACb;EACA;EACA;EACA,gBAAgB;EAChB;;UAGa;EACb,UAAU;EACV,aAAa;EACb;EACA;EACA;EACA;;iBAoEY,8BAA8B;;;;iBAgB9B,2BACZ,SAAS,cACT,aAAa,uBACd;;;;cA+BU;;EAIG,cAAE;IAA4B;;EAK1C,OAAO;EAUP,yBAAyB,cAAc;
|
|
1
|
+
{"version":3,"file":"session.d.ts","names":[],"sources":["../../../src/services/auth/session.ts"],"mappings":";;;;;UAyBiB;EACb;;UAGa;EACb;EACA,cAAc;EACd,UAAU;EACV,aAAa;EACb;EACA;EACA;EACA,gBAAgB;EAChB;;UAGa;EACb,UAAU;EACV,aAAa;EACb;EACA;EACA;EACA;;iBAoEY,8BAA8B;;;;iBAgB9B,2BACZ,SAAS,cACT,aAAa,uBACd;;;;cA+BU;;EAIG,cAAE;IAA4B;;EAK1C,OAAO;EAUP,yBAAyB,cAAc;EAgBvC,YAAY,QAAQ,0BAA0B,cAAc;EAsB5D,cAAc,QAAQ,qBAAqB,UAAU,uBAAuB;EA8B5E,YAAY,yBAAyB,UAAU;EAO/C,iBACI,eAAe,KAAK,qCACpB,UAAU;EAoBd"}
|