convex 1.26.0-alpha.2 → 1.26.0-alpha.4
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 +43 -0
- package/dist/browser.bundle.js +102 -25
- package/dist/browser.bundle.js.map +2 -2
- package/dist/cjs/browser/http_client.js +2 -0
- package/dist/cjs/browser/http_client.js.map +2 -2
- package/dist/cjs/browser/logging.js +4 -4
- package/dist/cjs/browser/logging.js.map +2 -2
- package/dist/cjs/browser/simple_client.js +25 -0
- package/dist/cjs/browser/simple_client.js.map +2 -2
- package/dist/cjs/browser/sync/client.js +41 -2
- package/dist/cjs/browser/sync/client.js.map +2 -2
- package/dist/cjs/browser/sync/remote_query_set.js.map +2 -2
- package/dist/cjs/browser/sync/request_manager.js +8 -1
- package/dist/cjs/browser/sync/request_manager.js.map +2 -2
- package/dist/cjs/browser/sync/web_socket_manager.js +5 -1
- package/dist/cjs/browser/sync/web_socket_manager.js.map +2 -2
- package/dist/cjs/bundler/context.js.map +1 -1
- package/dist/cjs/bundler/debugBundle.js +31 -6
- package/dist/cjs/bundler/debugBundle.js.map +2 -2
- package/dist/cjs/bundler/depgraph.js +24 -26
- package/dist/cjs/bundler/depgraph.js.map +3 -3
- package/dist/cjs/cli/codegen_templates/api.js +51 -0
- package/dist/cjs/cli/codegen_templates/api.js.map +2 -2
- package/dist/cjs/cli/codegen_templates/readme.js +4 -4
- package/dist/cjs/cli/codegen_templates/readme.js.map +1 -1
- package/dist/cjs/cli/configure.js +11 -6
- package/dist/cjs/cli/configure.js.map +2 -2
- package/dist/cjs/cli/dev.js +5 -0
- package/dist/cjs/cli/dev.js.map +2 -2
- package/dist/cjs/cli/index.js +7 -3
- package/dist/cjs/cli/index.js.map +3 -3
- package/dist/cjs/cli/lib/codegen.js +33 -24
- package/dist/cjs/cli/lib/codegen.js.map +3 -3
- package/dist/cjs/cli/lib/deploymentSelection.js.map +2 -2
- package/dist/cjs/cli/lib/envvars.js +29 -0
- package/dist/cjs/cli/lib/envvars.js.map +2 -2
- package/dist/cjs/cli/lib/localDeployment/anonymous.js +21 -7
- package/dist/cjs/cli/lib/localDeployment/anonymous.js.map +2 -2
- package/dist/cjs/cli/lib/mcp/tools/run.js +1 -1
- package/dist/cjs/cli/lib/mcp/tools/run.js.map +2 -2
- package/dist/cjs/cli/lib/networkTest.js +8 -2
- package/dist/cjs/cli/lib/networkTest.js.map +2 -2
- package/dist/cjs/cli/lib/usage.js +4 -4
- package/dist/cjs/cli/lib/usage.js.map +1 -1
- package/dist/cjs/cli/lib/utils/prompts.js +4 -4
- package/dist/cjs/cli/lib/utils/prompts.js.map +2 -2
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/nextjs/index.js +9 -1
- package/dist/cjs/nextjs/index.js.map +2 -2
- package/dist/cjs/react/client.js +37 -0
- package/dist/cjs/react/client.js.map +2 -2
- package/dist/cjs/react/index.js +1 -0
- package/dist/cjs/react/index.js.map +2 -2
- package/dist/cjs/server/database.js.map +1 -1
- package/dist/cjs/server/impl/database_impl.js +26 -20
- package/dist/cjs/server/impl/database_impl.js.map +2 -2
- package/dist/cjs/server/impl/registration_impl.js +10 -2
- package/dist/cjs/server/impl/registration_impl.js.map +2 -2
- package/dist/cjs/values/validators.js +19 -0
- package/dist/cjs/values/validators.js.map +2 -2
- package/dist/cjs-types/browser/http_client.d.ts +2 -0
- package/dist/cjs-types/browser/http_client.d.ts.map +1 -1
- package/dist/cjs-types/browser/logging.d.ts +7 -1
- package/dist/cjs-types/browser/logging.d.ts.map +1 -1
- package/dist/cjs-types/browser/simple_client.d.ts +19 -0
- package/dist/cjs-types/browser/simple_client.d.ts.map +1 -1
- package/dist/cjs-types/browser/sync/client.d.ts +21 -0
- package/dist/cjs-types/browser/sync/client.d.ts.map +1 -1
- package/dist/cjs-types/browser/sync/client_node_test_helpers.d.ts +5 -0
- package/dist/cjs-types/browser/sync/client_node_test_helpers.d.ts.map +1 -1
- package/dist/cjs-types/browser/sync/remote_query_set.d.ts +2 -0
- package/dist/cjs-types/browser/sync/remote_query_set.d.ts.map +1 -1
- package/dist/cjs-types/browser/sync/request_manager.d.ts +2 -1
- package/dist/cjs-types/browser/sync/request_manager.d.ts.map +1 -1
- package/dist/cjs-types/browser/sync/web_socket_manager.d.ts +2 -1
- package/dist/cjs-types/browser/sync/web_socket_manager.d.ts.map +1 -1
- package/dist/cjs-types/bundler/debugBundle.d.ts +4 -2
- package/dist/cjs-types/bundler/debugBundle.d.ts.map +1 -1
- package/dist/cjs-types/bundler/depgraph.d.ts +5 -4
- package/dist/cjs-types/bundler/depgraph.d.ts.map +1 -1
- package/dist/cjs-types/cli/codegen.d.ts +1 -1
- package/dist/cjs-types/cli/codegen_templates/api.d.ts.map +1 -1
- package/dist/cjs-types/cli/configure.d.ts.map +1 -1
- package/dist/cjs-types/cli/deploy.d.ts +1 -1
- package/dist/cjs-types/cli/dev.d.ts +1 -1
- package/dist/cjs-types/cli/dev.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/codegen.d.ts +1 -0
- package/dist/cjs-types/cli/lib/codegen.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/deploymentSelection.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/envvars.d.ts +1 -0
- package/dist/cjs-types/cli/lib/envvars.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/localDeployment/anonymous.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/networkTest.d.ts.map +1 -1
- package/dist/cjs-types/cli/run.d.ts +1 -1
- package/dist/cjs-types/index.d.ts +1 -1
- package/dist/cjs-types/nextjs/index.d.ts +2 -2
- package/dist/cjs-types/react/client.d.ts +30 -0
- package/dist/cjs-types/react/client.d.ts.map +1 -1
- package/dist/cjs-types/react/index.d.ts +1 -1
- package/dist/cjs-types/react/index.d.ts.map +1 -1
- package/dist/cjs-types/server/database.d.ts.map +1 -1
- package/dist/cjs-types/server/database_api.test.d.ts +2 -0
- package/dist/cjs-types/server/database_api.test.d.ts.map +1 -0
- package/dist/cjs-types/server/impl/database_impl.d.ts.map +1 -1
- package/dist/cjs-types/server/impl/registration_impl.d.ts.map +1 -1
- package/dist/cjs-types/values/validators.d.ts.map +1 -1
- package/dist/cli.bundle.cjs +291 -97
- package/dist/cli.bundle.cjs.map +3 -3
- package/dist/esm/browser/http_client.js +2 -0
- package/dist/esm/browser/http_client.js.map +2 -2
- package/dist/esm/browser/logging.js +3 -3
- package/dist/esm/browser/logging.js.map +2 -2
- package/dist/esm/browser/simple_client.js +25 -0
- package/dist/esm/browser/simple_client.js.map +2 -2
- package/dist/esm/browser/sync/client.js +41 -2
- package/dist/esm/browser/sync/client.js.map +2 -2
- package/dist/esm/browser/sync/remote_query_set.js.map +2 -2
- package/dist/esm/browser/sync/request_manager.js +8 -1
- package/dist/esm/browser/sync/request_manager.js.map +2 -2
- package/dist/esm/browser/sync/web_socket_manager.js +5 -1
- package/dist/esm/browser/sync/web_socket_manager.js.map +2 -2
- package/dist/esm/bundler/context.js.map +1 -1
- package/dist/esm/bundler/debugBundle.js +33 -7
- package/dist/esm/bundler/debugBundle.js.map +2 -2
- package/dist/esm/bundler/depgraph.js +24 -26
- package/dist/esm/bundler/depgraph.js.map +3 -3
- package/dist/esm/cli/codegen_templates/api.js +51 -0
- package/dist/esm/cli/codegen_templates/api.js.map +2 -2
- package/dist/esm/cli/codegen_templates/readme.js +4 -4
- package/dist/esm/cli/codegen_templates/readme.js.map +1 -1
- package/dist/esm/cli/configure.js +12 -8
- package/dist/esm/cli/configure.js.map +2 -2
- package/dist/esm/cli/dev.js +5 -0
- package/dist/esm/cli/dev.js.map +2 -2
- package/dist/esm/cli/index.js +7 -3
- package/dist/esm/cli/index.js.map +2 -2
- package/dist/esm/cli/lib/codegen.js +33 -25
- package/dist/esm/cli/lib/codegen.js.map +3 -3
- package/dist/esm/cli/lib/deploymentSelection.js.map +2 -2
- package/dist/esm/cli/lib/envvars.js +28 -0
- package/dist/esm/cli/lib/envvars.js.map +2 -2
- package/dist/esm/cli/lib/localDeployment/anonymous.js +21 -7
- package/dist/esm/cli/lib/localDeployment/anonymous.js.map +2 -2
- package/dist/esm/cli/lib/mcp/tools/run.js +2 -2
- package/dist/esm/cli/lib/mcp/tools/run.js.map +2 -2
- package/dist/esm/cli/lib/networkTest.js +9 -3
- package/dist/esm/cli/lib/networkTest.js.map +2 -2
- package/dist/esm/cli/lib/usage.js +4 -4
- package/dist/esm/cli/lib/usage.js.map +1 -1
- package/dist/esm/cli/lib/utils/prompts.js +4 -4
- package/dist/esm/cli/lib/utils/prompts.js.map +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/nextjs/index.js +9 -1
- package/dist/esm/nextjs/index.js.map +2 -2
- package/dist/esm/react/client.js +37 -1
- package/dist/esm/react/client.js.map +2 -2
- package/dist/esm/react/index.js +2 -1
- package/dist/esm/react/index.js.map +2 -2
- package/dist/esm/server/impl/database_impl.js +26 -20
- package/dist/esm/server/impl/database_impl.js.map +2 -2
- package/dist/esm/server/impl/registration_impl.js +10 -2
- package/dist/esm/server/impl/registration_impl.js.map +2 -2
- package/dist/esm/values/validators.js +19 -0
- package/dist/esm/values/validators.js.map +2 -2
- package/dist/esm-types/browser/http_client.d.ts +2 -0
- package/dist/esm-types/browser/http_client.d.ts.map +1 -1
- package/dist/esm-types/browser/logging.d.ts +7 -1
- package/dist/esm-types/browser/logging.d.ts.map +1 -1
- package/dist/esm-types/browser/simple_client.d.ts +19 -0
- package/dist/esm-types/browser/simple_client.d.ts.map +1 -1
- package/dist/esm-types/browser/sync/client.d.ts +21 -0
- package/dist/esm-types/browser/sync/client.d.ts.map +1 -1
- package/dist/esm-types/browser/sync/client_node_test_helpers.d.ts +5 -0
- package/dist/esm-types/browser/sync/client_node_test_helpers.d.ts.map +1 -1
- package/dist/esm-types/browser/sync/remote_query_set.d.ts +2 -0
- package/dist/esm-types/browser/sync/remote_query_set.d.ts.map +1 -1
- package/dist/esm-types/browser/sync/request_manager.d.ts +2 -1
- package/dist/esm-types/browser/sync/request_manager.d.ts.map +1 -1
- package/dist/esm-types/browser/sync/web_socket_manager.d.ts +2 -1
- package/dist/esm-types/browser/sync/web_socket_manager.d.ts.map +1 -1
- package/dist/esm-types/bundler/debugBundle.d.ts +4 -2
- package/dist/esm-types/bundler/debugBundle.d.ts.map +1 -1
- package/dist/esm-types/bundler/depgraph.d.ts +5 -4
- package/dist/esm-types/bundler/depgraph.d.ts.map +1 -1
- package/dist/esm-types/cli/codegen.d.ts +1 -1
- package/dist/esm-types/cli/codegen_templates/api.d.ts.map +1 -1
- package/dist/esm-types/cli/configure.d.ts.map +1 -1
- package/dist/esm-types/cli/deploy.d.ts +1 -1
- package/dist/esm-types/cli/dev.d.ts +1 -1
- package/dist/esm-types/cli/dev.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/codegen.d.ts +1 -0
- package/dist/esm-types/cli/lib/codegen.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/deploymentSelection.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/envvars.d.ts +1 -0
- package/dist/esm-types/cli/lib/envvars.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/localDeployment/anonymous.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/networkTest.d.ts.map +1 -1
- package/dist/esm-types/cli/run.d.ts +1 -1
- package/dist/esm-types/index.d.ts +1 -1
- package/dist/esm-types/nextjs/index.d.ts +2 -2
- package/dist/esm-types/react/client.d.ts +30 -0
- package/dist/esm-types/react/client.d.ts.map +1 -1
- package/dist/esm-types/react/index.d.ts +1 -1
- package/dist/esm-types/react/index.d.ts.map +1 -1
- package/dist/esm-types/server/database.d.ts.map +1 -1
- package/dist/esm-types/server/database_api.test.d.ts +2 -0
- package/dist/esm-types/server/database_api.test.d.ts.map +1 -0
- package/dist/esm-types/server/impl/database_impl.d.ts.map +1 -1
- package/dist/esm-types/server/impl/registration_impl.d.ts.map +1 -1
- package/dist/esm-types/values/validators.d.ts.map +1 -1
- package/dist/react.bundle.js +172 -84
- package/dist/react.bundle.js.map +4 -4
- package/package.json +3 -3
- package/src/browser/.claude/settings.local.json +1 -1
- package/src/browser/http_client.ts +2 -0
- package/src/browser/logging.ts +10 -3
- package/src/browser/simple_client.ts +29 -0
- package/src/browser/sync/client.ts +54 -1
- package/src/browser/sync/client_node.test.ts +415 -0
- package/src/browser/sync/client_node_test_helpers.ts +19 -0
- package/src/browser/sync/remote_query_set.ts +2 -0
- package/src/browser/sync/request_manager.test.ts +59 -1
- package/src/browser/sync/request_manager.ts +10 -1
- package/src/browser/sync/web_socket_manager.ts +4 -0
- package/src/bundler/context.ts +1 -1
- package/src/bundler/debugBundle.ts +32 -4
- package/src/bundler/depgraph.ts +39 -41
- package/src/cli/codegen_templates/api.ts +54 -0
- package/src/cli/codegen_templates/readme.ts +4 -4
- package/src/cli/configure.ts +27 -21
- package/src/cli/dev.ts +6 -0
- package/src/cli/index.ts +12 -3
- package/src/cli/lib/codegen.ts +11 -1
- package/src/cli/lib/deploymentSelection.ts +2 -0
- package/src/cli/lib/envvars.ts +42 -0
- package/src/cli/lib/localDeployment/anonymous.ts +25 -7
- package/src/cli/lib/mcp/tools/run.ts +2 -2
- package/src/cli/lib/networkTest.ts +10 -3
- package/src/cli/lib/usage.ts +4 -4
- package/src/cli/lib/utils/prompts.ts +4 -4
- package/src/index.ts +1 -1
- package/src/nextjs/index.ts +21 -3
- package/src/react/client.ts +62 -1
- package/src/react/index.ts +1 -0
- package/src/server/database.ts +71 -0
- package/src/server/database_api.test.ts +360 -0
- package/src/server/impl/database_impl.ts +37 -17
- package/src/server/impl/registration_impl.ts +10 -2
- package/src/values/validator.test.ts +71 -0
- package/src/values/validators.ts +24 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/browser/sync/client.ts"],
|
|
4
|
-
"sourcesContent": ["import { version } from \"../../index.js\";\nimport { convexToJson, Value } from \"../../values/index.js\";\nimport {\n createHybridErrorStacktrace,\n forwardData,\n instantiateDefaultLogger,\n instantiateNoopLogger,\n logFatalError,\n Logger,\n} from \"../logging.js\";\nimport { LocalSyncState } from \"./local_state.js\";\nimport { RequestManager } from \"./request_manager.js\";\nimport {\n OptimisticLocalStore,\n OptimisticUpdate,\n} from \"./optimistic_updates.js\";\nimport {\n OptimisticQueryResults,\n QueryResultsMap,\n} from \"./optimistic_updates_impl.js\";\nimport {\n ActionRequest,\n MutationRequest,\n QueryId,\n QueryJournal,\n RequestId,\n ServerMessage,\n TS,\n UserIdentityAttributes,\n} from \"./protocol.js\";\nimport { RemoteQuerySet } from \"./remote_query_set.js\";\nimport { QueryToken, serializePathAndArgs } from \"./udf_path_utils.js\";\nimport { ReconnectMetadata, WebSocketManager } from \"./web_socket_manager.js\";\nimport { newSessionId } from \"./session.js\";\nimport { FunctionResult } from \"./function_result.js\";\nimport {\n AuthenticationManager,\n AuthTokenFetcher,\n} from \"./authentication_manager.js\";\nexport { type AuthTokenFetcher } from \"./authentication_manager.js\";\nimport { getMarksReport, mark, MarkName } from \"./metrics.js\";\nimport { parseArgs, validateDeploymentUrl } from \"../../common/index.js\";\nimport { ConvexError } from \"../../values/errors.js\";\n\n/**\n * Options for {@link BaseConvexClient}.\n *\n * @public\n */\nexport interface BaseConvexClientOptions {\n /**\n * Whether to prompt the user if they have unsaved changes pending\n * when navigating away or closing a web page.\n *\n * This is only possible when the `window` object exists, i.e. in a browser.\n *\n * The default value is `true` in browsers.\n */\n unsavedChangesWarning?: boolean;\n /**\n * Specifies an alternate\n * [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)\n * constructor to use for client communication with the Convex cloud.\n * The default behavior is to use `WebSocket` from the global environment.\n */\n webSocketConstructor?: typeof WebSocket;\n /**\n * Adds additional logging for debugging purposes.\n *\n * The default value is `false`.\n */\n verbose?: boolean;\n /**\n * A logger, `true`, or `false`. If not provided or `true`, logs to the console.\n * If `false`, logs are not printed anywhere.\n *\n * You can construct your own logger to customize logging to log elsewhere.\n */\n logger?: Logger | boolean;\n /**\n * Sends additional metrics to Convex for debugging purposes.\n *\n * The default value is `false`.\n */\n reportDebugInfoToConvex?: boolean;\n /**\n * This API is experimental: it may change or disappear.\n *\n * A function to call on receiving abnormal WebSocket close messages from the\n * connected Convex deployment. The content of these messages is not stable,\n * it is an implementation detail that may change.\n *\n * Consider this API an observability stopgap until higher level codes with\n * recommendations on what to do are available, which could be a more stable\n * interface instead of `string`.\n *\n * Check `connectionState` for more quantitative metrics about connection status.\n */\n onServerDisconnectError?: (message: string) => void;\n /**\n * Skip validating that the Convex deployment URL looks like\n * `https://happy-animal-123.convex.cloud` or localhost.\n *\n * This can be useful if running a self-hosted Convex backend that uses a different\n * URL.\n *\n * The default value is `false`\n */\n skipConvexDeploymentUrlCheck?: boolean;\n /**\n * If using auth, the number of seconds before a token expires that we should refresh it.\n *\n * The default value is `2`.\n */\n authRefreshTokenLeewaySeconds?: number;\n}\n\n/**\n * State describing the client's connection with the Convex backend.\n *\n * @public\n */\nexport type ConnectionState = {\n hasInflightRequests: boolean;\n isWebSocketConnected: boolean;\n timeOfOldestInflightRequest: Date | null;\n /**\n * True if the client has ever opened a WebSocket to the \"ready\" state.\n */\n hasEverConnected: boolean;\n /**\n * The number of times this client has connected to the Convex backend.\n *\n * A number of things can cause the client to reconnect -- server errors,\n * bad internet, auth expiring. But this number being high is an indication\n * that the client is having trouble keeping a stable connection.\n */\n connectionCount: number;\n /**\n * The number of times this client has tried (and failed) to connect to the Convex backend.\n */\n connectionRetries: number;\n /**\n * The number of mutations currently in flight.\n */\n inflightMutations: number;\n /**\n * The number of actions currently in flight.\n */\n inflightActions: number;\n};\n\n/**\n * Options for {@link BaseConvexClient.subscribe}.\n *\n * @public\n */\nexport interface SubscribeOptions {\n /**\n * An (optional) journal produced from a previous execution of this query\n * function.\n *\n * If there is an existing subscription to a query function with the same\n * name and arguments, this journal will have no effect.\n */\n journal?: QueryJournal;\n\n /**\n * @internal\n */\n componentPath?: string;\n}\n\n/**\n * Options for {@link BaseConvexClient.mutation}.\n *\n * @public\n */\nexport interface MutationOptions {\n /**\n * An optimistic update to apply along with this mutation.\n *\n * An optimistic update locally updates queries while a mutation is pending.\n * Once the mutation completes, the update will be rolled back.\n */\n optimisticUpdate?: OptimisticUpdate<any>;\n}\n\n/**\n * Type describing updates to a query within a `Transition`.\n *\n * @public\n */\nexport type QueryModification =\n // `undefined` generally comes from an optimistic update setting the query to be loading\n { kind: \"Updated\"; result: FunctionResult | undefined } | { kind: \"Removed\" };\n\n/**\n * Object describing a transition passed into the `onTransition` handler.\n *\n * These can be from receiving a transition from the server, or from applying an\n * optimistic update locally.\n *\n * @public\n */\nexport type Transition = {\n queries: Array<{ token: QueryToken; modification: QueryModification }>;\n reflectedMutations: Array<{ requestId: RequestId; result: FunctionResult }>;\n timestamp: TS;\n};\n\n/**\n * Low-level client for directly integrating state management libraries\n * with Convex.\n *\n * Most developers should use higher level clients, like\n * the {@link ConvexHttpClient} or the React hook based {@link react.ConvexReactClient}.\n *\n * @public\n */\nexport class BaseConvexClient {\n private readonly address: string;\n private readonly state: LocalSyncState;\n private readonly requestManager: RequestManager;\n private readonly webSocketManager: WebSocketManager;\n private readonly authenticationManager: AuthenticationManager;\n private remoteQuerySet: RemoteQuerySet;\n private readonly optimisticQueryResults: OptimisticQueryResults;\n private _transitionHandlerCounter = 0;\n private _nextRequestId: RequestId;\n private _onTransitionFns: Map<number, (transition: Transition) => void> =\n new Map();\n private readonly _sessionId: string;\n private firstMessageReceived = false;\n private readonly debug: boolean;\n private readonly logger: Logger;\n private maxObservedTimestamp: TS | undefined;\n\n /**\n * @param address - The url of your Convex deployment, often provided\n * by an environment variable. E.g. `https://small-mouse-123.convex.cloud`.\n * @param onTransition - A callback receiving an array of query tokens\n * corresponding to query results that have changed -- additional handlers\n * can be added via `addOnTransitionHandler`.\n * @param options - See {@link BaseConvexClientOptions} for a full description.\n */\n constructor(\n address: string,\n onTransition: (updatedQueries: QueryToken[]) => void,\n options?: BaseConvexClientOptions,\n ) {\n if (typeof address === \"object\") {\n throw new Error(\n \"Passing a ClientConfig object is no longer supported. Pass the URL of the Convex deployment as a string directly.\",\n );\n }\n if (options?.skipConvexDeploymentUrlCheck !== true) {\n validateDeploymentUrl(address);\n }\n options = { ...options };\n const authRefreshTokenLeewaySeconds =\n options.authRefreshTokenLeewaySeconds ?? 2;\n let webSocketConstructor = options.webSocketConstructor;\n if (!webSocketConstructor && typeof WebSocket === \"undefined\") {\n throw new Error(\n \"No WebSocket global variable defined! To use Convex in an environment without WebSocket try the HTTP client: https://docs.convex.dev/api/classes/browser.ConvexHttpClient\",\n );\n }\n webSocketConstructor = webSocketConstructor || WebSocket;\n this.debug = options.reportDebugInfoToConvex ?? false;\n this.address = address;\n this.logger =\n options.logger === false\n ? instantiateNoopLogger({ verbose: options.verbose ?? false })\n : options.logger !== true && options.logger\n ? options.logger\n : instantiateDefaultLogger({ verbose: options.verbose ?? false });\n // Substitute http(s) with ws(s)\n const i = address.search(\"://\");\n if (i === -1) {\n throw new Error(\"Provided address was not an absolute URL.\");\n }\n const origin = address.substring(i + 3); // move past the double slash\n const protocol = address.substring(0, i);\n let wsProtocol;\n if (protocol === \"http\") {\n wsProtocol = \"ws\";\n } else if (protocol === \"https\") {\n wsProtocol = \"wss\";\n } else {\n throw new Error(`Unknown parent protocol ${protocol}`);\n }\n const wsUri = `${wsProtocol}://${origin}/api/${version}/sync`;\n\n this.state = new LocalSyncState();\n this.remoteQuerySet = new RemoteQuerySet(\n (queryId) => this.state.queryPath(queryId),\n this.logger,\n );\n this.requestManager = new RequestManager(this.logger);\n this.authenticationManager = new AuthenticationManager(\n this.state,\n {\n authenticate: (token) => {\n const message = this.state.setAuth(token);\n this.webSocketManager.sendMessage(message);\n return message.baseVersion;\n },\n stopSocket: () => this.webSocketManager.stop(),\n tryRestartSocket: () => this.webSocketManager.tryRestart(),\n pauseSocket: () => {\n this.webSocketManager.pause();\n this.state.pause();\n },\n resumeSocket: () => this.webSocketManager.resume(),\n clearAuth: () => {\n this.clearAuth();\n },\n },\n {\n logger: this.logger,\n refreshTokenLeewaySeconds: authRefreshTokenLeewaySeconds,\n },\n );\n this.optimisticQueryResults = new OptimisticQueryResults();\n this.addOnTransitionHandler((transition) => {\n onTransition(transition.queries.map((q) => q.token));\n });\n this._nextRequestId = 0;\n this._sessionId = newSessionId();\n\n const { unsavedChangesWarning } = options;\n if (\n typeof window === \"undefined\" ||\n typeof window.addEventListener === \"undefined\"\n ) {\n if (unsavedChangesWarning === true) {\n throw new Error(\n \"unsavedChangesWarning requested, but window.addEventListener not found! Remove {unsavedChangesWarning: true} from Convex client options.\",\n );\n }\n } else if (unsavedChangesWarning !== false) {\n // Listen for tab close events and notify the user on unsaved changes.\n window.addEventListener(\"beforeunload\", (e) => {\n if (this.requestManager.hasIncompleteRequests()) {\n // There are 3 different ways to trigger this pop up so just try all of\n // them.\n\n e.preventDefault();\n // This confirmation message doesn't actually appear in most modern\n // browsers but we tried.\n const confirmationMessage =\n \"Are you sure you want to leave? Your changes may not be saved.\";\n (e || window.event).returnValue = confirmationMessage;\n return confirmationMessage;\n }\n });\n }\n\n this.webSocketManager = new WebSocketManager(\n wsUri,\n {\n onOpen: (reconnectMetadata: ReconnectMetadata) => {\n // We have a new WebSocket!\n this.mark(\"convexWebSocketOpen\");\n this.webSocketManager.sendMessage({\n ...reconnectMetadata,\n type: \"Connect\",\n sessionId: this._sessionId,\n maxObservedTimestamp: this.maxObservedTimestamp,\n });\n\n // Throw out our remote query, reissue queries\n // and outstanding mutations, and reauthenticate.\n const oldRemoteQueryResults = new Set(\n this.remoteQuerySet.remoteQueryResults().keys(),\n );\n this.remoteQuerySet = new RemoteQuerySet(\n (queryId) => this.state.queryPath(queryId),\n this.logger,\n );\n const [querySetModification, authModification] = this.state.restart(\n oldRemoteQueryResults,\n );\n if (authModification) {\n this.webSocketManager.sendMessage(authModification);\n }\n this.webSocketManager.sendMessage(querySetModification);\n for (const message of this.requestManager.restart()) {\n this.webSocketManager.sendMessage(message);\n }\n },\n onResume: () => {\n const [querySetModification, authModification] = this.state.resume();\n if (authModification) {\n this.webSocketManager.sendMessage(authModification);\n }\n if (querySetModification) {\n this.webSocketManager.sendMessage(querySetModification);\n }\n for (const message of this.requestManager.resume()) {\n this.webSocketManager.sendMessage(message);\n }\n },\n onMessage: (serverMessage: ServerMessage) => {\n // Metrics events grow linearly with reconnection attempts so this\n // conditional prevents n^2 metrics reporting.\n if (!this.firstMessageReceived) {\n this.firstMessageReceived = true;\n this.mark(\"convexFirstMessageReceived\");\n this.reportMarks();\n }\n switch (serverMessage.type) {\n case \"Transition\": {\n this.observedTimestamp(serverMessage.endVersion.ts);\n this.authenticationManager.onTransition(serverMessage);\n this.remoteQuerySet.transition(serverMessage);\n this.state.transition(serverMessage);\n const completedRequests = this.requestManager.removeCompleted(\n this.remoteQuerySet.timestamp(),\n );\n this.notifyOnQueryResultChanges(completedRequests);\n break;\n }\n case \"MutationResponse\": {\n if (serverMessage.success) {\n this.observedTimestamp(serverMessage.ts);\n }\n const completedMutationInfo =\n this.requestManager.onResponse(serverMessage);\n if (completedMutationInfo !== null) {\n this.notifyOnQueryResultChanges(\n new Map([\n [\n completedMutationInfo.requestId,\n completedMutationInfo.result,\n ],\n ]),\n );\n }\n break;\n }\n case \"ActionResponse\": {\n this.requestManager.onResponse(serverMessage);\n break;\n }\n case \"AuthError\": {\n this.authenticationManager.onAuthError(serverMessage);\n break;\n }\n case \"FatalError\": {\n const error = logFatalError(this.logger, serverMessage.error);\n void this.webSocketManager.terminate();\n throw error;\n }\n case \"Ping\":\n break; // do nothing\n default: {\n const _typeCheck: never = serverMessage;\n }\n }\n\n return {\n hasSyncedPastLastReconnect: this.hasSyncedPastLastReconnect(),\n };\n },\n onServerDisconnectError: options.onServerDisconnectError,\n },\n webSocketConstructor,\n this.logger,\n );\n this.mark(\"convexClientConstructed\");\n }\n\n /**\n * Return true if there is outstanding work from prior to the time of the most recent restart.\n * This indicates that the client has not proven itself to have gotten past the issue that\n * potentially led to the restart. Use this to influence when to reset backoff after a failure.\n */\n private hasSyncedPastLastReconnect() {\n const hasSyncedPastLastReconnect =\n this.requestManager.hasSyncedPastLastReconnect() ||\n this.state.hasSyncedPastLastReconnect();\n return hasSyncedPastLastReconnect;\n }\n\n private observedTimestamp(observedTs: TS) {\n if (\n this.maxObservedTimestamp === undefined ||\n this.maxObservedTimestamp.lessThanOrEqual(observedTs)\n ) {\n this.maxObservedTimestamp = observedTs;\n }\n }\n\n getMaxObservedTimestamp() {\n return this.maxObservedTimestamp;\n }\n\n /**\n * Compute the current query results based on the remoteQuerySet and the\n * current optimistic updates and call `onTransition` for all the changed\n * queries.\n *\n * @param completedMutations - A set of mutation IDs whose optimistic updates\n * are no longer needed.\n */\n private notifyOnQueryResultChanges(\n completedRequests: Map<RequestId, FunctionResult>,\n ) {\n const remoteQueryResults: Map<QueryId, FunctionResult> =\n this.remoteQuerySet.remoteQueryResults();\n const queryTokenToValue: QueryResultsMap = new Map();\n for (const [queryId, result] of remoteQueryResults) {\n const queryToken = this.state.queryToken(queryId);\n // It's possible that we've already unsubscribed to this query but\n // the server hasn't learned about that yet. If so, ignore this one.\n\n if (queryToken !== null) {\n const query = {\n result,\n udfPath: this.state.queryPath(queryId)!,\n args: this.state.queryArgs(queryId)!,\n };\n queryTokenToValue.set(queryToken, query);\n }\n }\n\n // Query tokens that are new (because of new server results or new local optimistic updates)\n // or differ from old values (because of changes from local optimistic updates or new results\n // from the server).\n const changedQueryTokens =\n this.optimisticQueryResults.ingestQueryResultsFromServer(\n queryTokenToValue,\n new Set(completedRequests.keys()),\n );\n\n this.handleTransition({\n queries: changedQueryTokens.map((token) => {\n const optimisticResult =\n this.optimisticQueryResults.rawQueryResult(token);\n return {\n token,\n modification: {\n kind: \"Updated\",\n result: optimisticResult!.result,\n },\n };\n }),\n reflectedMutations: Array.from(completedRequests).map(\n ([requestId, result]) => ({\n requestId,\n result,\n }),\n ),\n timestamp: this.remoteQuerySet.timestamp(),\n });\n }\n\n private handleTransition(transition: Transition) {\n for (const fn of this._onTransitionFns.values()) {\n fn(transition);\n }\n }\n\n /**\n * Add a handler that will be called on a transition.\n *\n * Any external side effects (e.g. setting React state) should be handled here.\n *\n * @param fn\n *\n * @returns\n */\n addOnTransitionHandler(fn: (transition: Transition) => void) {\n const id = this._transitionHandlerCounter++;\n this._onTransitionFns.set(id, fn);\n return () => this._onTransitionFns.delete(id);\n }\n\n /**\n * Set the authentication token to be used for subsequent queries and mutations.\n * `fetchToken` will be called automatically again if a token expires.\n * `fetchToken` should return `null` if the token cannot be retrieved, for example\n * when the user's rights were permanently revoked.\n * @param fetchToken - an async function returning the JWT-encoded OpenID Connect Identity Token\n * @param onChange - a callback that will be called when the authentication status changes\n */\n setAuth(\n fetchToken: AuthTokenFetcher,\n onChange: (isAuthenticated: boolean) => void,\n ) {\n void this.authenticationManager.setConfig(fetchToken, onChange);\n }\n\n hasAuth() {\n return this.state.hasAuth();\n }\n\n /** @internal */\n setAdminAuth(value: string, fakeUserIdentity?: UserIdentityAttributes) {\n const message = this.state.setAdminAuth(value, fakeUserIdentity);\n this.webSocketManager.sendMessage(message);\n }\n\n clearAuth() {\n const message = this.state.clearAuth();\n this.webSocketManager.sendMessage(message);\n }\n\n /**\n * Subscribe to a query function.\n *\n * Whenever this query's result changes, the `onTransition` callback\n * passed into the constructor will be called.\n *\n * @param name - The name of the query.\n * @param args - An arguments object for the query. If this is omitted, the\n * arguments will be `{}`.\n * @param options - A {@link SubscribeOptions} options object for this query.\n\n * @returns An object containing a {@link QueryToken} corresponding to this\n * query and an `unsubscribe` callback.\n */\n subscribe(\n name: string,\n args?: Record<string, Value>,\n options?: SubscribeOptions,\n ): { queryToken: QueryToken; unsubscribe: () => void } {\n const argsObject = parseArgs(args);\n\n const { modification, queryToken, unsubscribe } = this.state.subscribe(\n name,\n argsObject,\n options?.journal,\n options?.componentPath,\n );\n if (modification !== null) {\n this.webSocketManager.sendMessage(modification);\n }\n return {\n queryToken,\n unsubscribe: () => {\n const modification = unsubscribe();\n if (modification) {\n this.webSocketManager.sendMessage(modification);\n }\n },\n };\n }\n\n /**\n * A query result based only on the current, local state.\n *\n * The only way this will return a value is if we're already subscribed to the\n * query or its value has been set optimistically.\n */\n localQueryResult(\n udfPath: string,\n args?: Record<string, Value>,\n ): Value | undefined {\n const argsObject = parseArgs(args);\n const queryToken = serializePathAndArgs(udfPath, argsObject);\n return this.optimisticQueryResults.queryResult(queryToken);\n }\n\n /**\n * Get query result by query token based on current, local state\n *\n * The only way this will return a value is if we're already subscribed to the\n * query or its value has been set optimistically.\n *\n * @internal\n */\n localQueryResultByToken(queryToken: QueryToken): Value | undefined {\n return this.optimisticQueryResults.queryResult(queryToken);\n }\n\n /**\n * Whether local query result is available for a toke.\n *\n * This method does not throw if the result is an error.\n *\n * @internal\n */\n hasLocalQueryResultByToken(queryToken: QueryToken): boolean {\n return this.optimisticQueryResults.hasQueryResult(queryToken);\n }\n\n /**\n * @internal\n */\n localQueryLogs(\n udfPath: string,\n args?: Record<string, Value>,\n ): string[] | undefined {\n const argsObject = parseArgs(args);\n const queryToken = serializePathAndArgs(udfPath, argsObject);\n return this.optimisticQueryResults.queryLogs(queryToken);\n }\n\n /**\n * Retrieve the current {@link QueryJournal} for this query function.\n *\n * If we have not yet received a result for this query, this will be `undefined`.\n *\n * @param name - The name of the query.\n * @param args - The arguments object for this query.\n * @returns The query's {@link QueryJournal} or `undefined`.\n */\n queryJournal(\n name: string,\n args?: Record<string, Value>,\n ): QueryJournal | undefined {\n const argsObject = parseArgs(args);\n const queryToken = serializePathAndArgs(name, argsObject);\n return this.state.queryJournal(queryToken);\n }\n\n /**\n * Get the current {@link ConnectionState} between the client and the Convex\n * backend.\n *\n * @returns The {@link ConnectionState} with the Convex backend.\n */\n connectionState(): ConnectionState {\n const wsConnectionState = this.webSocketManager.connectionState();\n return {\n hasInflightRequests: this.requestManager.hasInflightRequests(),\n isWebSocketConnected: wsConnectionState.isConnected,\n hasEverConnected: wsConnectionState.hasEverConnected,\n connectionCount: wsConnectionState.connectionCount,\n connectionRetries: wsConnectionState.connectionRetries,\n timeOfOldestInflightRequest:\n this.requestManager.timeOfOldestInflightRequest(),\n inflightMutations: this.requestManager.inflightMutations(),\n inflightActions: this.requestManager.inflightActions(),\n };\n }\n\n /**\n * Execute a mutation function.\n *\n * @param name - The name of the mutation.\n * @param args - An arguments object for the mutation. If this is omitted,\n * the arguments will be `{}`.\n * @param options - A {@link MutationOptions} options object for this mutation.\n\n * @returns - A promise of the mutation's result.\n */\n async mutation(\n name: string,\n args?: Record<string, Value>,\n options?: MutationOptions,\n ): Promise<any> {\n const result = await this.mutationInternal(name, args, options);\n if (!result.success) {\n if (result.errorData !== undefined) {\n throw forwardData(\n result,\n new ConvexError(\n createHybridErrorStacktrace(\"mutation\", name, result),\n ),\n );\n }\n throw new Error(createHybridErrorStacktrace(\"mutation\", name, result));\n }\n return result.value;\n }\n\n /**\n * @internal\n */\n async mutationInternal(\n udfPath: string,\n args?: Record<string, Value>,\n options?: MutationOptions,\n componentPath?: string,\n ): Promise<FunctionResult> {\n const { mutationPromise } = this.enqueueMutation(\n udfPath,\n args,\n options,\n componentPath,\n );\n return mutationPromise;\n }\n\n /**\n * @internal\n */\n enqueueMutation(\n udfPath: string,\n args?: Record<string, Value>,\n options?: MutationOptions,\n componentPath?: string,\n ): { requestId: RequestId; mutationPromise: Promise<FunctionResult> } {\n const mutationArgs = parseArgs(args);\n this.tryReportLongDisconnect();\n const requestId = this.nextRequestId;\n this._nextRequestId++;\n\n if (options !== undefined) {\n const optimisticUpdate = options.optimisticUpdate;\n if (optimisticUpdate !== undefined) {\n const wrappedUpdate = (localQueryStore: OptimisticLocalStore) => {\n const result: unknown = optimisticUpdate(\n localQueryStore,\n mutationArgs,\n );\n if (result instanceof Promise) {\n this.logger.warn(\n \"Optimistic update handler returned a Promise. Optimistic updates should be synchronous.\",\n );\n }\n };\n\n const changedQueryTokens =\n this.optimisticQueryResults.applyOptimisticUpdate(\n wrappedUpdate,\n requestId,\n );\n\n const changedQueries = changedQueryTokens.map((token) => {\n const localResult = this.localQueryResultByToken(token);\n return {\n token,\n modification: {\n kind: \"Updated\" as const,\n result:\n localResult === undefined\n ? undefined\n : {\n success: true as const,\n value: localResult,\n logLines: [],\n },\n },\n };\n });\n this.handleTransition({\n queries: changedQueries,\n reflectedMutations: [],\n timestamp: this.remoteQuerySet.timestamp(),\n });\n }\n }\n\n const message: MutationRequest = {\n type: \"Mutation\",\n requestId,\n udfPath,\n componentPath,\n args: [convexToJson(mutationArgs)],\n };\n const mightBeSent = this.webSocketManager.sendMessage(message);\n const mutationPromise = this.requestManager.request(message, mightBeSent);\n return {\n requestId,\n mutationPromise,\n };\n }\n\n /**\n * Execute an action function.\n *\n * @param name - The name of the action.\n * @param args - An arguments object for the action. If this is omitted,\n * the arguments will be `{}`.\n * @returns A promise of the action's result.\n */\n async action(name: string, args?: Record<string, Value>): Promise<any> {\n const result = await this.actionInternal(name, args);\n if (!result.success) {\n if (result.errorData !== undefined) {\n throw forwardData(\n result,\n new ConvexError(createHybridErrorStacktrace(\"action\", name, result)),\n );\n }\n throw new Error(createHybridErrorStacktrace(\"action\", name, result));\n }\n return result.value;\n }\n\n /**\n * @internal\n */\n async actionInternal(\n udfPath: string,\n args?: Record<string, Value>,\n componentPath?: string,\n ): Promise<FunctionResult> {\n const actionArgs = parseArgs(args);\n const requestId = this.nextRequestId;\n this._nextRequestId++;\n this.tryReportLongDisconnect();\n\n const message: ActionRequest = {\n type: \"Action\",\n requestId,\n udfPath,\n componentPath,\n args: [convexToJson(actionArgs)],\n };\n\n const mightBeSent = this.webSocketManager.sendMessage(message);\n return this.requestManager.request(message, mightBeSent);\n }\n\n /**\n * Close any network handles associated with this client and stop all subscriptions.\n *\n * Call this method when you're done with an {@link BaseConvexClient} to\n * dispose of its sockets and resources.\n *\n * @returns A `Promise` fulfilled when the connection has been completely closed.\n */\n async close(): Promise<void> {\n this.authenticationManager.stop();\n return this.webSocketManager.terminate();\n }\n\n /**\n * Return the address for this client, useful for creating a new client.\n *\n * Not guaranteed to match the address with which this client was constructed:\n * it may be canonicalized.\n */\n get url() {\n return this.address;\n }\n\n /**\n * @internal\n */\n get nextRequestId() {\n return this._nextRequestId;\n }\n\n /**\n * @internal\n */\n get sessionId() {\n return this._sessionId;\n }\n\n // Instance property so that `mark()` doesn't need to be called as a method.\n private mark = (name: MarkName) => {\n if (this.debug) {\n mark(name, this.sessionId);\n }\n };\n\n /**\n * Reports performance marks to the server. This should only be called when\n * we have a functional websocket.\n */\n private reportMarks() {\n if (this.debug) {\n const report = getMarksReport(this.sessionId);\n this.webSocketManager.sendMessage({\n type: \"Event\",\n eventType: \"ClientConnect\",\n event: report,\n });\n }\n }\n\n private tryReportLongDisconnect() {\n if (!this.debug) {\n return;\n }\n const timeOfOldestRequest =\n this.connectionState().timeOfOldestInflightRequest;\n if (\n timeOfOldestRequest === null ||\n Date.now() - timeOfOldestRequest.getTime() <= 60 * 1000\n ) {\n return;\n }\n const endpoint = `${this.address}/api/debug_event`;\n fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Convex-Client\": `npm-${version}`,\n },\n body: JSON.stringify({ event: \"LongWebsocketDisconnect\" }),\n })\n .then((response) => {\n if (!response.ok) {\n this.logger.warn(\n \"Analytics request failed with response:\",\n response.body,\n );\n }\n })\n .catch((error) => {\n this.logger.warn(\"Analytics response failed with error:\", error);\n });\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { version } from \"../../index.js\";\nimport { convexToJson, Value } from \"../../values/index.js\";\nimport {\n createHybridErrorStacktrace,\n forwardData,\n instantiateDefaultLogger,\n instantiateNoopLogger,\n logFatalError,\n Logger,\n} from \"../logging.js\";\nimport { LocalSyncState } from \"./local_state.js\";\nimport { RequestManager } from \"./request_manager.js\";\nimport {\n OptimisticLocalStore,\n OptimisticUpdate,\n} from \"./optimistic_updates.js\";\nimport {\n OptimisticQueryResults,\n QueryResultsMap,\n} from \"./optimistic_updates_impl.js\";\nimport {\n ActionRequest,\n MutationRequest,\n QueryId,\n QueryJournal,\n RequestId,\n ServerMessage,\n TS,\n UserIdentityAttributes,\n} from \"./protocol.js\";\nimport { RemoteQuerySet } from \"./remote_query_set.js\";\nimport { QueryToken, serializePathAndArgs } from \"./udf_path_utils.js\";\nimport { ReconnectMetadata, WebSocketManager } from \"./web_socket_manager.js\";\nimport { newSessionId } from \"./session.js\";\nimport { FunctionResult } from \"./function_result.js\";\nimport {\n AuthenticationManager,\n AuthTokenFetcher,\n} from \"./authentication_manager.js\";\nexport { type AuthTokenFetcher } from \"./authentication_manager.js\";\nimport { getMarksReport, mark, MarkName } from \"./metrics.js\";\nimport { parseArgs, validateDeploymentUrl } from \"../../common/index.js\";\nimport { ConvexError } from \"../../values/errors.js\";\n\n/**\n * Options for {@link BaseConvexClient}.\n *\n * @public\n */\nexport interface BaseConvexClientOptions {\n /**\n * Whether to prompt the user if they have unsaved changes pending\n * when navigating away or closing a web page.\n *\n * This is only possible when the `window` object exists, i.e. in a browser.\n *\n * The default value is `true` in browsers.\n */\n unsavedChangesWarning?: boolean;\n /**\n * Specifies an alternate\n * [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)\n * constructor to use for client communication with the Convex cloud.\n * The default behavior is to use `WebSocket` from the global environment.\n */\n webSocketConstructor?: typeof WebSocket;\n /**\n * Adds additional logging for debugging purposes.\n *\n * The default value is `false`.\n */\n verbose?: boolean;\n /**\n * A logger, `true`, or `false`. If not provided or `true`, logs to the console.\n * If `false`, logs are not printed anywhere.\n *\n * You can construct your own logger to customize logging to log elsewhere.\n * A logger is an object with 4 methods: log(), warn(), error(), and logVerbose().\n * These methods can receive multiple arguments of any types, like console.log().\n */\n logger?: Logger | boolean;\n /**\n * Sends additional metrics to Convex for debugging purposes.\n *\n * The default value is `false`.\n */\n reportDebugInfoToConvex?: boolean;\n /**\n * This API is experimental: it may change or disappear.\n *\n * A function to call on receiving abnormal WebSocket close messages from the\n * connected Convex deployment. The content of these messages is not stable,\n * it is an implementation detail that may change.\n *\n * Consider this API an observability stopgap until higher level codes with\n * recommendations on what to do are available, which could be a more stable\n * interface instead of `string`.\n *\n * Check `connectionState` for more quantitative metrics about connection status.\n */\n onServerDisconnectError?: (message: string) => void;\n /**\n * Skip validating that the Convex deployment URL looks like\n * `https://happy-animal-123.convex.cloud` or localhost.\n *\n * This can be useful if running a self-hosted Convex backend that uses a different\n * URL.\n *\n * The default value is `false`\n */\n skipConvexDeploymentUrlCheck?: boolean;\n /**\n * If using auth, the number of seconds before a token expires that we should refresh it.\n *\n * The default value is `2`.\n */\n authRefreshTokenLeewaySeconds?: number;\n}\n\n/**\n * State describing the client's connection with the Convex backend.\n *\n * @public\n */\nexport type ConnectionState = {\n hasInflightRequests: boolean;\n isWebSocketConnected: boolean;\n timeOfOldestInflightRequest: Date | null;\n /**\n * True if the client has ever opened a WebSocket to the \"ready\" state.\n */\n hasEverConnected: boolean;\n /**\n * The number of times this client has connected to the Convex backend.\n *\n * A number of things can cause the client to reconnect -- server errors,\n * bad internet, auth expiring. But this number being high is an indication\n * that the client is having trouble keeping a stable connection.\n */\n connectionCount: number;\n /**\n * The number of times this client has tried (and failed) to connect to the Convex backend.\n */\n connectionRetries: number;\n /**\n * The number of mutations currently in flight.\n */\n inflightMutations: number;\n /**\n * The number of actions currently in flight.\n */\n inflightActions: number;\n};\n\n/**\n * Options for {@link BaseConvexClient.subscribe}.\n *\n * @public\n */\nexport interface SubscribeOptions {\n /**\n * An (optional) journal produced from a previous execution of this query\n * function.\n *\n * If there is an existing subscription to a query function with the same\n * name and arguments, this journal will have no effect.\n */\n journal?: QueryJournal;\n\n /**\n * @internal\n */\n componentPath?: string;\n}\n\n/**\n * Options for {@link BaseConvexClient.mutation}.\n *\n * @public\n */\nexport interface MutationOptions {\n /**\n * An optimistic update to apply along with this mutation.\n *\n * An optimistic update locally updates queries while a mutation is pending.\n * Once the mutation completes, the update will be rolled back.\n */\n optimisticUpdate?: OptimisticUpdate<any>;\n}\n\n/**\n * Type describing updates to a query within a `Transition`.\n *\n * @public\n */\nexport type QueryModification =\n // `undefined` generally comes from an optimistic update setting the query to be loading\n { kind: \"Updated\"; result: FunctionResult | undefined } | { kind: \"Removed\" };\n\n/**\n * Object describing a transition passed into the `onTransition` handler.\n *\n * These can be from receiving a transition from the server, or from applying an\n * optimistic update locally.\n *\n * @public\n */\nexport type Transition = {\n queries: Array<{ token: QueryToken; modification: QueryModification }>;\n reflectedMutations: Array<{ requestId: RequestId; result: FunctionResult }>;\n timestamp: TS;\n};\n\n/**\n * Low-level client for directly integrating state management libraries\n * with Convex.\n *\n * Most developers should use higher level clients, like\n * the {@link ConvexHttpClient} or the React hook based {@link react.ConvexReactClient}.\n *\n * @public\n */\nexport class BaseConvexClient {\n private readonly address: string;\n private readonly state: LocalSyncState;\n private readonly requestManager: RequestManager;\n private readonly webSocketManager: WebSocketManager;\n private readonly authenticationManager: AuthenticationManager;\n private remoteQuerySet: RemoteQuerySet;\n private readonly optimisticQueryResults: OptimisticQueryResults;\n private _transitionHandlerCounter = 0;\n private _nextRequestId: RequestId;\n private _onTransitionFns: Map<number, (transition: Transition) => void> =\n new Map();\n private readonly _sessionId: string;\n private firstMessageReceived = false;\n private readonly debug: boolean;\n private readonly logger: Logger;\n private maxObservedTimestamp: TS | undefined;\n private connectionStateSubscribers = new Map<\n number,\n (connectionState: ConnectionState) => void\n >();\n private nextConnectionStateSubscriberId: number = 0;\n private _lastPublishedConnectionState: ConnectionState | undefined;\n\n /**\n * @param address - The url of your Convex deployment, often provided\n * by an environment variable. E.g. `https://small-mouse-123.convex.cloud`.\n * @param onTransition - A callback receiving an array of query tokens\n * corresponding to query results that have changed -- additional handlers\n * can be added via `addOnTransitionHandler`.\n * @param options - See {@link BaseConvexClientOptions} for a full description.\n */\n constructor(\n address: string,\n onTransition: (updatedQueries: QueryToken[]) => void,\n options?: BaseConvexClientOptions,\n ) {\n if (typeof address === \"object\") {\n throw new Error(\n \"Passing a ClientConfig object is no longer supported. Pass the URL of the Convex deployment as a string directly.\",\n );\n }\n if (options?.skipConvexDeploymentUrlCheck !== true) {\n validateDeploymentUrl(address);\n }\n options = { ...options };\n const authRefreshTokenLeewaySeconds =\n options.authRefreshTokenLeewaySeconds ?? 2;\n let webSocketConstructor = options.webSocketConstructor;\n if (!webSocketConstructor && typeof WebSocket === \"undefined\") {\n throw new Error(\n \"No WebSocket global variable defined! To use Convex in an environment without WebSocket try the HTTP client: https://docs.convex.dev/api/classes/browser.ConvexHttpClient\",\n );\n }\n webSocketConstructor = webSocketConstructor || WebSocket;\n this.debug = options.reportDebugInfoToConvex ?? false;\n this.address = address;\n this.logger =\n options.logger === false\n ? instantiateNoopLogger({ verbose: options.verbose ?? false })\n : options.logger !== true && options.logger\n ? options.logger\n : instantiateDefaultLogger({ verbose: options.verbose ?? false });\n // Substitute http(s) with ws(s)\n const i = address.search(\"://\");\n if (i === -1) {\n throw new Error(\"Provided address was not an absolute URL.\");\n }\n const origin = address.substring(i + 3); // move past the double slash\n const protocol = address.substring(0, i);\n let wsProtocol;\n if (protocol === \"http\") {\n wsProtocol = \"ws\";\n } else if (protocol === \"https\") {\n wsProtocol = \"wss\";\n } else {\n throw new Error(`Unknown parent protocol ${protocol}`);\n }\n const wsUri = `${wsProtocol}://${origin}/api/${version}/sync`;\n\n this.state = new LocalSyncState();\n this.remoteQuerySet = new RemoteQuerySet(\n (queryId) => this.state.queryPath(queryId),\n this.logger,\n );\n this.requestManager = new RequestManager(\n this.logger,\n this.markConnectionStateDirty,\n );\n this.authenticationManager = new AuthenticationManager(\n this.state,\n {\n authenticate: (token) => {\n const message = this.state.setAuth(token);\n this.webSocketManager.sendMessage(message);\n return message.baseVersion;\n },\n stopSocket: () => this.webSocketManager.stop(),\n tryRestartSocket: () => this.webSocketManager.tryRestart(),\n pauseSocket: () => {\n this.webSocketManager.pause();\n this.state.pause();\n },\n resumeSocket: () => this.webSocketManager.resume(),\n clearAuth: () => {\n this.clearAuth();\n },\n },\n {\n logger: this.logger,\n refreshTokenLeewaySeconds: authRefreshTokenLeewaySeconds,\n },\n );\n this.optimisticQueryResults = new OptimisticQueryResults();\n this.addOnTransitionHandler((transition) => {\n onTransition(transition.queries.map((q) => q.token));\n });\n this._nextRequestId = 0;\n this._sessionId = newSessionId();\n\n const { unsavedChangesWarning } = options;\n if (\n typeof window === \"undefined\" ||\n typeof window.addEventListener === \"undefined\"\n ) {\n if (unsavedChangesWarning === true) {\n throw new Error(\n \"unsavedChangesWarning requested, but window.addEventListener not found! Remove {unsavedChangesWarning: true} from Convex client options.\",\n );\n }\n } else if (unsavedChangesWarning !== false) {\n // Listen for tab close events and notify the user on unsaved changes.\n window.addEventListener(\"beforeunload\", (e) => {\n if (this.requestManager.hasIncompleteRequests()) {\n // There are 3 different ways to trigger this pop up so just try all of\n // them.\n\n e.preventDefault();\n // This confirmation message doesn't actually appear in most modern\n // browsers but we tried.\n const confirmationMessage =\n \"Are you sure you want to leave? Your changes may not be saved.\";\n (e || window.event).returnValue = confirmationMessage;\n return confirmationMessage;\n }\n });\n }\n\n this.webSocketManager = new WebSocketManager(\n wsUri,\n {\n onOpen: (reconnectMetadata: ReconnectMetadata) => {\n // We have a new WebSocket!\n this.mark(\"convexWebSocketOpen\");\n this.webSocketManager.sendMessage({\n ...reconnectMetadata,\n type: \"Connect\",\n sessionId: this._sessionId,\n maxObservedTimestamp: this.maxObservedTimestamp,\n });\n\n // Throw out our remote query, reissue queries\n // and outstanding mutations, and reauthenticate.\n const oldRemoteQueryResults = new Set(\n this.remoteQuerySet.remoteQueryResults().keys(),\n );\n this.remoteQuerySet = new RemoteQuerySet(\n (queryId) => this.state.queryPath(queryId),\n this.logger,\n );\n const [querySetModification, authModification] = this.state.restart(\n oldRemoteQueryResults,\n );\n if (authModification) {\n this.webSocketManager.sendMessage(authModification);\n }\n this.webSocketManager.sendMessage(querySetModification);\n for (const message of this.requestManager.restart()) {\n this.webSocketManager.sendMessage(message);\n }\n },\n onResume: () => {\n const [querySetModification, authModification] = this.state.resume();\n if (authModification) {\n this.webSocketManager.sendMessage(authModification);\n }\n if (querySetModification) {\n this.webSocketManager.sendMessage(querySetModification);\n }\n for (const message of this.requestManager.resume()) {\n this.webSocketManager.sendMessage(message);\n }\n },\n onMessage: (serverMessage: ServerMessage) => {\n // Metrics events grow linearly with reconnection attempts so this\n // conditional prevents n^2 metrics reporting.\n if (!this.firstMessageReceived) {\n this.firstMessageReceived = true;\n this.mark(\"convexFirstMessageReceived\");\n this.reportMarks();\n }\n switch (serverMessage.type) {\n case \"Transition\": {\n this.observedTimestamp(serverMessage.endVersion.ts);\n this.authenticationManager.onTransition(serverMessage);\n this.remoteQuerySet.transition(serverMessage);\n this.state.transition(serverMessage);\n const completedRequests = this.requestManager.removeCompleted(\n this.remoteQuerySet.timestamp(),\n );\n this.notifyOnQueryResultChanges(completedRequests);\n break;\n }\n case \"MutationResponse\": {\n if (serverMessage.success) {\n this.observedTimestamp(serverMessage.ts);\n }\n const completedMutationInfo =\n this.requestManager.onResponse(serverMessage);\n if (completedMutationInfo !== null) {\n this.notifyOnQueryResultChanges(\n new Map([\n [\n completedMutationInfo.requestId,\n completedMutationInfo.result,\n ],\n ]),\n );\n }\n break;\n }\n case \"ActionResponse\": {\n this.requestManager.onResponse(serverMessage);\n break;\n }\n case \"AuthError\": {\n this.authenticationManager.onAuthError(serverMessage);\n break;\n }\n case \"FatalError\": {\n const error = logFatalError(this.logger, serverMessage.error);\n void this.webSocketManager.terminate();\n throw error;\n }\n case \"Ping\":\n break; // do nothing\n default: {\n const _typeCheck: never = serverMessage;\n }\n }\n\n return {\n hasSyncedPastLastReconnect: this.hasSyncedPastLastReconnect(),\n };\n },\n onServerDisconnectError: options.onServerDisconnectError,\n },\n webSocketConstructor,\n this.logger,\n this.markConnectionStateDirty,\n );\n this.mark(\"convexClientConstructed\");\n }\n\n /**\n * Return true if there is outstanding work from prior to the time of the most recent restart.\n * This indicates that the client has not proven itself to have gotten past the issue that\n * potentially led to the restart. Use this to influence when to reset backoff after a failure.\n */\n private hasSyncedPastLastReconnect() {\n const hasSyncedPastLastReconnect =\n this.requestManager.hasSyncedPastLastReconnect() ||\n this.state.hasSyncedPastLastReconnect();\n return hasSyncedPastLastReconnect;\n }\n\n private observedTimestamp(observedTs: TS) {\n if (\n this.maxObservedTimestamp === undefined ||\n this.maxObservedTimestamp.lessThanOrEqual(observedTs)\n ) {\n this.maxObservedTimestamp = observedTs;\n }\n }\n\n getMaxObservedTimestamp() {\n return this.maxObservedTimestamp;\n }\n\n /**\n * Compute the current query results based on the remoteQuerySet and the\n * current optimistic updates and call `onTransition` for all the changed\n * queries.\n *\n * @param completedMutations - A set of mutation IDs whose optimistic updates\n * are no longer needed.\n */\n private notifyOnQueryResultChanges(\n completedRequests: Map<RequestId, FunctionResult>,\n ) {\n const remoteQueryResults: Map<QueryId, FunctionResult> =\n this.remoteQuerySet.remoteQueryResults();\n const queryTokenToValue: QueryResultsMap = new Map();\n for (const [queryId, result] of remoteQueryResults) {\n const queryToken = this.state.queryToken(queryId);\n // It's possible that we've already unsubscribed to this query but\n // the server hasn't learned about that yet. If so, ignore this one.\n\n if (queryToken !== null) {\n const query = {\n result,\n udfPath: this.state.queryPath(queryId)!,\n args: this.state.queryArgs(queryId)!,\n };\n queryTokenToValue.set(queryToken, query);\n }\n }\n\n // Query tokens that are new (because of new server results or new local optimistic updates)\n // or differ from old values (because of changes from local optimistic updates or new results\n // from the server).\n const changedQueryTokens =\n this.optimisticQueryResults.ingestQueryResultsFromServer(\n queryTokenToValue,\n new Set(completedRequests.keys()),\n );\n\n this.handleTransition({\n queries: changedQueryTokens.map((token) => {\n const optimisticResult =\n this.optimisticQueryResults.rawQueryResult(token);\n return {\n token,\n modification: {\n kind: \"Updated\",\n result: optimisticResult!.result,\n },\n };\n }),\n reflectedMutations: Array.from(completedRequests).map(\n ([requestId, result]) => ({\n requestId,\n result,\n }),\n ),\n timestamp: this.remoteQuerySet.timestamp(),\n });\n }\n\n private handleTransition(transition: Transition) {\n for (const fn of this._onTransitionFns.values()) {\n fn(transition);\n }\n }\n\n /**\n * Add a handler that will be called on a transition.\n *\n * Any external side effects (e.g. setting React state) should be handled here.\n *\n * @param fn\n *\n * @returns\n */\n addOnTransitionHandler(fn: (transition: Transition) => void) {\n const id = this._transitionHandlerCounter++;\n this._onTransitionFns.set(id, fn);\n return () => this._onTransitionFns.delete(id);\n }\n\n /**\n * Set the authentication token to be used for subsequent queries and mutations.\n * `fetchToken` will be called automatically again if a token expires.\n * `fetchToken` should return `null` if the token cannot be retrieved, for example\n * when the user's rights were permanently revoked.\n * @param fetchToken - an async function returning the JWT-encoded OpenID Connect Identity Token\n * @param onChange - a callback that will be called when the authentication status changes\n */\n setAuth(\n fetchToken: AuthTokenFetcher,\n onChange: (isAuthenticated: boolean) => void,\n ) {\n void this.authenticationManager.setConfig(fetchToken, onChange);\n }\n\n hasAuth() {\n return this.state.hasAuth();\n }\n\n /** @internal */\n setAdminAuth(value: string, fakeUserIdentity?: UserIdentityAttributes) {\n const message = this.state.setAdminAuth(value, fakeUserIdentity);\n this.webSocketManager.sendMessage(message);\n }\n\n clearAuth() {\n const message = this.state.clearAuth();\n this.webSocketManager.sendMessage(message);\n }\n\n /**\n * Subscribe to a query function.\n *\n * Whenever this query's result changes, the `onTransition` callback\n * passed into the constructor will be called.\n *\n * @param name - The name of the query.\n * @param args - An arguments object for the query. If this is omitted, the\n * arguments will be `{}`.\n * @param options - A {@link SubscribeOptions} options object for this query.\n\n * @returns An object containing a {@link QueryToken} corresponding to this\n * query and an `unsubscribe` callback.\n */\n subscribe(\n name: string,\n args?: Record<string, Value>,\n options?: SubscribeOptions,\n ): { queryToken: QueryToken; unsubscribe: () => void } {\n const argsObject = parseArgs(args);\n\n const { modification, queryToken, unsubscribe } = this.state.subscribe(\n name,\n argsObject,\n options?.journal,\n options?.componentPath,\n );\n if (modification !== null) {\n this.webSocketManager.sendMessage(modification);\n }\n return {\n queryToken,\n unsubscribe: () => {\n const modification = unsubscribe();\n if (modification) {\n this.webSocketManager.sendMessage(modification);\n }\n },\n };\n }\n\n /**\n * A query result based only on the current, local state.\n *\n * The only way this will return a value is if we're already subscribed to the\n * query or its value has been set optimistically.\n */\n localQueryResult(\n udfPath: string,\n args?: Record<string, Value>,\n ): Value | undefined {\n const argsObject = parseArgs(args);\n const queryToken = serializePathAndArgs(udfPath, argsObject);\n return this.optimisticQueryResults.queryResult(queryToken);\n }\n\n /**\n * Get query result by query token based on current, local state\n *\n * The only way this will return a value is if we're already subscribed to the\n * query or its value has been set optimistically.\n *\n * @internal\n */\n localQueryResultByToken(queryToken: QueryToken): Value | undefined {\n return this.optimisticQueryResults.queryResult(queryToken);\n }\n\n /**\n * Whether local query result is available for a toke.\n *\n * This method does not throw if the result is an error.\n *\n * @internal\n */\n hasLocalQueryResultByToken(queryToken: QueryToken): boolean {\n return this.optimisticQueryResults.hasQueryResult(queryToken);\n }\n\n /**\n * @internal\n */\n localQueryLogs(\n udfPath: string,\n args?: Record<string, Value>,\n ): string[] | undefined {\n const argsObject = parseArgs(args);\n const queryToken = serializePathAndArgs(udfPath, argsObject);\n return this.optimisticQueryResults.queryLogs(queryToken);\n }\n\n /**\n * Retrieve the current {@link QueryJournal} for this query function.\n *\n * If we have not yet received a result for this query, this will be `undefined`.\n *\n * @param name - The name of the query.\n * @param args - The arguments object for this query.\n * @returns The query's {@link QueryJournal} or `undefined`.\n */\n queryJournal(\n name: string,\n args?: Record<string, Value>,\n ): QueryJournal | undefined {\n const argsObject = parseArgs(args);\n const queryToken = serializePathAndArgs(name, argsObject);\n return this.state.queryJournal(queryToken);\n }\n\n /**\n * Get the current {@link ConnectionState} between the client and the Convex\n * backend.\n *\n * @returns The {@link ConnectionState} with the Convex backend.\n */\n connectionState(): ConnectionState {\n const wsConnectionState = this.webSocketManager.connectionState();\n return {\n hasInflightRequests: this.requestManager.hasInflightRequests(),\n isWebSocketConnected: wsConnectionState.isConnected,\n hasEverConnected: wsConnectionState.hasEverConnected,\n connectionCount: wsConnectionState.connectionCount,\n connectionRetries: wsConnectionState.connectionRetries,\n timeOfOldestInflightRequest:\n this.requestManager.timeOfOldestInflightRequest(),\n inflightMutations: this.requestManager.inflightMutations(),\n inflightActions: this.requestManager.inflightActions(),\n };\n }\n\n /**\n * Call this whenever the connection state may have changed in a way that could\n * require publishing it. Schedules a possibly update.\n */\n private markConnectionStateDirty = () => {\n void Promise.resolve().then(() => {\n const curConnectionState = this.connectionState();\n if (\n JSON.stringify(curConnectionState) !==\n JSON.stringify(this._lastPublishedConnectionState)\n ) {\n this._lastPublishedConnectionState = curConnectionState;\n for (const cb of this.connectionStateSubscribers.values()) {\n // One of these callback throwing will prevent other callbacks\n // from running but will not leave the client in a undefined state.\n cb(curConnectionState);\n }\n }\n });\n };\n\n /**\n * Subscribe to the {@link ConnectionState} between the client and the Convex\n * backend, calling a callback each time it changes.\n *\n * Subscribed callbacks will be called when any part of ConnectionState changes.\n * ConnectionState may grow in future versions (e.g. to provide a array of\n * inflight requests) in which case callbacks would be called more frequently.\n *\n * @returns An unsubscribe function to stop listening.\n */\n subscribeToConnectionState(\n cb: (connectionState: ConnectionState) => void,\n ): () => void {\n const id = this.nextConnectionStateSubscriberId++;\n this.connectionStateSubscribers.set(id, cb);\n return () => {\n this.connectionStateSubscribers.delete(id);\n };\n }\n\n /**\n * Execute a mutation function.\n *\n * @param name - The name of the mutation.\n * @param args - An arguments object for the mutation. If this is omitted,\n * the arguments will be `{}`.\n * @param options - A {@link MutationOptions} options object for this mutation.\n\n * @returns - A promise of the mutation's result.\n */\n async mutation(\n name: string,\n args?: Record<string, Value>,\n options?: MutationOptions,\n ): Promise<any> {\n const result = await this.mutationInternal(name, args, options);\n if (!result.success) {\n if (result.errorData !== undefined) {\n throw forwardData(\n result,\n new ConvexError(\n createHybridErrorStacktrace(\"mutation\", name, result),\n ),\n );\n }\n throw new Error(createHybridErrorStacktrace(\"mutation\", name, result));\n }\n return result.value;\n }\n\n /**\n * @internal\n */\n async mutationInternal(\n udfPath: string,\n args?: Record<string, Value>,\n options?: MutationOptions,\n componentPath?: string,\n ): Promise<FunctionResult> {\n const { mutationPromise } = this.enqueueMutation(\n udfPath,\n args,\n options,\n componentPath,\n );\n return mutationPromise;\n }\n\n /**\n * @internal\n */\n enqueueMutation(\n udfPath: string,\n args?: Record<string, Value>,\n options?: MutationOptions,\n componentPath?: string,\n ): { requestId: RequestId; mutationPromise: Promise<FunctionResult> } {\n const mutationArgs = parseArgs(args);\n this.tryReportLongDisconnect();\n const requestId = this.nextRequestId;\n this._nextRequestId++;\n\n if (options !== undefined) {\n const optimisticUpdate = options.optimisticUpdate;\n if (optimisticUpdate !== undefined) {\n const wrappedUpdate = (localQueryStore: OptimisticLocalStore) => {\n const result: unknown = optimisticUpdate(\n localQueryStore,\n mutationArgs,\n );\n if (result instanceof Promise) {\n this.logger.warn(\n \"Optimistic update handler returned a Promise. Optimistic updates should be synchronous.\",\n );\n }\n };\n\n const changedQueryTokens =\n this.optimisticQueryResults.applyOptimisticUpdate(\n wrappedUpdate,\n requestId,\n );\n\n const changedQueries = changedQueryTokens.map((token) => {\n const localResult = this.localQueryResultByToken(token);\n return {\n token,\n modification: {\n kind: \"Updated\" as const,\n result:\n localResult === undefined\n ? undefined\n : {\n success: true as const,\n value: localResult,\n logLines: [],\n },\n },\n };\n });\n this.handleTransition({\n queries: changedQueries,\n reflectedMutations: [],\n timestamp: this.remoteQuerySet.timestamp(),\n });\n }\n }\n\n const message: MutationRequest = {\n type: \"Mutation\",\n requestId,\n udfPath,\n componentPath,\n args: [convexToJson(mutationArgs)],\n };\n const mightBeSent = this.webSocketManager.sendMessage(message);\n const mutationPromise = this.requestManager.request(message, mightBeSent);\n return {\n requestId,\n mutationPromise,\n };\n }\n\n /**\n * Execute an action function.\n *\n * @param name - The name of the action.\n * @param args - An arguments object for the action. If this is omitted,\n * the arguments will be `{}`.\n * @returns A promise of the action's result.\n */\n async action(name: string, args?: Record<string, Value>): Promise<any> {\n const result = await this.actionInternal(name, args);\n if (!result.success) {\n if (result.errorData !== undefined) {\n throw forwardData(\n result,\n new ConvexError(createHybridErrorStacktrace(\"action\", name, result)),\n );\n }\n throw new Error(createHybridErrorStacktrace(\"action\", name, result));\n }\n return result.value;\n }\n\n /**\n * @internal\n */\n async actionInternal(\n udfPath: string,\n args?: Record<string, Value>,\n componentPath?: string,\n ): Promise<FunctionResult> {\n const actionArgs = parseArgs(args);\n const requestId = this.nextRequestId;\n this._nextRequestId++;\n this.tryReportLongDisconnect();\n\n const message: ActionRequest = {\n type: \"Action\",\n requestId,\n udfPath,\n componentPath,\n args: [convexToJson(actionArgs)],\n };\n\n const mightBeSent = this.webSocketManager.sendMessage(message);\n return this.requestManager.request(message, mightBeSent);\n }\n\n /**\n * Close any network handles associated with this client and stop all subscriptions.\n *\n * Call this method when you're done with an {@link BaseConvexClient} to\n * dispose of its sockets and resources.\n *\n * @returns A `Promise` fulfilled when the connection has been completely closed.\n */\n async close(): Promise<void> {\n this.authenticationManager.stop();\n return this.webSocketManager.terminate();\n }\n\n /**\n * Return the address for this client, useful for creating a new client.\n *\n * Not guaranteed to match the address with which this client was constructed:\n * it may be canonicalized.\n */\n get url() {\n return this.address;\n }\n\n /**\n * @internal\n */\n get nextRequestId() {\n return this._nextRequestId;\n }\n\n /**\n * @internal\n */\n get sessionId() {\n return this._sessionId;\n }\n\n // Instance property so that `mark()` doesn't need to be called as a method.\n private mark = (name: MarkName) => {\n if (this.debug) {\n mark(name, this.sessionId);\n }\n };\n\n /**\n * Reports performance marks to the server. This should only be called when\n * we have a functional websocket.\n */\n private reportMarks() {\n if (this.debug) {\n const report = getMarksReport(this.sessionId);\n this.webSocketManager.sendMessage({\n type: \"Event\",\n eventType: \"ClientConnect\",\n event: report,\n });\n }\n }\n\n private tryReportLongDisconnect() {\n if (!this.debug) {\n return;\n }\n const timeOfOldestRequest =\n this.connectionState().timeOfOldestInflightRequest;\n if (\n timeOfOldestRequest === null ||\n Date.now() - timeOfOldestRequest.getTime() <= 60 * 1000\n ) {\n return;\n }\n const endpoint = `${this.address}/api/debug_event`;\n fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Convex-Client\": `npm-${version}`,\n },\n body: JSON.stringify({ event: \"LongWebsocketDisconnect\" }),\n })\n .then((response) => {\n if (!response.ok) {\n this.logger.warn(\n \"Analytics request failed with response:\",\n response.body,\n );\n }\n })\n .catch((error) => {\n this.logger.warn(\"Analytics response failed with error:\", error);\n });\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["modification"]
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/browser/sync/remote_query_set.ts"],
|
|
4
|
-
"sourcesContent": ["import { jsonToConvex } from \"../../values/index.js\";\nimport { Long } from \"../long.js\";\nimport { logForFunction, Logger } from \"../logging.js\";\nimport { QueryId, StateVersion, Transition } from \"./protocol.js\";\nimport { FunctionResult } from \"./function_result.js\";\n\n/**\n * A represention of the query results we've received on the current WebSocket\n * connection.\n *\n * Queries you won't find here include:\n * - queries which have been requested, but no query transition has been received yet for\n * - queries which are populated only though active optimistic updates, but are not subscribed to\n */\nexport class RemoteQuerySet {\n private version: StateVersion;\n private readonly remoteQuerySet: Map<QueryId, FunctionResult>;\n private readonly queryPath: (queryId: QueryId) => string | null;\n private readonly logger: Logger;\n\n constructor(queryPath: (queryId: QueryId) => string | null, logger: Logger) {\n this.version = { querySet: 0, ts: Long.fromNumber(0), identity: 0 };\n this.remoteQuerySet = new Map();\n this.queryPath = queryPath;\n this.logger = logger;\n }\n\n transition(transition: Transition): void {\n const start = transition.startVersion;\n if (\n this.version.querySet !== start.querySet ||\n this.version.ts.notEquals(start.ts) ||\n this.version.identity !== start.identity\n ) {\n throw new Error(\n `Invalid start version: ${start.ts.toString()}:${start.querySet}`,\n );\n }\n for (const modification of transition.modifications) {\n switch (modification.type) {\n case \"QueryUpdated\": {\n const queryPath = this.queryPath(modification.queryId);\n if (queryPath) {\n for (const line of modification.logLines) {\n logForFunction(this.logger, \"info\", \"query\", queryPath, line);\n }\n }\n const value = jsonToConvex(modification.value ?? null);\n this.remoteQuerySet.set(modification.queryId, {\n success: true,\n value,\n logLines: modification.logLines,\n });\n break;\n }\n case \"QueryFailed\": {\n const queryPath = this.queryPath(modification.queryId);\n if (queryPath) {\n for (const line of modification.logLines) {\n logForFunction(this.logger, \"info\", \"query\", queryPath, line);\n }\n }\n const { errorData } = modification;\n this.remoteQuerySet.set(modification.queryId, {\n success: false,\n errorMessage: modification.errorMessage,\n errorData:\n errorData !== undefined ? jsonToConvex(errorData) : undefined,\n logLines: modification.logLines,\n });\n break;\n }\n case \"QueryRemoved\": {\n this.remoteQuerySet.delete(modification.queryId);\n break;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n const _: never = modification;\n throw new Error(`Invalid modification ${(modification as any).type}`);\n }\n }\n }\n this.version = transition.endVersion;\n }\n\n remoteQueryResults(): Map<QueryId, FunctionResult> {\n return this.remoteQuerySet;\n }\n\n timestamp(): Long {\n return this.version.ts;\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA6B;AAC7B,kBAAqB;AACrB,qBAAuC;
|
|
4
|
+
"sourcesContent": ["import { jsonToConvex } from \"../../values/index.js\";\nimport { Long } from \"../long.js\";\nimport { logForFunction, Logger } from \"../logging.js\";\nimport { QueryId, StateVersion, Transition } from \"./protocol.js\";\nimport { FunctionResult } from \"./function_result.js\";\n\n/**\n * A represention of the query results we've received on the current WebSocket\n * connection.\n *\n * Queries you won't find here include:\n * - queries which have been requested, but no query transition has been received yet for\n * - queries which are populated only though active optimistic updates, but are not subscribed to\n * - queries which have already been removed by the server (which it shouldn't do unless that's\n * been requested by the client)\n */\nexport class RemoteQuerySet {\n private version: StateVersion;\n private readonly remoteQuerySet: Map<QueryId, FunctionResult>;\n private readonly queryPath: (queryId: QueryId) => string | null;\n private readonly logger: Logger;\n\n constructor(queryPath: (queryId: QueryId) => string | null, logger: Logger) {\n this.version = { querySet: 0, ts: Long.fromNumber(0), identity: 0 };\n this.remoteQuerySet = new Map();\n this.queryPath = queryPath;\n this.logger = logger;\n }\n\n transition(transition: Transition): void {\n const start = transition.startVersion;\n if (\n this.version.querySet !== start.querySet ||\n this.version.ts.notEquals(start.ts) ||\n this.version.identity !== start.identity\n ) {\n throw new Error(\n `Invalid start version: ${start.ts.toString()}:${start.querySet}`,\n );\n }\n for (const modification of transition.modifications) {\n switch (modification.type) {\n case \"QueryUpdated\": {\n const queryPath = this.queryPath(modification.queryId);\n if (queryPath) {\n for (const line of modification.logLines) {\n logForFunction(this.logger, \"info\", \"query\", queryPath, line);\n }\n }\n const value = jsonToConvex(modification.value ?? null);\n this.remoteQuerySet.set(modification.queryId, {\n success: true,\n value,\n logLines: modification.logLines,\n });\n break;\n }\n case \"QueryFailed\": {\n const queryPath = this.queryPath(modification.queryId);\n if (queryPath) {\n for (const line of modification.logLines) {\n logForFunction(this.logger, \"info\", \"query\", queryPath, line);\n }\n }\n const { errorData } = modification;\n this.remoteQuerySet.set(modification.queryId, {\n success: false,\n errorMessage: modification.errorMessage,\n errorData:\n errorData !== undefined ? jsonToConvex(errorData) : undefined,\n logLines: modification.logLines,\n });\n break;\n }\n case \"QueryRemoved\": {\n this.remoteQuerySet.delete(modification.queryId);\n break;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n const _: never = modification;\n throw new Error(`Invalid modification ${(modification as any).type}`);\n }\n }\n }\n this.version = transition.endVersion;\n }\n\n remoteQueryResults(): Map<QueryId, FunctionResult> {\n return this.remoteQuerySet;\n }\n\n timestamp(): Long {\n return this.version.ts;\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA6B;AAC7B,kBAAqB;AACrB,qBAAuC;AAchC,MAAM,eAAe;AAAA,EAM1B,YAAY,WAAgD,QAAgB;AAL5E,wBAAQ;AACR,wBAAiB;AACjB,wBAAiB;AACjB,wBAAiB;AAGf,SAAK,UAAU,EAAE,UAAU,GAAG,IAAI,iBAAK,WAAW,CAAC,GAAG,UAAU,EAAE;AAClE,SAAK,iBAAiB,oBAAI,IAAI;AAC9B,SAAK,YAAY;AACjB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,WAAW,YAA8B;AACvC,UAAM,QAAQ,WAAW;AACzB,QACE,KAAK,QAAQ,aAAa,MAAM,YAChC,KAAK,QAAQ,GAAG,UAAU,MAAM,EAAE,KAClC,KAAK,QAAQ,aAAa,MAAM,UAChC;AACA,YAAM,IAAI;AAAA,QACR,0BAA0B,MAAM,GAAG,SAAS,CAAC,IAAI,MAAM,QAAQ;AAAA,MACjE;AAAA,IACF;AACA,eAAW,gBAAgB,WAAW,eAAe;AACnD,cAAQ,aAAa,MAAM;AAAA,QACzB,KAAK,gBAAgB;AACnB,gBAAM,YAAY,KAAK,UAAU,aAAa,OAAO;AACrD,cAAI,WAAW;AACb,uBAAW,QAAQ,aAAa,UAAU;AACxC,iDAAe,KAAK,QAAQ,QAAQ,SAAS,WAAW,IAAI;AAAA,YAC9D;AAAA,UACF;AACA,gBAAM,YAAQ,4BAAa,aAAa,SAAS,IAAI;AACrD,eAAK,eAAe,IAAI,aAAa,SAAS;AAAA,YAC5C,SAAS;AAAA,YACT;AAAA,YACA,UAAU,aAAa;AAAA,UACzB,CAAC;AACD;AAAA,QACF;AAAA,QACA,KAAK,eAAe;AAClB,gBAAM,YAAY,KAAK,UAAU,aAAa,OAAO;AACrD,cAAI,WAAW;AACb,uBAAW,QAAQ,aAAa,UAAU;AACxC,iDAAe,KAAK,QAAQ,QAAQ,SAAS,WAAW,IAAI;AAAA,YAC9D;AAAA,UACF;AACA,gBAAM,EAAE,UAAU,IAAI;AACtB,eAAK,eAAe,IAAI,aAAa,SAAS;AAAA,YAC5C,SAAS;AAAA,YACT,cAAc,aAAa;AAAA,YAC3B,WACE,cAAc,aAAY,4BAAa,SAAS,IAAI;AAAA,YACtD,UAAU,aAAa;AAAA,UACzB,CAAC;AACD;AAAA,QACF;AAAA,QACA,KAAK,gBAAgB;AACnB,eAAK,eAAe,OAAO,aAAa,OAAO;AAC/C;AAAA,QACF;AAAA,QACA,SAAS;AAEP,gBAAM,IAAW;AACjB,gBAAM,IAAI,MAAM,wBAAyB,aAAqB,IAAI,EAAE;AAAA,QACtE;AAAA,MACF;AAAA,IACF;AACA,SAAK,UAAU,WAAW;AAAA,EAC5B;AAAA,EAEA,qBAAmD;AACjD,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,YAAkB;AAChB,WAAO,KAAK,QAAQ;AAAA,EACtB;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -26,8 +26,9 @@ module.exports = __toCommonJS(request_manager_exports);
|
|
|
26
26
|
var import_values = require("../../values/index.js");
|
|
27
27
|
var import_logging = require("../logging.js");
|
|
28
28
|
class RequestManager {
|
|
29
|
-
constructor(logger) {
|
|
29
|
+
constructor(logger, markConnectionStateDirty) {
|
|
30
30
|
this.logger = logger;
|
|
31
|
+
this.markConnectionStateDirty = markConnectionStateDirty;
|
|
31
32
|
__publicField(this, "inflightRequests");
|
|
32
33
|
__publicField(this, "requestsOlderThanRestart");
|
|
33
34
|
__publicField(this, "inflightMutationsCount", 0);
|
|
@@ -48,6 +49,7 @@ class RequestManager {
|
|
|
48
49
|
this.inflightActionsCount++;
|
|
49
50
|
}
|
|
50
51
|
});
|
|
52
|
+
this.markConnectionStateDirty();
|
|
51
53
|
return result;
|
|
52
54
|
}
|
|
53
55
|
/**
|
|
@@ -100,6 +102,7 @@ class RequestManager {
|
|
|
100
102
|
} else if (requestInfo.message.type === "Mutation") {
|
|
101
103
|
this.inflightMutationsCount--;
|
|
102
104
|
}
|
|
105
|
+
this.markConnectionStateDirty();
|
|
103
106
|
return { requestId: response.requestId, result };
|
|
104
107
|
}
|
|
105
108
|
requestInfo.status = {
|
|
@@ -127,6 +130,9 @@ class RequestManager {
|
|
|
127
130
|
this.requestsOlderThanRestart.delete(requestId);
|
|
128
131
|
}
|
|
129
132
|
}
|
|
133
|
+
if (completeRequests.size > 0) {
|
|
134
|
+
this.markConnectionStateDirty();
|
|
135
|
+
}
|
|
130
136
|
return completeRequests;
|
|
131
137
|
}
|
|
132
138
|
restart() {
|
|
@@ -154,6 +160,7 @@ class RequestManager {
|
|
|
154
160
|
});
|
|
155
161
|
}
|
|
156
162
|
}
|
|
163
|
+
this.markConnectionStateDirty();
|
|
157
164
|
return allMessages;
|
|
158
165
|
}
|
|
159
166
|
resume() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/browser/sync/request_manager.ts"],
|
|
4
|
-
"sourcesContent": ["import { jsonToConvex } from \"../../values/index.js\";\nimport { logForFunction, Logger } from \"../logging.js\";\nimport { Long } from \"../long.js\";\nimport { FunctionResult } from \"./function_result.js\";\nimport {\n ActionRequest,\n ActionResponse,\n ClientMessage,\n MutationRequest,\n MutationResponse,\n RequestId,\n} from \"./protocol.js\";\n\ntype RequestStatus =\n | {\n status: \"Requested\" | \"NotSent\";\n onResult: (result: FunctionResult) => void;\n requestedAt: Date;\n }\n | {\n status: \"Completed\";\n result: FunctionResult;\n onResolve: () => void;\n ts: Long;\n };\n\nexport class RequestManager {\n private inflightRequests: Map<\n RequestId,\n {\n message: MutationRequest | ActionRequest;\n status: RequestStatus;\n }\n >;\n private requestsOlderThanRestart: Set<RequestId>;\n private inflightMutationsCount: number = 0;\n private inflightActionsCount: number = 0;\n constructor(private readonly logger: Logger) {\n this.inflightRequests = new Map();\n this.requestsOlderThanRestart = new Set();\n }\n\n request(\n message: MutationRequest | ActionRequest,\n sent: boolean,\n ): Promise<FunctionResult> {\n const result = new Promise<FunctionResult>((resolve) => {\n const status = sent ? \"Requested\" : \"NotSent\";\n this.inflightRequests.set(message.requestId, {\n message,\n status: { status, requestedAt: new Date(), onResult: resolve },\n });\n\n if (message.type === \"Mutation\") {\n this.inflightMutationsCount++;\n } else if (message.type === \"Action\") {\n this.inflightActionsCount++;\n }\n });\n\n return result;\n }\n\n /**\n * Update the state after receiving a response.\n *\n * @returns A RequestId if the request is complete and its optimistic update\n * can be dropped, null otherwise.\n */\n onResponse(\n response: MutationResponse | ActionResponse,\n ): { requestId: RequestId; result: FunctionResult } | null {\n const requestInfo = this.inflightRequests.get(response.requestId);\n if (requestInfo === undefined) {\n // Annoyingly we can occasionally get responses to mutations that we're no\n // longer tracking. One flow where this happens is:\n // 1. Client sends mutation 1\n // 2. Client gets response for mutation 1. The sever says that it was committed at ts=10.\n // 3. Client is disconnected\n // 4. Client reconnects and re-issues queries and this mutation.\n // 5. Server sends transition message to ts=20\n // 6. Client drops mutation because it's already been observed.\n // 7. Client receives a second response for mutation 1 but doesn't know about it anymore.\n\n // The right fix for this is probably to add a reconciliation phase on\n // reconnection where we receive responses to all the mutations before\n // the transition message so this flow could never happen (CX-1513).\n\n // For now though, we can just ignore this message.\n return null;\n }\n\n // Because `.restart()` re-requests completed requests, we may get some\n // responses for requests that are already in the \"Completed\" state.\n // We can safely ignore those because we've already notified the UI about\n // their results.\n if (requestInfo.status.status === \"Completed\") {\n return null;\n }\n\n const udfType =\n requestInfo.message.type === \"Mutation\" ? \"mutation\" : \"action\";\n const udfPath = requestInfo.message.udfPath;\n\n for (const line of response.logLines) {\n logForFunction(this.logger, \"info\", udfType, udfPath, line);\n }\n\n const status = requestInfo.status;\n let result: FunctionResult;\n let onResolve;\n if (response.success) {\n result = {\n success: true,\n logLines: response.logLines,\n value: jsonToConvex(response.result),\n };\n onResolve = () => status.onResult(result);\n } else {\n const errorMessage = response.result as string;\n const { errorData } = response;\n logForFunction(this.logger, \"error\", udfType, udfPath, errorMessage);\n result = {\n success: false,\n errorMessage,\n errorData:\n errorData !== undefined ? jsonToConvex(errorData) : undefined,\n logLines: response.logLines,\n };\n onResolve = () => status.onResult(result);\n }\n\n // We can resolve Mutation failures immediately since they don't have any\n // side effects. Actions are intentionally decoupled from\n // queries/mutations here on the sync protocol since they have different\n // guarantees.\n if (response.type === \"ActionResponse\" || !response.success) {\n onResolve();\n this.inflightRequests.delete(response.requestId);\n this.requestsOlderThanRestart.delete(response.requestId);\n\n if (requestInfo.message.type === \"Action\") {\n this.inflightActionsCount--;\n } else if (requestInfo.message.type === \"Mutation\") {\n this.inflightMutationsCount--;\n }\n\n return { requestId: response.requestId, result };\n }\n\n // We have to wait to resolve the request promise until after we transition\n // past this timestamp so clients can read their own writes.\n requestInfo.status = {\n status: \"Completed\",\n result,\n ts: response.ts,\n onResolve,\n };\n\n return null;\n }\n\n // Remove and returns completed requests.\n removeCompleted(ts: Long): Map<RequestId, FunctionResult> {\n const completeRequests: Map<RequestId, FunctionResult> = new Map();\n for (const [requestId, requestInfo] of this.inflightRequests.entries()) {\n const status = requestInfo.status;\n if (status.status === \"Completed\" && status.ts.lessThanOrEqual(ts)) {\n status.onResolve();\n completeRequests.set(requestId, status.result);\n\n if (requestInfo.message.type === \"Mutation\") {\n this.inflightMutationsCount--;\n } else if (requestInfo.message.type === \"Action\") {\n this.inflightActionsCount--;\n }\n\n this.inflightRequests.delete(requestId);\n this.requestsOlderThanRestart.delete(requestId);\n }\n }\n return completeRequests;\n }\n\n restart(): ClientMessage[] {\n // When we reconnect to the backend, re-request all requests that are safe\n // to be resend.\n\n this.requestsOlderThanRestart = new Set(this.inflightRequests.keys());\n const allMessages = [];\n for (const [requestId, value] of this.inflightRequests) {\n if (value.status.status === \"NotSent\") {\n value.status.status = \"Requested\";\n allMessages.push(value.message);\n continue;\n }\n\n if (value.message.type === \"Mutation\") {\n // This includes ones that have already been completed because we still\n // want to tell the backend to transition the client past the completed\n // timestamp. This is safe since mutations are idempotent.\n allMessages.push(value.message);\n } else if (value.message.type === \"Action\") {\n // Unlike mutations, actions are not idempotent. When we reconnect to the\n // backend, we don't know if it is safe to resend in-flight actions, so we\n // cancel them and consider them failed.\n this.inflightRequests.delete(requestId);\n this.requestsOlderThanRestart.delete(requestId);\n this.inflightActionsCount--;\n if (value.status.status === \"Completed\") {\n throw new Error(\"Action should never be in 'Completed' state\");\n }\n value.status.onResult({\n success: false,\n errorMessage: \"Connection lost while action was in flight\",\n logLines: [],\n });\n }\n }\n return allMessages;\n }\n\n resume(): ClientMessage[] {\n const allMessages = [];\n for (const [, value] of this.inflightRequests) {\n if (value.status.status === \"NotSent\") {\n value.status.status = \"Requested\";\n allMessages.push(value.message);\n continue;\n }\n }\n return allMessages;\n }\n\n /**\n * @returns true if there are any requests that have been requested but have\n * not be completed yet.\n */\n hasIncompleteRequests(): boolean {\n for (const requestInfo of this.inflightRequests.values()) {\n if (requestInfo.status.status === \"Requested\") {\n return true;\n }\n }\n return false;\n }\n\n /**\n * @returns true if there are any inflight requests, including ones that have\n * completed on the server, but have not been applied.\n */\n hasInflightRequests(): boolean {\n return this.inflightRequests.size > 0;\n }\n\n /**\n * @returns true if there are any inflight requests, that have been hanging around\n * since prior to the most recent restart.\n */\n hasSyncedPastLastReconnect(): boolean {\n return this.requestsOlderThanRestart.size === 0;\n }\n\n timeOfOldestInflightRequest(): Date | null {\n if (this.inflightRequests.size === 0) {\n return null;\n }\n let oldestInflightRequest = Date.now();\n for (const request of this.inflightRequests.values()) {\n if (request.status.status !== \"Completed\") {\n if (request.status.requestedAt.getTime() < oldestInflightRequest) {\n oldestInflightRequest = request.status.requestedAt.getTime();\n }\n }\n }\n return new Date(oldestInflightRequest);\n }\n\n /**\n * @returns The number of mutations currently in flight.\n */\n inflightMutations(): number {\n return this.inflightMutationsCount;\n }\n\n /**\n * @returns The number of actions currently in flight.\n */\n inflightActions(): number {\n return this.inflightActionsCount;\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA6B;AAC7B,qBAAuC;AAyBhC,MAAM,eAAe;AAAA,EAW1B,
|
|
4
|
+
"sourcesContent": ["import { jsonToConvex } from \"../../values/index.js\";\nimport { logForFunction, Logger } from \"../logging.js\";\nimport { Long } from \"../long.js\";\nimport { FunctionResult } from \"./function_result.js\";\nimport {\n ActionRequest,\n ActionResponse,\n ClientMessage,\n MutationRequest,\n MutationResponse,\n RequestId,\n} from \"./protocol.js\";\n\ntype RequestStatus =\n | {\n status: \"Requested\" | \"NotSent\";\n onResult: (result: FunctionResult) => void;\n requestedAt: Date;\n }\n | {\n status: \"Completed\";\n result: FunctionResult;\n onResolve: () => void;\n ts: Long;\n };\n\nexport class RequestManager {\n private inflightRequests: Map<\n RequestId,\n {\n message: MutationRequest | ActionRequest;\n status: RequestStatus;\n }\n >;\n private requestsOlderThanRestart: Set<RequestId>;\n private inflightMutationsCount: number = 0;\n private inflightActionsCount: number = 0;\n constructor(\n private readonly logger: Logger,\n private readonly markConnectionStateDirty: () => void,\n ) {\n this.inflightRequests = new Map();\n this.requestsOlderThanRestart = new Set();\n }\n\n request(\n message: MutationRequest | ActionRequest,\n sent: boolean,\n ): Promise<FunctionResult> {\n const result = new Promise<FunctionResult>((resolve) => {\n const status = sent ? \"Requested\" : \"NotSent\";\n this.inflightRequests.set(message.requestId, {\n message,\n status: { status, requestedAt: new Date(), onResult: resolve },\n });\n\n if (message.type === \"Mutation\") {\n this.inflightMutationsCount++;\n } else if (message.type === \"Action\") {\n this.inflightActionsCount++;\n }\n });\n\n this.markConnectionStateDirty();\n return result;\n }\n\n /**\n * Update the state after receiving a response.\n *\n * @returns A RequestId if the request is complete and its optimistic update\n * can be dropped, null otherwise.\n */\n onResponse(\n response: MutationResponse | ActionResponse,\n ): { requestId: RequestId; result: FunctionResult } | null {\n const requestInfo = this.inflightRequests.get(response.requestId);\n if (requestInfo === undefined) {\n // Annoyingly we can occasionally get responses to mutations that we're no\n // longer tracking. One flow where this happens is:\n // 1. Client sends mutation 1\n // 2. Client gets response for mutation 1. The sever says that it was committed at ts=10.\n // 3. Client is disconnected\n // 4. Client reconnects and re-issues queries and this mutation.\n // 5. Server sends transition message to ts=20\n // 6. Client drops mutation because it's already been observed.\n // 7. Client receives a second response for mutation 1 but doesn't know about it anymore.\n\n // The right fix for this is probably to add a reconciliation phase on\n // reconnection where we receive responses to all the mutations before\n // the transition message so this flow could never happen (CX-1513).\n\n // For now though, we can just ignore this message.\n return null;\n }\n\n // Because `.restart()` re-requests completed requests, we may get some\n // responses for requests that are already in the \"Completed\" state.\n // We can safely ignore those because we've already notified the UI about\n // their results.\n if (requestInfo.status.status === \"Completed\") {\n return null;\n }\n\n const udfType =\n requestInfo.message.type === \"Mutation\" ? \"mutation\" : \"action\";\n const udfPath = requestInfo.message.udfPath;\n\n for (const line of response.logLines) {\n logForFunction(this.logger, \"info\", udfType, udfPath, line);\n }\n\n const status = requestInfo.status;\n let result: FunctionResult;\n let onResolve;\n if (response.success) {\n result = {\n success: true,\n logLines: response.logLines,\n value: jsonToConvex(response.result),\n };\n onResolve = () => status.onResult(result);\n } else {\n const errorMessage = response.result as string;\n const { errorData } = response;\n logForFunction(this.logger, \"error\", udfType, udfPath, errorMessage);\n result = {\n success: false,\n errorMessage,\n errorData:\n errorData !== undefined ? jsonToConvex(errorData) : undefined,\n logLines: response.logLines,\n };\n onResolve = () => status.onResult(result);\n }\n\n // We can resolve Mutation failures immediately since they don't have any\n // side effects. Actions are intentionally decoupled from\n // queries/mutations here on the sync protocol since they have different\n // guarantees.\n if (response.type === \"ActionResponse\" || !response.success) {\n onResolve();\n this.inflightRequests.delete(response.requestId);\n this.requestsOlderThanRestart.delete(response.requestId);\n\n if (requestInfo.message.type === \"Action\") {\n this.inflightActionsCount--;\n } else if (requestInfo.message.type === \"Mutation\") {\n this.inflightMutationsCount--;\n }\n\n this.markConnectionStateDirty();\n return { requestId: response.requestId, result };\n }\n\n // We have to wait to resolve the request promise until after we transition\n // past this timestamp so clients can read their own writes.\n requestInfo.status = {\n status: \"Completed\",\n result,\n ts: response.ts,\n onResolve,\n };\n\n return null;\n }\n\n // Remove and returns completed requests.\n removeCompleted(ts: Long): Map<RequestId, FunctionResult> {\n const completeRequests: Map<RequestId, FunctionResult> = new Map();\n for (const [requestId, requestInfo] of this.inflightRequests.entries()) {\n const status = requestInfo.status;\n if (status.status === \"Completed\" && status.ts.lessThanOrEqual(ts)) {\n status.onResolve();\n completeRequests.set(requestId, status.result);\n\n if (requestInfo.message.type === \"Mutation\") {\n this.inflightMutationsCount--;\n } else if (requestInfo.message.type === \"Action\") {\n this.inflightActionsCount--;\n }\n\n this.inflightRequests.delete(requestId);\n this.requestsOlderThanRestart.delete(requestId);\n }\n }\n if (completeRequests.size > 0) {\n this.markConnectionStateDirty();\n }\n return completeRequests;\n }\n\n restart(): ClientMessage[] {\n // When we reconnect to the backend, re-request all requests that are safe\n // to be resend.\n\n this.requestsOlderThanRestart = new Set(this.inflightRequests.keys());\n const allMessages = [];\n for (const [requestId, value] of this.inflightRequests) {\n if (value.status.status === \"NotSent\") {\n value.status.status = \"Requested\";\n allMessages.push(value.message);\n continue;\n }\n\n if (value.message.type === \"Mutation\") {\n // This includes ones that have already been completed because we still\n // want to tell the backend to transition the client past the completed\n // timestamp. This is safe since mutations are idempotent.\n allMessages.push(value.message);\n } else if (value.message.type === \"Action\") {\n // Unlike mutations, actions are not idempotent. When we reconnect to the\n // backend, we don't know if it is safe to resend in-flight actions, so we\n // cancel them and consider them failed.\n this.inflightRequests.delete(requestId);\n this.requestsOlderThanRestart.delete(requestId);\n this.inflightActionsCount--;\n if (value.status.status === \"Completed\") {\n throw new Error(\"Action should never be in 'Completed' state\");\n }\n value.status.onResult({\n success: false,\n errorMessage: \"Connection lost while action was in flight\",\n logLines: [],\n });\n }\n }\n this.markConnectionStateDirty();\n return allMessages;\n }\n\n resume(): ClientMessage[] {\n const allMessages = [];\n for (const [, value] of this.inflightRequests) {\n if (value.status.status === \"NotSent\") {\n value.status.status = \"Requested\";\n allMessages.push(value.message);\n continue;\n }\n }\n return allMessages;\n }\n\n /**\n * @returns true if there are any requests that have been requested but have\n * not be completed yet.\n */\n hasIncompleteRequests(): boolean {\n for (const requestInfo of this.inflightRequests.values()) {\n if (requestInfo.status.status === \"Requested\") {\n return true;\n }\n }\n return false;\n }\n\n /**\n * @returns true if there are any inflight requests, including ones that have\n * completed on the server, but have not been applied.\n */\n hasInflightRequests(): boolean {\n return this.inflightRequests.size > 0;\n }\n\n /**\n * @returns true if there are any inflight requests, that have been hanging around\n * since prior to the most recent restart.\n */\n hasSyncedPastLastReconnect(): boolean {\n return this.requestsOlderThanRestart.size === 0;\n }\n\n timeOfOldestInflightRequest(): Date | null {\n if (this.inflightRequests.size === 0) {\n return null;\n }\n let oldestInflightRequest = Date.now();\n for (const request of this.inflightRequests.values()) {\n if (request.status.status !== \"Completed\") {\n if (request.status.requestedAt.getTime() < oldestInflightRequest) {\n oldestInflightRequest = request.status.requestedAt.getTime();\n }\n }\n }\n return new Date(oldestInflightRequest);\n }\n\n /**\n * @returns The number of mutations currently in flight.\n */\n inflightMutations(): number {\n return this.inflightMutationsCount;\n }\n\n /**\n * @returns The number of actions currently in flight.\n */\n inflightActions(): number {\n return this.inflightActionsCount;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -59,7 +59,8 @@ function classifyDisconnectError(s) {
|
|
|
59
59
|
return "Unknown";
|
|
60
60
|
}
|
|
61
61
|
class WebSocketManager {
|
|
62
|
-
constructor(uri, callbacks, webSocketConstructor, logger) {
|
|
62
|
+
constructor(uri, callbacks, webSocketConstructor, logger, markConnectionStateDirty) {
|
|
63
|
+
this.markConnectionStateDirty = markConnectionStateDirty;
|
|
63
64
|
__publicField(this, "socket");
|
|
64
65
|
__publicField(this, "connectionCount");
|
|
65
66
|
__publicField(this, "_hasEverConnected", false);
|
|
@@ -103,6 +104,7 @@ class WebSocketManager {
|
|
|
103
104
|
this._logVerbose(
|
|
104
105
|
`socket state changed: ${this.socket.state}, paused: ${"paused" in this.socket ? this.socket.paused : void 0}`
|
|
105
106
|
);
|
|
107
|
+
this.markConnectionStateDirty();
|
|
106
108
|
}
|
|
107
109
|
connect() {
|
|
108
110
|
if (this.socket.state === "terminated") {
|
|
@@ -156,6 +158,7 @@ class WebSocketManager {
|
|
|
156
158
|
const response = this.onMessage(serverMessage);
|
|
157
159
|
if (response.hasSyncedPastLastReconnect) {
|
|
158
160
|
this.retries = 0;
|
|
161
|
+
this.markConnectionStateDirty();
|
|
159
162
|
}
|
|
160
163
|
};
|
|
161
164
|
ws.onclose = (event) => {
|
|
@@ -236,6 +239,7 @@ class WebSocketManager {
|
|
|
236
239
|
scheduleReconnect(reason) {
|
|
237
240
|
this.socket = { state: "disconnected" };
|
|
238
241
|
const backoff = this.nextBackoff(reason);
|
|
242
|
+
this.markConnectionStateDirty();
|
|
239
243
|
this.logger.log(`Attempting reconnect in ${backoff}ms`);
|
|
240
244
|
setTimeout(() => this.connect(), backoff);
|
|
241
245
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/browser/sync/web_socket_manager.ts"],
|
|
4
|
-
"sourcesContent": ["import { Logger } from \"../logging.js\";\nimport {\n ClientMessage,\n encodeClientMessage,\n parseServerMessage,\n ServerMessage,\n} from \"./protocol.js\";\n\nconst CLOSE_NORMAL = 1000;\nconst CLOSE_GOING_AWAY = 1001;\nconst CLOSE_NO_STATUS = 1005;\n/** Convex-specific close code representing a \"404 Not Found\".\n * The edge Onramp accepts websocket upgrades before confirming that the\n * intended destination exists, so this code is sent once we've discovered that\n * the destination does not exist.\n */\nconst CLOSE_NOT_FOUND = 4040;\n\n/**\n * The various states our WebSocket can be in:\n *\n * - \"disconnected\": We don't have a WebSocket, but plan to create one.\n * - \"connecting\": We have created the WebSocket and are waiting for the\n * `onOpen` callback.\n * - \"ready\": We have an open WebSocket.\n * - \"stopped\": The WebSocket was closed and a new one can be created via `.restart()`.\n * - \"terminated\": We have closed the WebSocket and will never create a new one.\n *\n *\n * WebSocket State Machine\n * -----------------------\n * initialState: disconnected\n * validTransitions:\n * disconnected:\n * new WebSocket() -> connecting\n * terminate() -> terminated\n * connecting:\n * onopen -> ready\n * close() -> disconnected\n * terminate() -> terminated\n * ready:\n * close() -> disconnected\n * stop() -> stopped\n * terminate() -> terminated\n * stopped:\n * restart() -> connecting\n * terminate() -> terminated\n * terminalStates:\n * terminated\n *\n *\n *\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n * \u250C\u2500\u2500\u2500\u2500terminate()\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502 disconnected \u2502\u25C0\u2500\u2510\n * \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n * \u25BC \u2502 \u25B2 \u2502\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 new WebSocket() \u2502 \u2502\n * \u250C\u2500\u25B6\u2502 terminated \u2502\u25C0\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 \u2502 \u2502\n * \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 \u2502 \u2502 \u2502\n * \u2502 \u25B2 terminate() \u2502 close() close()\n * \u2502 terminate() \u2502 \u2502 \u2502 \u2502\n * \u2502 \u2502 \u2502 \u25BC \u2502 \u2502\n * \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n * \u2502 \u2502 stopped \u2502\u2500\u2500restart()\u2500\u2500\u2500\u25B6\u2502 connecting \u2502 \u2502\n * \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n * \u2502 \u25B2 \u2502 \u2502\n * \u2502 \u2502 onopen \u2502\n * \u2502 \u2502 \u2502 \u2502\n * \u2502 \u2502 \u25BC \u2502\n * terminate() \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n * \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500stop()\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502 ready \u2502\u2500\u2500\u2518\n * \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n * \u2502 \u2502\n * \u2502 \u2502\n * \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n *\n * The `connecting` and `ready` state have a sub-state-machine for pausing.\n */\n\ntype Socket =\n | { state: \"disconnected\" }\n | { state: \"connecting\"; ws: WebSocket; paused: \"yes\" | \"no\" }\n | { state: \"ready\"; ws: WebSocket; paused: \"yes\" | \"no\" | \"uninitialized\" }\n | { state: \"stopped\" }\n | { state: \"terminated\" };\n\nexport type ReconnectMetadata = {\n connectionCount: number;\n lastCloseReason: string | null;\n};\n\nexport type OnMessageResponse = {\n hasSyncedPastLastReconnect: boolean;\n};\n\nconst serverDisconnectErrors = {\n // A known error, e.g. during a restart or push\n InternalServerError: { timeout: 1000 },\n // ErrorMetadata::overloaded() messages that we realy should back off\n SubscriptionsWorkerFullError: { timeout: 3000 },\n TooManyConcurrentRequests: { timeout: 3000 },\n CommitterFullError: { timeout: 3000 },\n AwsTooManyRequestsException: { timeout: 3000 },\n ExecuteFullError: { timeout: 3000 },\n SystemTimeoutError: { timeout: 3000 },\n ExpiredInQueue: { timeout: 3000 },\n // More ErrorMetadata::overloaded() that typically indicate a deploy just happened\n VectorIndexesUnavailable: { timeout: 1000 },\n SearchIndexesUnavailable: { timeout: 1000 },\n // More ErrorMeatadata::overloaded()\n VectorIndexTooLarge: { timeout: 3000 },\n SearchIndexTooLarge: { timeout: 3000 },\n TooManyWritesInTimePeriod: { timeout: 3000 },\n} as const satisfies Record<string, { timeout: number }>;\n\ntype ServerDisconnectError = keyof typeof serverDisconnectErrors | \"Unknown\";\n\nfunction classifyDisconnectError(s?: string): ServerDisconnectError {\n if (s === undefined) return \"Unknown\";\n // startsWith so more info could be at the end (although currently there isn't)\n\n for (const prefix of Object.keys(\n serverDisconnectErrors,\n ) as ServerDisconnectError[]) {\n if (s.startsWith(prefix)) {\n return prefix;\n }\n }\n return \"Unknown\";\n}\n\n/**\n * A wrapper around a websocket that handles errors, reconnection, and message\n * parsing.\n */\nexport class WebSocketManager {\n private socket: Socket;\n\n private connectionCount: number;\n private _hasEverConnected: boolean = false;\n private lastCloseReason:\n | \"InitialConnect\"\n | \"OnCloseInvoked\"\n | (string & {}) // a full serverErrorReason (not just the prefix) or a new one\n | null;\n\n /** Upon HTTPS/WSS failure, the first jittered backoff duration, in ms. */\n private readonly defaultInitialBackoff: number;\n\n /** We backoff exponentially, but we need to cap that--this is the jittered max. */\n private readonly maxBackoff: number;\n\n /** How many times have we failed consecutively? */\n private retries: number;\n\n /** How long before lack of server response causes us to initiate a reconnect,\n * in ms */\n private readonly serverInactivityThreshold: number;\n\n private reconnectDueToServerInactivityTimeout: ReturnType<\n typeof setTimeout\n > | null;\n\n private readonly uri: string;\n private readonly onOpen: (reconnectMetadata: ReconnectMetadata) => void;\n private readonly onResume: () => void;\n private readonly onMessage: (message: ServerMessage) => OnMessageResponse;\n private readonly webSocketConstructor: typeof WebSocket;\n private readonly logger: Logger;\n private readonly onServerDisconnectError:\n | ((message: string) => void)\n | undefined;\n\n constructor(\n uri: string,\n callbacks: {\n onOpen: (reconnectMetadata: ReconnectMetadata) => void;\n onResume: () => void;\n onMessage: (message: ServerMessage) => OnMessageResponse;\n onServerDisconnectError?: (message: string) => void;\n },\n webSocketConstructor: typeof WebSocket,\n logger: Logger,\n ) {\n this.webSocketConstructor = webSocketConstructor;\n this.socket = { state: \"disconnected\" };\n this.connectionCount = 0;\n this.lastCloseReason = \"InitialConnect\";\n\n // backoff for unknown errors\n this.defaultInitialBackoff = 1000;\n this.maxBackoff = 16000;\n this.retries = 0;\n\n this.serverInactivityThreshold = 30000;\n this.reconnectDueToServerInactivityTimeout = null;\n\n this.uri = uri;\n this.onOpen = callbacks.onOpen;\n this.onResume = callbacks.onResume;\n this.onMessage = callbacks.onMessage;\n this.onServerDisconnectError = callbacks.onServerDisconnectError;\n this.logger = logger;\n\n this.connect();\n }\n\n private setSocketState(state: Socket) {\n this.socket = state;\n this._logVerbose(\n `socket state changed: ${this.socket.state}, paused: ${\n \"paused\" in this.socket ? this.socket.paused : undefined\n }`,\n );\n }\n\n private connect() {\n if (this.socket.state === \"terminated\") {\n return;\n }\n if (\n this.socket.state !== \"disconnected\" &&\n this.socket.state !== \"stopped\"\n ) {\n throw new Error(\n \"Didn't start connection from disconnected state: \" + this.socket.state,\n );\n }\n\n const ws = new this.webSocketConstructor(this.uri);\n this._logVerbose(\"constructed WebSocket\");\n this.setSocketState({\n state: \"connecting\",\n ws,\n paused: \"no\",\n });\n\n // Kick off server inactivity timer before WebSocket connection is established\n // so we can detect cases where handshake fails.\n // The `onopen` event only fires after the connection is established:\n // Source: https://datatracker.ietf.org/doc/html/rfc6455#page-19:~:text=_The%20WebSocket%20Connection%20is%20Established_,-and\n this.resetServerInactivityTimeout();\n\n ws.onopen = () => {\n this.logger.logVerbose(\"begin ws.onopen\");\n if (this.socket.state !== \"connecting\") {\n throw new Error(\"onopen called with socket not in connecting state\");\n }\n this.setSocketState({\n state: \"ready\",\n ws,\n paused: this.socket.paused === \"yes\" ? \"uninitialized\" : \"no\",\n });\n this.resetServerInactivityTimeout();\n if (this.socket.paused === \"no\") {\n this._hasEverConnected = true;\n this.onOpen({\n connectionCount: this.connectionCount,\n lastCloseReason: this.lastCloseReason,\n });\n }\n\n if (this.lastCloseReason !== \"InitialConnect\") {\n this.logger.log(\"WebSocket reconnected\");\n }\n\n this.connectionCount += 1;\n this.lastCloseReason = null;\n };\n // NB: The WebSocket API calls `onclose` even if connection fails, so we can route all error paths through `onclose`.\n ws.onerror = (error) => {\n const message = (error as ErrorEvent).message;\n this.logger.log(`WebSocket error: ${message}`);\n };\n ws.onmessage = (message) => {\n this.resetServerInactivityTimeout();\n const serverMessage = parseServerMessage(JSON.parse(message.data));\n this._logVerbose(`received ws message with type ${serverMessage.type}`);\n const response = this.onMessage(serverMessage);\n if (response.hasSyncedPastLastReconnect) {\n // Reset backoff to 0 once all outstanding requests are complete.\n this.retries = 0;\n }\n };\n ws.onclose = (event) => {\n this._logVerbose(\"begin ws.onclose\");\n if (this.lastCloseReason === null) {\n this.lastCloseReason = event.reason ?? \"OnCloseInvoked\";\n }\n if (\n event.code !== CLOSE_NORMAL &&\n event.code !== CLOSE_GOING_AWAY && // This commonly gets fired on mobile apps when the app is backgrounded\n event.code !== CLOSE_NO_STATUS &&\n event.code !== CLOSE_NOT_FOUND // Note that we want to retry on a 404, as it can be transient during a push.\n ) {\n let msg = `WebSocket closed with code ${event.code}`;\n if (event.reason) {\n msg += `: ${event.reason}`;\n }\n this.logger.log(msg);\n if (this.onServerDisconnectError && event.reason) {\n // This callback is a unstable API, InternalServerErrors in particular may be removed\n // since they reflect expected temporary downtime. But until a quantitative measure\n // of uptime is reported this unstable API errs on the inclusive side.\n this.onServerDisconnectError(msg);\n }\n }\n const reason = classifyDisconnectError(event.reason);\n this.scheduleReconnect(reason);\n return;\n };\n }\n\n /**\n * @returns The state of the {@link Socket}.\n */\n socketState(): string {\n return this.socket.state;\n }\n\n /**\n * @param message - A ClientMessage to send.\n * @returns Whether the message (might have been) sent.\n */\n sendMessage(message: ClientMessage) {\n const messageForLog = {\n type: message.type,\n ...(message.type === \"Authenticate\" && message.tokenType === \"User\"\n ? {\n value: `...${message.value.slice(-7)}`,\n }\n : {}),\n };\n if (this.socket.state === \"ready\" && this.socket.paused === \"no\") {\n const encodedMessage = encodeClientMessage(message);\n const request = JSON.stringify(encodedMessage);\n try {\n this.socket.ws.send(request);\n } catch (error: any) {\n this.logger.log(\n `Failed to send message on WebSocket, reconnecting: ${error}`,\n );\n this.closeAndReconnect(\"FailedToSendMessage\");\n }\n // We are not sure if this was sent or not.\n this._logVerbose(\n `sent message with type ${message.type}: ${JSON.stringify(\n messageForLog,\n )}`,\n );\n return true;\n }\n this._logVerbose(\n `message not sent (socket state: ${this.socket.state}, paused: ${\"paused\" in this.socket ? this.socket.paused : undefined}): ${JSON.stringify(\n messageForLog,\n )}`,\n );\n\n return false;\n }\n\n private resetServerInactivityTimeout() {\n if (this.socket.state === \"terminated\") {\n // Don't reset any timers if we were trying to terminate.\n return;\n }\n if (this.reconnectDueToServerInactivityTimeout !== null) {\n clearTimeout(this.reconnectDueToServerInactivityTimeout);\n this.reconnectDueToServerInactivityTimeout = null;\n }\n this.reconnectDueToServerInactivityTimeout = setTimeout(() => {\n this.closeAndReconnect(\"InactiveServer\");\n }, this.serverInactivityThreshold);\n }\n\n private scheduleReconnect(reason: \"client\" | ServerDisconnectError) {\n this.socket = { state: \"disconnected\" };\n const backoff = this.nextBackoff(reason);\n this.logger.log(`Attempting reconnect in ${backoff}ms`);\n setTimeout(() => this.connect(), backoff);\n }\n\n /**\n * Close the WebSocket and schedule a reconnect.\n *\n * This should be used when we hit an error and would like to restart the session.\n */\n private closeAndReconnect(closeReason: string) {\n this._logVerbose(`begin closeAndReconnect with reason ${closeReason}`);\n switch (this.socket.state) {\n case \"disconnected\":\n case \"terminated\":\n case \"stopped\":\n // Nothing to do if we don't have a WebSocket.\n return;\n case \"connecting\":\n case \"ready\": {\n this.lastCloseReason = closeReason;\n // Close the old socket asynchronously, we'll open a new socket in reconnect.\n void this.close();\n this.scheduleReconnect(\"client\");\n return;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n const _: never = this.socket;\n }\n }\n }\n\n /**\n * Close the WebSocket, being careful to clear the onclose handler to avoid re-entrant\n * calls. Use this instead of directly calling `ws.close()`\n *\n * It is the callers responsibility to update the state after this method is called so that the\n * closed socket is not accessible or used again after this method is called\n */\n private close(): Promise<void> {\n switch (this.socket.state) {\n case \"disconnected\":\n case \"terminated\":\n case \"stopped\":\n // Nothing to do if we don't have a WebSocket.\n return Promise.resolve();\n case \"connecting\": {\n const ws = this.socket.ws;\n return new Promise((r) => {\n ws.onclose = () => {\n this._logVerbose(\"Closed after connecting\");\n r();\n };\n ws.onopen = () => {\n this._logVerbose(\"Opened after connecting\");\n ws.close();\n };\n });\n }\n case \"ready\": {\n this._logVerbose(\"ws.close called\");\n const ws = this.socket.ws;\n const result: Promise<void> = new Promise((r) => {\n ws.onclose = () => {\n r();\n };\n });\n ws.close();\n return result;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n const _: never = this.socket;\n return Promise.resolve();\n }\n }\n }\n\n /**\n * Close the WebSocket and do not reconnect.\n * @returns A Promise that resolves when the WebSocket `onClose` callback is called.\n */\n terminate(): Promise<void> {\n if (this.reconnectDueToServerInactivityTimeout) {\n clearTimeout(this.reconnectDueToServerInactivityTimeout);\n }\n switch (this.socket.state) {\n case \"terminated\":\n case \"stopped\":\n case \"disconnected\":\n case \"connecting\":\n case \"ready\": {\n const result = this.close();\n this.setSocketState({ state: \"terminated\" });\n return result;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n const _: never = this.socket;\n throw new Error(\n `Invalid websocket state: ${(this.socket as any).state}`,\n );\n }\n }\n }\n\n stop(): Promise<void> {\n switch (this.socket.state) {\n case \"terminated\":\n // If we're terminating we ignore stop\n return Promise.resolve();\n case \"connecting\":\n case \"stopped\":\n case \"disconnected\":\n case \"ready\": {\n const result = this.close();\n this.socket = { state: \"stopped\" };\n return result;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n const _: never = this.socket;\n return Promise.resolve();\n }\n }\n }\n\n /**\n * Create a new WebSocket after a previous `stop()`, unless `terminate()` was\n * called before.\n */\n tryRestart(): void {\n switch (this.socket.state) {\n case \"stopped\":\n break;\n case \"terminated\":\n case \"connecting\":\n case \"ready\":\n case \"disconnected\":\n this.logger.logVerbose(\"Restart called without stopping first\");\n return;\n default: {\n // Enforce that the switch-case is exhaustive.\n const _: never = this.socket;\n }\n }\n this.connect();\n }\n\n pause(): void {\n switch (this.socket.state) {\n case \"disconnected\":\n case \"stopped\":\n case \"terminated\":\n // If already stopped or stopping ignore.\n return;\n case \"connecting\":\n case \"ready\": {\n this.socket = { ...this.socket, paused: \"yes\" };\n return;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n const _: never = this.socket;\n return;\n }\n }\n }\n\n /**\n * Resume the state machine if previously paused.\n */\n resume(): void {\n switch (this.socket.state) {\n case \"connecting\":\n this.socket = { ...this.socket, paused: \"no\" };\n return;\n case \"ready\":\n if (this.socket.paused === \"uninitialized\") {\n this.socket = { ...this.socket, paused: \"no\" };\n this.onOpen({\n connectionCount: this.connectionCount,\n lastCloseReason: this.lastCloseReason,\n });\n } else if (this.socket.paused === \"yes\") {\n this.socket = { ...this.socket, paused: \"no\" };\n this.onResume();\n }\n return;\n case \"terminated\":\n case \"stopped\":\n case \"disconnected\":\n // Ignore resume if not paused, perhaps we already resumed.\n return;\n default: {\n // Enforce that the switch-case is exhaustive.\n const _: never = this.socket;\n }\n }\n this.connect();\n }\n\n connectionState(): {\n isConnected: boolean;\n hasEverConnected: boolean;\n connectionCount: number;\n connectionRetries: number;\n } {\n return {\n isConnected: this.socket.state === \"ready\",\n hasEverConnected: this._hasEverConnected,\n connectionCount: this.connectionCount,\n connectionRetries: this.retries,\n };\n }\n\n private _logVerbose(message: string) {\n this.logger.logVerbose(message);\n }\n\n private nextBackoff(reason: \"client\" | ServerDisconnectError): number {\n const initialBackoff: number =\n reason === \"client\"\n ? 100 // There's no evidence of a server problem, retry quickly\n : reason === \"Unknown\"\n ? this.defaultInitialBackoff\n : serverDisconnectErrors[reason].timeout;\n\n const baseBackoff = initialBackoff * Math.pow(2, this.retries);\n this.retries += 1;\n const actualBackoff = Math.min(baseBackoff, this.maxBackoff);\n const jitter = actualBackoff * (Math.random() - 0.5);\n return actualBackoff + jitter;\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,sBAKO;AAEP,MAAM,eAAe;AACrB,MAAM,mBAAmB;AACzB,MAAM,kBAAkB;AAMxB,MAAM,kBAAkB;AA+ExB,MAAM,yBAAyB;AAAA;AAAA,EAE7B,qBAAqB,EAAE,SAAS,IAAK;AAAA;AAAA,EAErC,8BAA8B,EAAE,SAAS,IAAK;AAAA,EAC9C,2BAA2B,EAAE,SAAS,IAAK;AAAA,EAC3C,oBAAoB,EAAE,SAAS,IAAK;AAAA,EACpC,6BAA6B,EAAE,SAAS,IAAK;AAAA,EAC7C,kBAAkB,EAAE,SAAS,IAAK;AAAA,EAClC,oBAAoB,EAAE,SAAS,IAAK;AAAA,EACpC,gBAAgB,EAAE,SAAS,IAAK;AAAA;AAAA,EAEhC,0BAA0B,EAAE,SAAS,IAAK;AAAA,EAC1C,0BAA0B,EAAE,SAAS,IAAK;AAAA;AAAA,EAE1C,qBAAqB,EAAE,SAAS,IAAK;AAAA,EACrC,qBAAqB,EAAE,SAAS,IAAK;AAAA,EACrC,2BAA2B,EAAE,SAAS,IAAK;AAC7C;AAIA,SAAS,wBAAwB,GAAmC;AAClE,MAAI,MAAM,OAAW,QAAO;AAG5B,aAAW,UAAU,OAAO;AAAA,IAC1B;AAAA,EACF,GAA8B;AAC5B,QAAI,EAAE,WAAW,MAAM,GAAG;AACxB,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAMO,MAAM,iBAAiB;AAAA,EAsC5B,YACE,KACA,WAMA,sBACA,
|
|
4
|
+
"sourcesContent": ["import { Logger } from \"../logging.js\";\nimport {\n ClientMessage,\n encodeClientMessage,\n parseServerMessage,\n ServerMessage,\n} from \"./protocol.js\";\n\nconst CLOSE_NORMAL = 1000;\nconst CLOSE_GOING_AWAY = 1001;\nconst CLOSE_NO_STATUS = 1005;\n/** Convex-specific close code representing a \"404 Not Found\".\n * The edge Onramp accepts websocket upgrades before confirming that the\n * intended destination exists, so this code is sent once we've discovered that\n * the destination does not exist.\n */\nconst CLOSE_NOT_FOUND = 4040;\n\n/**\n * The various states our WebSocket can be in:\n *\n * - \"disconnected\": We don't have a WebSocket, but plan to create one.\n * - \"connecting\": We have created the WebSocket and are waiting for the\n * `onOpen` callback.\n * - \"ready\": We have an open WebSocket.\n * - \"stopped\": The WebSocket was closed and a new one can be created via `.restart()`.\n * - \"terminated\": We have closed the WebSocket and will never create a new one.\n *\n *\n * WebSocket State Machine\n * -----------------------\n * initialState: disconnected\n * validTransitions:\n * disconnected:\n * new WebSocket() -> connecting\n * terminate() -> terminated\n * connecting:\n * onopen -> ready\n * close() -> disconnected\n * terminate() -> terminated\n * ready:\n * close() -> disconnected\n * stop() -> stopped\n * terminate() -> terminated\n * stopped:\n * restart() -> connecting\n * terminate() -> terminated\n * terminalStates:\n * terminated\n *\n *\n *\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n * \u250C\u2500\u2500\u2500\u2500terminate()\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502 disconnected \u2502\u25C0\u2500\u2510\n * \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n * \u25BC \u2502 \u25B2 \u2502\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 new WebSocket() \u2502 \u2502\n * \u250C\u2500\u25B6\u2502 terminated \u2502\u25C0\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 \u2502 \u2502\n * \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 \u2502 \u2502 \u2502\n * \u2502 \u25B2 terminate() \u2502 close() close()\n * \u2502 terminate() \u2502 \u2502 \u2502 \u2502\n * \u2502 \u2502 \u2502 \u25BC \u2502 \u2502\n * \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n * \u2502 \u2502 stopped \u2502\u2500\u2500restart()\u2500\u2500\u2500\u25B6\u2502 connecting \u2502 \u2502\n * \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n * \u2502 \u25B2 \u2502 \u2502\n * \u2502 \u2502 onopen \u2502\n * \u2502 \u2502 \u2502 \u2502\n * \u2502 \u2502 \u25BC \u2502\n * terminate() \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n * \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500stop()\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502 ready \u2502\u2500\u2500\u2518\n * \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n * \u2502 \u2502\n * \u2502 \u2502\n * \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n *\n * The `connecting` and `ready` state have a sub-state-machine for pausing.\n */\n\ntype Socket =\n | { state: \"disconnected\" }\n | { state: \"connecting\"; ws: WebSocket; paused: \"yes\" | \"no\" }\n | { state: \"ready\"; ws: WebSocket; paused: \"yes\" | \"no\" | \"uninitialized\" }\n | { state: \"stopped\" }\n | { state: \"terminated\" };\n\nexport type ReconnectMetadata = {\n connectionCount: number;\n lastCloseReason: string | null;\n};\n\nexport type OnMessageResponse = {\n hasSyncedPastLastReconnect: boolean;\n};\n\nconst serverDisconnectErrors = {\n // A known error, e.g. during a restart or push\n InternalServerError: { timeout: 1000 },\n // ErrorMetadata::overloaded() messages that we realy should back off\n SubscriptionsWorkerFullError: { timeout: 3000 },\n TooManyConcurrentRequests: { timeout: 3000 },\n CommitterFullError: { timeout: 3000 },\n AwsTooManyRequestsException: { timeout: 3000 },\n ExecuteFullError: { timeout: 3000 },\n SystemTimeoutError: { timeout: 3000 },\n ExpiredInQueue: { timeout: 3000 },\n // More ErrorMetadata::overloaded() that typically indicate a deploy just happened\n VectorIndexesUnavailable: { timeout: 1000 },\n SearchIndexesUnavailable: { timeout: 1000 },\n // More ErrorMeatadata::overloaded()\n VectorIndexTooLarge: { timeout: 3000 },\n SearchIndexTooLarge: { timeout: 3000 },\n TooManyWritesInTimePeriod: { timeout: 3000 },\n} as const satisfies Record<string, { timeout: number }>;\n\ntype ServerDisconnectError = keyof typeof serverDisconnectErrors | \"Unknown\";\n\nfunction classifyDisconnectError(s?: string): ServerDisconnectError {\n if (s === undefined) return \"Unknown\";\n // startsWith so more info could be at the end (although currently there isn't)\n\n for (const prefix of Object.keys(\n serverDisconnectErrors,\n ) as ServerDisconnectError[]) {\n if (s.startsWith(prefix)) {\n return prefix;\n }\n }\n return \"Unknown\";\n}\n\n/**\n * A wrapper around a websocket that handles errors, reconnection, and message\n * parsing.\n */\nexport class WebSocketManager {\n private socket: Socket;\n\n private connectionCount: number;\n private _hasEverConnected: boolean = false;\n private lastCloseReason:\n | \"InitialConnect\"\n | \"OnCloseInvoked\"\n | (string & {}) // a full serverErrorReason (not just the prefix) or a new one\n | null;\n\n /** Upon HTTPS/WSS failure, the first jittered backoff duration, in ms. */\n private readonly defaultInitialBackoff: number;\n\n /** We backoff exponentially, but we need to cap that--this is the jittered max. */\n private readonly maxBackoff: number;\n\n /** How many times have we failed consecutively? */\n private retries: number;\n\n /** How long before lack of server response causes us to initiate a reconnect,\n * in ms */\n private readonly serverInactivityThreshold: number;\n\n private reconnectDueToServerInactivityTimeout: ReturnType<\n typeof setTimeout\n > | null;\n\n private readonly uri: string;\n private readonly onOpen: (reconnectMetadata: ReconnectMetadata) => void;\n private readonly onResume: () => void;\n private readonly onMessage: (message: ServerMessage) => OnMessageResponse;\n private readonly webSocketConstructor: typeof WebSocket;\n private readonly logger: Logger;\n private readonly onServerDisconnectError:\n | ((message: string) => void)\n | undefined;\n\n constructor(\n uri: string,\n callbacks: {\n onOpen: (reconnectMetadata: ReconnectMetadata) => void;\n onResume: () => void;\n onMessage: (message: ServerMessage) => OnMessageResponse;\n onServerDisconnectError?: (message: string) => void;\n },\n webSocketConstructor: typeof WebSocket,\n logger: Logger,\n private readonly markConnectionStateDirty: () => void,\n ) {\n this.webSocketConstructor = webSocketConstructor;\n this.socket = { state: \"disconnected\" };\n this.connectionCount = 0;\n this.lastCloseReason = \"InitialConnect\";\n\n // backoff for unknown errors\n this.defaultInitialBackoff = 1000;\n this.maxBackoff = 16000;\n this.retries = 0;\n\n this.serverInactivityThreshold = 30000;\n this.reconnectDueToServerInactivityTimeout = null;\n\n this.uri = uri;\n this.onOpen = callbacks.onOpen;\n this.onResume = callbacks.onResume;\n this.onMessage = callbacks.onMessage;\n this.onServerDisconnectError = callbacks.onServerDisconnectError;\n this.logger = logger;\n\n this.connect();\n }\n\n private setSocketState(state: Socket) {\n this.socket = state;\n this._logVerbose(\n `socket state changed: ${this.socket.state}, paused: ${\n \"paused\" in this.socket ? this.socket.paused : undefined\n }`,\n );\n this.markConnectionStateDirty();\n }\n\n private connect() {\n if (this.socket.state === \"terminated\") {\n return;\n }\n if (\n this.socket.state !== \"disconnected\" &&\n this.socket.state !== \"stopped\"\n ) {\n throw new Error(\n \"Didn't start connection from disconnected state: \" + this.socket.state,\n );\n }\n\n const ws = new this.webSocketConstructor(this.uri);\n this._logVerbose(\"constructed WebSocket\");\n this.setSocketState({\n state: \"connecting\",\n ws,\n paused: \"no\",\n });\n\n // Kick off server inactivity timer before WebSocket connection is established\n // so we can detect cases where handshake fails.\n // The `onopen` event only fires after the connection is established:\n // Source: https://datatracker.ietf.org/doc/html/rfc6455#page-19:~:text=_The%20WebSocket%20Connection%20is%20Established_,-and\n this.resetServerInactivityTimeout();\n\n ws.onopen = () => {\n this.logger.logVerbose(\"begin ws.onopen\");\n if (this.socket.state !== \"connecting\") {\n throw new Error(\"onopen called with socket not in connecting state\");\n }\n this.setSocketState({\n state: \"ready\",\n ws,\n paused: this.socket.paused === \"yes\" ? \"uninitialized\" : \"no\",\n });\n this.resetServerInactivityTimeout();\n if (this.socket.paused === \"no\") {\n this._hasEverConnected = true;\n this.onOpen({\n connectionCount: this.connectionCount,\n lastCloseReason: this.lastCloseReason,\n });\n }\n\n if (this.lastCloseReason !== \"InitialConnect\") {\n this.logger.log(\"WebSocket reconnected\");\n }\n\n this.connectionCount += 1;\n this.lastCloseReason = null;\n };\n // NB: The WebSocket API calls `onclose` even if connection fails, so we can route all error paths through `onclose`.\n ws.onerror = (error) => {\n const message = (error as ErrorEvent).message;\n this.logger.log(`WebSocket error: ${message}`);\n };\n ws.onmessage = (message) => {\n this.resetServerInactivityTimeout();\n const serverMessage = parseServerMessage(JSON.parse(message.data));\n this._logVerbose(`received ws message with type ${serverMessage.type}`);\n const response = this.onMessage(serverMessage);\n if (response.hasSyncedPastLastReconnect) {\n // Reset backoff to 0 once all outstanding requests are complete.\n this.retries = 0;\n this.markConnectionStateDirty();\n }\n };\n ws.onclose = (event) => {\n this._logVerbose(\"begin ws.onclose\");\n if (this.lastCloseReason === null) {\n this.lastCloseReason = event.reason ?? \"OnCloseInvoked\";\n }\n if (\n event.code !== CLOSE_NORMAL &&\n event.code !== CLOSE_GOING_AWAY && // This commonly gets fired on mobile apps when the app is backgrounded\n event.code !== CLOSE_NO_STATUS &&\n event.code !== CLOSE_NOT_FOUND // Note that we want to retry on a 404, as it can be transient during a push.\n ) {\n let msg = `WebSocket closed with code ${event.code}`;\n if (event.reason) {\n msg += `: ${event.reason}`;\n }\n this.logger.log(msg);\n if (this.onServerDisconnectError && event.reason) {\n // This callback is a unstable API, InternalServerErrors in particular may be removed\n // since they reflect expected temporary downtime. But until a quantitative measure\n // of uptime is reported this unstable API errs on the inclusive side.\n this.onServerDisconnectError(msg);\n }\n }\n const reason = classifyDisconnectError(event.reason);\n this.scheduleReconnect(reason);\n return;\n };\n }\n\n /**\n * @returns The state of the {@link Socket}.\n */\n socketState(): string {\n return this.socket.state;\n }\n\n /**\n * @param message - A ClientMessage to send.\n * @returns Whether the message (might have been) sent.\n */\n sendMessage(message: ClientMessage) {\n const messageForLog = {\n type: message.type,\n ...(message.type === \"Authenticate\" && message.tokenType === \"User\"\n ? {\n value: `...${message.value.slice(-7)}`,\n }\n : {}),\n };\n if (this.socket.state === \"ready\" && this.socket.paused === \"no\") {\n const encodedMessage = encodeClientMessage(message);\n const request = JSON.stringify(encodedMessage);\n try {\n this.socket.ws.send(request);\n } catch (error: any) {\n this.logger.log(\n `Failed to send message on WebSocket, reconnecting: ${error}`,\n );\n this.closeAndReconnect(\"FailedToSendMessage\");\n }\n // We are not sure if this was sent or not.\n this._logVerbose(\n `sent message with type ${message.type}: ${JSON.stringify(\n messageForLog,\n )}`,\n );\n return true;\n }\n this._logVerbose(\n `message not sent (socket state: ${this.socket.state}, paused: ${\"paused\" in this.socket ? this.socket.paused : undefined}): ${JSON.stringify(\n messageForLog,\n )}`,\n );\n\n return false;\n }\n\n private resetServerInactivityTimeout() {\n if (this.socket.state === \"terminated\") {\n // Don't reset any timers if we were trying to terminate.\n return;\n }\n if (this.reconnectDueToServerInactivityTimeout !== null) {\n clearTimeout(this.reconnectDueToServerInactivityTimeout);\n this.reconnectDueToServerInactivityTimeout = null;\n }\n this.reconnectDueToServerInactivityTimeout = setTimeout(() => {\n this.closeAndReconnect(\"InactiveServer\");\n }, this.serverInactivityThreshold);\n }\n\n private scheduleReconnect(reason: \"client\" | ServerDisconnectError) {\n this.socket = { state: \"disconnected\" };\n const backoff = this.nextBackoff(reason);\n this.markConnectionStateDirty();\n this.logger.log(`Attempting reconnect in ${backoff}ms`);\n setTimeout(() => this.connect(), backoff);\n }\n\n /**\n * Close the WebSocket and schedule a reconnect.\n *\n * This should be used when we hit an error and would like to restart the session.\n */\n private closeAndReconnect(closeReason: string) {\n this._logVerbose(`begin closeAndReconnect with reason ${closeReason}`);\n switch (this.socket.state) {\n case \"disconnected\":\n case \"terminated\":\n case \"stopped\":\n // Nothing to do if we don't have a WebSocket.\n return;\n case \"connecting\":\n case \"ready\": {\n this.lastCloseReason = closeReason;\n // Close the old socket asynchronously, we'll open a new socket in reconnect.\n void this.close();\n this.scheduleReconnect(\"client\");\n return;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n const _: never = this.socket;\n }\n }\n }\n\n /**\n * Close the WebSocket, being careful to clear the onclose handler to avoid re-entrant\n * calls. Use this instead of directly calling `ws.close()`\n *\n * It is the callers responsibility to update the state after this method is called so that the\n * closed socket is not accessible or used again after this method is called\n */\n private close(): Promise<void> {\n switch (this.socket.state) {\n case \"disconnected\":\n case \"terminated\":\n case \"stopped\":\n // Nothing to do if we don't have a WebSocket.\n return Promise.resolve();\n case \"connecting\": {\n const ws = this.socket.ws;\n return new Promise((r) => {\n ws.onclose = () => {\n this._logVerbose(\"Closed after connecting\");\n r();\n };\n ws.onopen = () => {\n this._logVerbose(\"Opened after connecting\");\n ws.close();\n };\n });\n }\n case \"ready\": {\n this._logVerbose(\"ws.close called\");\n const ws = this.socket.ws;\n const result: Promise<void> = new Promise((r) => {\n ws.onclose = () => {\n r();\n };\n });\n ws.close();\n return result;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n const _: never = this.socket;\n return Promise.resolve();\n }\n }\n }\n\n /**\n * Close the WebSocket and do not reconnect.\n * @returns A Promise that resolves when the WebSocket `onClose` callback is called.\n */\n terminate(): Promise<void> {\n if (this.reconnectDueToServerInactivityTimeout) {\n clearTimeout(this.reconnectDueToServerInactivityTimeout);\n }\n switch (this.socket.state) {\n case \"terminated\":\n case \"stopped\":\n case \"disconnected\":\n case \"connecting\":\n case \"ready\": {\n const result = this.close();\n this.setSocketState({ state: \"terminated\" });\n return result;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n const _: never = this.socket;\n throw new Error(\n `Invalid websocket state: ${(this.socket as any).state}`,\n );\n }\n }\n }\n\n stop(): Promise<void> {\n switch (this.socket.state) {\n case \"terminated\":\n // If we're terminating we ignore stop\n return Promise.resolve();\n case \"connecting\":\n case \"stopped\":\n case \"disconnected\":\n case \"ready\": {\n const result = this.close();\n this.socket = { state: \"stopped\" };\n return result;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n const _: never = this.socket;\n return Promise.resolve();\n }\n }\n }\n\n /**\n * Create a new WebSocket after a previous `stop()`, unless `terminate()` was\n * called before.\n */\n tryRestart(): void {\n switch (this.socket.state) {\n case \"stopped\":\n break;\n case \"terminated\":\n case \"connecting\":\n case \"ready\":\n case \"disconnected\":\n this.logger.logVerbose(\"Restart called without stopping first\");\n return;\n default: {\n // Enforce that the switch-case is exhaustive.\n const _: never = this.socket;\n }\n }\n this.connect();\n }\n\n pause(): void {\n switch (this.socket.state) {\n case \"disconnected\":\n case \"stopped\":\n case \"terminated\":\n // If already stopped or stopping ignore.\n return;\n case \"connecting\":\n case \"ready\": {\n this.socket = { ...this.socket, paused: \"yes\" };\n return;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n const _: never = this.socket;\n return;\n }\n }\n }\n\n /**\n * Resume the state machine if previously paused.\n */\n resume(): void {\n switch (this.socket.state) {\n case \"connecting\":\n this.socket = { ...this.socket, paused: \"no\" };\n return;\n case \"ready\":\n if (this.socket.paused === \"uninitialized\") {\n this.socket = { ...this.socket, paused: \"no\" };\n this.onOpen({\n connectionCount: this.connectionCount,\n lastCloseReason: this.lastCloseReason,\n });\n } else if (this.socket.paused === \"yes\") {\n this.socket = { ...this.socket, paused: \"no\" };\n this.onResume();\n }\n return;\n case \"terminated\":\n case \"stopped\":\n case \"disconnected\":\n // Ignore resume if not paused, perhaps we already resumed.\n return;\n default: {\n // Enforce that the switch-case is exhaustive.\n const _: never = this.socket;\n }\n }\n this.connect();\n }\n\n connectionState(): {\n isConnected: boolean;\n hasEverConnected: boolean;\n connectionCount: number;\n connectionRetries: number;\n } {\n return {\n isConnected: this.socket.state === \"ready\",\n hasEverConnected: this._hasEverConnected,\n connectionCount: this.connectionCount,\n connectionRetries: this.retries,\n };\n }\n\n private _logVerbose(message: string) {\n this.logger.logVerbose(message);\n }\n\n private nextBackoff(reason: \"client\" | ServerDisconnectError): number {\n const initialBackoff: number =\n reason === \"client\"\n ? 100 // There's no evidence of a server problem, retry quickly\n : reason === \"Unknown\"\n ? this.defaultInitialBackoff\n : serverDisconnectErrors[reason].timeout;\n\n const baseBackoff = initialBackoff * Math.pow(2, this.retries);\n this.retries += 1;\n const actualBackoff = Math.min(baseBackoff, this.maxBackoff);\n const jitter = actualBackoff * (Math.random() - 0.5);\n return actualBackoff + jitter;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|