convex 1.42.0 → 1.42.2-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/browser.bundle.js +24 -6
  3. package/dist/browser.bundle.js.map +2 -2
  4. package/dist/cjs/browser/simple_client.js +2 -2
  5. package/dist/cjs/browser/simple_client.js.map +2 -2
  6. package/dist/cjs/browser/sync/authentication_manager.js +21 -4
  7. package/dist/cjs/browser/sync/authentication_manager.js.map +2 -2
  8. package/dist/cjs/browser/sync/client.js +2 -1
  9. package/dist/cjs/browser/sync/client.js.map +2 -2
  10. package/dist/cjs/cli/dev.js +1 -0
  11. package/dist/cjs/cli/dev.js.map +2 -2
  12. package/dist/cjs/cli/generatedApi.js.map +1 -1
  13. package/dist/cjs/cli/lib/api.js.map +2 -2
  14. package/dist/cjs/cli/lib/typecheck.js +28 -10
  15. package/dist/cjs/cli/lib/typecheck.js.map +2 -2
  16. package/dist/cjs/cli/lib/utils/utils.js +4 -12
  17. package/dist/cjs/cli/lib/utils/utils.js.map +2 -2
  18. package/dist/cjs/index.js +1 -1
  19. package/dist/cjs/index.js.map +1 -1
  20. package/dist/cjs/react/client.js +1 -1
  21. package/dist/cjs/react/client.js.map +2 -2
  22. package/dist/cjs/react-clerk/ConvexProviderWithClerk.js +2 -1
  23. package/dist/cjs/react-clerk/ConvexProviderWithClerk.js.map +2 -2
  24. package/dist/cjs/server/database.js.map +1 -1
  25. package/dist/cjs/server/impl/meta_impl.js +2 -2
  26. package/dist/cjs/server/impl/meta_impl.js.map +2 -2
  27. package/dist/cjs/server/impl/query_impl.js.map +2 -2
  28. package/dist/cjs/server/impl/registration_impl.js +0 -2
  29. package/dist/cjs/server/impl/registration_impl.js.map +2 -2
  30. package/dist/cjs/server/meta.js.map +1 -1
  31. package/dist/cjs/server/query.js.map +1 -1
  32. package/dist/cjs/server/registration.js.map +1 -1
  33. package/dist/cjs/server/schema.js.map +1 -1
  34. package/dist/cjs-types/browser/simple_client.d.ts +1 -2
  35. package/dist/cjs-types/browser/simple_client.d.ts.map +1 -1
  36. package/dist/cjs-types/browser/sync/authentication_manager.d.ts +2 -0
  37. package/dist/cjs-types/browser/sync/authentication_manager.d.ts.map +1 -1
  38. package/dist/cjs-types/browser/sync/client.d.ts +16 -0
  39. package/dist/cjs-types/browser/sync/client.d.ts.map +1 -1
  40. package/dist/cjs-types/cli/dev.d.ts.map +1 -1
  41. package/dist/cjs-types/cli/dev.test.d.ts +2 -0
  42. package/dist/cjs-types/cli/dev.test.d.ts.map +1 -0
  43. package/dist/cjs-types/cli/generatedApi.d.ts +0 -1
  44. package/dist/cjs-types/cli/generatedApi.d.ts.map +1 -1
  45. package/dist/cjs-types/cli/lib/api.d.ts +0 -1
  46. package/dist/cjs-types/cli/lib/api.d.ts.map +1 -1
  47. package/dist/cjs-types/cli/lib/utils/utils.d.ts.map +1 -1
  48. package/dist/cjs-types/index.d.ts +1 -1
  49. package/dist/cjs-types/index.d.ts.map +1 -1
  50. package/dist/cjs-types/react/client.d.ts.map +1 -1
  51. package/dist/cjs-types/react/client_provided_base_client.test.d.ts +2 -0
  52. package/dist/cjs-types/react/client_provided_base_client.test.d.ts.map +1 -0
  53. package/dist/cjs-types/react-clerk/ConvexProviderWithClerk.d.ts +1 -0
  54. package/dist/cjs-types/react-clerk/ConvexProviderWithClerk.d.ts.map +1 -1
  55. package/dist/cjs-types/server/database.d.ts.map +1 -1
  56. package/dist/cjs-types/server/impl/query_impl.d.ts.map +1 -1
  57. package/dist/cjs-types/server/impl/registration_impl.d.ts.map +1 -1
  58. package/dist/cjs-types/server/meta.d.ts.map +1 -1
  59. package/dist/cjs-types/server/query.d.ts.map +1 -1
  60. package/dist/cjs-types/server/registration.d.ts.map +1 -1
  61. package/dist/cjs-types/server/schema.d.ts.map +1 -1
  62. package/dist/cli.bundle.cjs +58 -28
  63. package/dist/cli.bundle.cjs.map +3 -3
  64. package/dist/esm/browser/simple_client.js +5 -3
  65. package/dist/esm/browser/simple_client.js.map +2 -2
  66. package/dist/esm/browser/sync/authentication_manager.js +21 -4
  67. package/dist/esm/browser/sync/authentication_manager.js.map +2 -2
  68. package/dist/esm/browser/sync/client.js +2 -1
  69. package/dist/esm/browser/sync/client.js.map +2 -2
  70. package/dist/esm/cli/dev.js +3 -1
  71. package/dist/esm/cli/dev.js.map +2 -2
  72. package/dist/esm/cli/lib/api.js.map +2 -2
  73. package/dist/esm/cli/lib/typecheck.js +28 -10
  74. package/dist/esm/cli/lib/typecheck.js.map +3 -3
  75. package/dist/esm/cli/lib/utils/utils.js +4 -12
  76. package/dist/esm/cli/lib/utils/utils.js.map +2 -2
  77. package/dist/esm/index.js +1 -1
  78. package/dist/esm/index.js.map +1 -1
  79. package/dist/esm/react/client.js +1 -1
  80. package/dist/esm/react/client.js.map +2 -2
  81. package/dist/esm/react-clerk/ConvexProviderWithClerk.js +2 -1
  82. package/dist/esm/react-clerk/ConvexProviderWithClerk.js.map +2 -2
  83. package/dist/esm/server/impl/meta_impl.js +2 -2
  84. package/dist/esm/server/impl/meta_impl.js.map +2 -2
  85. package/dist/esm/server/impl/query_impl.js.map +2 -2
  86. package/dist/esm/server/impl/registration_impl.js +0 -2
  87. package/dist/esm/server/impl/registration_impl.js.map +2 -2
  88. package/dist/esm/server/schema.js.map +1 -1
  89. package/dist/esm-types/browser/simple_client.d.ts +1 -2
  90. package/dist/esm-types/browser/simple_client.d.ts.map +1 -1
  91. package/dist/esm-types/browser/sync/authentication_manager.d.ts +2 -0
  92. package/dist/esm-types/browser/sync/authentication_manager.d.ts.map +1 -1
  93. package/dist/esm-types/browser/sync/client.d.ts +16 -0
  94. package/dist/esm-types/browser/sync/client.d.ts.map +1 -1
  95. package/dist/esm-types/cli/dev.d.ts.map +1 -1
  96. package/dist/esm-types/cli/dev.test.d.ts +2 -0
  97. package/dist/esm-types/cli/dev.test.d.ts.map +1 -0
  98. package/dist/esm-types/cli/generatedApi.d.ts +0 -1
  99. package/dist/esm-types/cli/generatedApi.d.ts.map +1 -1
  100. package/dist/esm-types/cli/lib/api.d.ts +0 -1
  101. package/dist/esm-types/cli/lib/api.d.ts.map +1 -1
  102. package/dist/esm-types/cli/lib/utils/utils.d.ts.map +1 -1
  103. package/dist/esm-types/index.d.ts +1 -1
  104. package/dist/esm-types/index.d.ts.map +1 -1
  105. package/dist/esm-types/react/client.d.ts.map +1 -1
  106. package/dist/esm-types/react/client_provided_base_client.test.d.ts +2 -0
  107. package/dist/esm-types/react/client_provided_base_client.test.d.ts.map +1 -0
  108. package/dist/esm-types/react-clerk/ConvexProviderWithClerk.d.ts +1 -0
  109. package/dist/esm-types/react-clerk/ConvexProviderWithClerk.d.ts.map +1 -1
  110. package/dist/esm-types/server/database.d.ts.map +1 -1
  111. package/dist/esm-types/server/impl/query_impl.d.ts.map +1 -1
  112. package/dist/esm-types/server/impl/registration_impl.d.ts.map +1 -1
  113. package/dist/esm-types/server/meta.d.ts.map +1 -1
  114. package/dist/esm-types/server/query.d.ts.map +1 -1
  115. package/dist/esm-types/server/registration.d.ts.map +1 -1
  116. package/dist/esm-types/server/schema.d.ts.map +1 -1
  117. package/dist/react.bundle.js +25 -7
  118. package/dist/react.bundle.js.map +2 -2
  119. package/package.json +2 -2
  120. package/src/browser/simple_client.ts +12 -10
  121. package/src/browser/sync/authentication_manager.ts +42 -14
  122. package/src/browser/sync/client.ts +17 -0
  123. package/src/cli/dev.test.ts +157 -0
  124. package/src/cli/dev.ts +7 -0
  125. package/src/cli/generatedApi.ts +0 -1
  126. package/src/cli/lib/api.ts +0 -1
  127. package/src/cli/lib/config.test.ts +1 -4
  128. package/src/cli/lib/typecheck.ts +26 -10
  129. package/src/cli/lib/utils/utils.ts +4 -12
  130. package/src/index.ts +1 -1
  131. package/src/react/ConvexAuthState.test.tsx +145 -0
  132. package/src/react/client.ts +23 -6
  133. package/src/react/client_provided_base_client.test.tsx +56 -0
  134. package/src/react-clerk/ConvexProviderWithClerk.tsx +3 -1
  135. package/src/server/database.ts +6 -4
  136. package/src/server/impl/meta_impl.ts +2 -2
  137. package/src/server/impl/query_impl.ts +1 -3
  138. package/src/server/impl/registration_impl.ts +0 -2
  139. package/src/server/meta.ts +10 -0
  140. package/src/server/query.ts +9 -6
  141. package/src/server/registration.ts +10 -10
  142. package/src/server/schema.ts +3 -2
@@ -25,8 +25,8 @@ __export(simple_client_exports, {
25
25
  });
26
26
  module.exports = __toCommonJS(simple_client_exports);
27
27
  var import_common = require("../common/index.js");
28
- var import_index = require("./index.js");
29
28
  var import_api = require("../server/api.js");
29
+ var import_client = require("./sync/client.js");
30
30
  var import_paginated_query_client = require("./sync/paginated_query_client.js");
31
31
  var import_udf_path_utils = require("./sync/udf_path_utils.js");
32
32
  let defaultWebSocketConstructor;
@@ -60,7 +60,7 @@ class ConvexClient {
60
60
  baseOptions.unsavedChangesWarning = false;
61
61
  }
62
62
  if (!this.disabled) {
63
- this._client = new import_index.BaseConvexClient(
63
+ this._client = new import_client.BaseConvexClient(
64
64
  address,
65
65
  () => {
66
66
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/browser/simple_client.ts"],
4
- "sourcesContent": ["import { validateDeploymentUrl } from \"../common/index.js\";\nimport {\n BaseConvexClient,\n BaseConvexClientOptions,\n MutationOptions,\n PaginatedQueryToken,\n QueryToken,\n UserIdentityAttributes,\n} from \"./index.js\";\nimport {\n FunctionArgs,\n FunctionReference,\n FunctionReturnType,\n PaginationResult,\n} from \"../server/index.js\";\nimport { getFunctionName } from \"../server/api.js\";\nimport { AuthTokenFetcher } from \"./sync/authentication_manager.js\";\nimport { ConnectionState } from \"./sync/client.js\";\nimport {\n ExtendedTransition,\n PaginatedQueryClient,\n} from \"./sync/paginated_query_client.js\";\nimport { PaginatedQueryResult } from \"./sync/pagination.js\";\nimport { serializedQueryTokenIsPaginated } from \"./sync/udf_path_utils.js\";\n\n// In Node.js builds this points to a bundled WebSocket implementation. If no\n// WebSocket implementation is manually specified or globally available,\n// this one is used.\nlet defaultWebSocketConstructor: typeof WebSocket | undefined;\n\n/** internal */\nexport function setDefaultWebSocketConstructor(ws: typeof WebSocket) {\n defaultWebSocketConstructor = ws;\n}\n\nexport type ConvexClientOptions = BaseConvexClientOptions & {\n /**\n * `disabled` makes onUpdate callback registration a no-op and actions,\n * mutations and one-shot queries throw. Setting disabled to true may be\n * useful for server-side rendering, where subscriptions don't make sense.\n */\n disabled?: boolean;\n /**\n * Whether to prompt users in browsers about queued or in-flight mutations.\n * This only works in environments where `window.onbeforeunload` is available.\n *\n * Defaults to true when `window` is defined, otherwise false.\n */\n unsavedChangesWarning?: boolean;\n};\n\n/**\n * Stops callbacks from running.\n *\n * @public\n */\nexport type Unsubscribe<T> = {\n /** Stop calling callback when query results changes. If this is the last listener on this query, stop received updates. */\n (): void;\n /** Stop calling callback when query results changes. If this is the last listener on this query, stop received updates. */\n unsubscribe(): void;\n /** Get the last known value, possibly with local optimistic updates applied. */\n getCurrentValue(): T | undefined;\n /** @internal */\n getQueryLogs(): string[] | undefined;\n};\n\n/**\n * Subscribes to Convex query functions and executes mutations and actions over a WebSocket.\n *\n * Optimistic updates for mutations are not provided for this client.\n * Third party clients may choose to wrap {@link browser.BaseConvexClient} for additional control.\n *\n * ```ts\n * const client = new ConvexClient(\"https://happy-otter-123.convex.cloud\");\n * const unsubscribe = client.onUpdate(api.messages.list, {}, (messages) => {\n * console.log(messages[0].body);\n * });\n * ```\n *\n * @public\n */\nexport class ConvexClient {\n private listeners: Set<QueryInfo>;\n private _client: BaseConvexClient | undefined;\n private _paginatedClient: PaginatedQueryClient | undefined;\n // A synthetic server event to run callbacks the first time\n private callNewListenersWithCurrentValuesTimer:\n | ReturnType<typeof setTimeout>\n | undefined;\n private _closed: boolean;\n private _disabled: boolean;\n /**\n * Once closed no registered callbacks will fire again.\n */\n get closed(): boolean {\n return this._closed;\n }\n get client(): BaseConvexClient {\n if (this._client) return this._client;\n throw new Error(\"ConvexClient is disabled\");\n }\n /**\n * @internal\n */\n get paginatedClient(): PaginatedQueryClient {\n if (this._paginatedClient) return this._paginatedClient;\n throw new Error(\"ConvexClient is disabled\");\n }\n get disabled(): boolean {\n return this._disabled;\n }\n\n /**\n * Construct a client and immediately initiate a WebSocket connection to the passed address.\n *\n * @public\n */\n constructor(address: string, options: ConvexClientOptions = {}) {\n if (options.skipConvexDeploymentUrlCheck !== true) {\n validateDeploymentUrl(address);\n }\n const { disabled, ...baseOptions } = options;\n this._closed = false;\n this._disabled = !!disabled;\n if (\n defaultWebSocketConstructor &&\n !(\"webSocketConstructor\" in baseOptions) &&\n typeof WebSocket === \"undefined\"\n ) {\n baseOptions.webSocketConstructor = defaultWebSocketConstructor;\n }\n if (\n typeof window === \"undefined\" &&\n !(\"unsavedChangesWarning\" in baseOptions)\n ) {\n baseOptions.unsavedChangesWarning = false;\n }\n if (!this.disabled) {\n this._client = new BaseConvexClient(\n address,\n () => {}, // NOP, let the paginated query client do it all\n baseOptions,\n );\n this._paginatedClient = new PaginatedQueryClient(\n this._client,\n (transition) => this._transition(transition),\n );\n }\n this.listeners = new Set();\n }\n\n /**\n * Call a callback whenever a new result for a query is received. The callback\n * will run soon after being registered if a result for the query is already\n * in memory.\n *\n * The return value is an {@link Unsubscribe} object which is both a function\n * an an object with properties. Both of the patterns below work with this object:\n *\n *```ts\n * // call the return value as a function\n * const unsubscribe = client.onUpdate(api.messages.list, {}, (messages) => {\n * console.log(messages);\n * });\n * unsubscribe();\n *\n * // unpack the return value into its properties\n * const {\n * getCurrentValue,\n * unsubscribe,\n * } = client.onUpdate(api.messages.list, {}, (messages) => {\n * console.log(messages);\n * });\n *```\n *\n * @param query - A {@link server.FunctionReference} for the public query to run.\n * @param args - The arguments to run the query with.\n * @param callback - Function to call when the query result updates.\n * @param onError - Function to call when the query result updates with an error.\n * If not provided, errors will be thrown instead of calling the callback.\n *\n * @return an {@link Unsubscribe} function to stop calling the onUpdate function.\n */\n onUpdate<Query extends FunctionReference<\"query\">>(\n query: Query,\n args: FunctionArgs<Query>,\n callback: (result: FunctionReturnType<Query>) => unknown,\n onError?: (e: Error) => unknown,\n ): Unsubscribe<Query[\"_returnType\"]> {\n if (this.disabled) {\n return this.createDisabledUnsubscribe<Query[\"_returnType\"]>();\n }\n\n // BaseConvexClient takes care of deduplicating queries subscriptions...\n const { queryToken, unsubscribe } = this.client.subscribe(\n getFunctionName(query),\n args,\n );\n\n // ...but we still need to bookkeep callbacks to actually call them.\n const queryInfo: QueryInfo = {\n queryToken,\n callback,\n onError,\n unsubscribe,\n hasEverRun: false,\n query,\n args,\n paginationOptions: undefined,\n };\n this.listeners.add(queryInfo);\n\n // If the callback is registered for a query with a result immediately available\n // schedule a fake transition to call the callback soon instead of waiting for\n // a new server update (which could take seconds or days).\n if (\n this.queryResultReady(queryToken) &&\n this.callNewListenersWithCurrentValuesTimer === undefined\n ) {\n this.callNewListenersWithCurrentValuesTimer = setTimeout(\n () => this.callNewListenersWithCurrentValues(),\n 0,\n );\n }\n\n const unsubscribeProps: RemoveCallSignature<\n Unsubscribe<Query[\"_returnType\"]>\n > = {\n unsubscribe: () => {\n if (this.closed) {\n // all unsubscribes already ran\n return;\n }\n this.listeners.delete(queryInfo);\n unsubscribe();\n },\n getCurrentValue: () => this.client.localQueryResultByToken(queryToken),\n getQueryLogs: () => this.client.localQueryLogs(queryToken),\n };\n const ret = unsubscribeProps.unsubscribe as Unsubscribe<\n Query[\"_returnType\"]\n >;\n Object.assign(ret, unsubscribeProps);\n return ret;\n }\n\n /**\n * Call a callback whenever a new result for a paginated query is received.\n *\n * This is an experimental preview: the final API may change.\n * In particular, caching behavior, page splitting, and required paginated query options\n * may change.\n *\n * @param query - A {@link server.FunctionReference} for the public query to run.\n * @param args - The arguments to run the query with.\n * @param options - Options for the paginated query including initialNumItems and id.\n * @param callback - Function to call when the query result updates.\n * @param onError - Function to call when the query result updates with an error.\n *\n * @return an {@link Unsubscribe} function to stop calling the callback.\n */\n onPaginatedUpdate_experimental<Query extends FunctionReference<\"query\">>(\n query: Query,\n args: FunctionArgs<Query>,\n options: { initialNumItems: number },\n callback: (result: PaginationResult<FunctionReturnType<Query>>) => unknown,\n onError?: (e: Error) => unknown,\n ): Unsubscribe<PaginatedQueryResult<FunctionReturnType<Query>[]>> {\n if (this.disabled) {\n return this.createDisabledUnsubscribe<\n PaginatedQueryResult<FunctionReturnType<Query>>\n >();\n }\n\n const paginationOptions = {\n initialNumItems: options.initialNumItems,\n id: -1,\n };\n\n const { paginatedQueryToken, unsubscribe } = this.paginatedClient.subscribe(\n getFunctionName(query),\n args,\n // Simple client doesn't use IDs, there's no expectation that these queries remain separate.\n paginationOptions,\n );\n\n const queryInfo: QueryInfo = {\n queryToken: paginatedQueryToken,\n callback,\n onError,\n unsubscribe,\n hasEverRun: false,\n query,\n args,\n paginationOptions,\n };\n this.listeners.add(queryInfo);\n\n // If the callback is registered for a query with a result immediately available\n // schedule a fake transition to call the callback soon instead of waiting for\n // a new server update (which could take seconds or days).\n if (\n !!this.paginatedClient.localQueryResultByToken(paginatedQueryToken) &&\n this.callNewListenersWithCurrentValuesTimer === undefined\n ) {\n this.callNewListenersWithCurrentValuesTimer = setTimeout(\n () => this.callNewListenersWithCurrentValues(),\n 0,\n );\n }\n\n const unsubscribeProps: RemoveCallSignature<\n Unsubscribe<PaginatedQueryResult<FunctionReturnType<Query>[]>>\n > = {\n unsubscribe: () => {\n if (this.closed) {\n // all unsubscribes already ran\n return;\n }\n this.listeners.delete(queryInfo);\n unsubscribe();\n },\n getCurrentValue: () => {\n const result = this.paginatedClient.localQueryResult(\n getFunctionName(query),\n args,\n paginationOptions,\n );\n // cast to apply the specific function type\n return result as\n | PaginatedQueryResult<FunctionReturnType<Query>>\n | undefined;\n },\n getQueryLogs: () => [], // Paginated queries don't aggregate their logs\n };\n const ret = unsubscribeProps.unsubscribe as Unsubscribe<\n PaginatedQueryResult<FunctionReturnType<Query>>\n >;\n Object.assign(ret, unsubscribeProps);\n return ret;\n }\n\n // Run all callbacks that have never been run before if they have a query\n // result available now.\n private callNewListenersWithCurrentValues() {\n this.callNewListenersWithCurrentValuesTimer = undefined;\n this._transition({ queries: [], paginatedQueries: [] }, true);\n }\n\n private queryResultReady(queryToken: QueryToken): boolean {\n return this.client.hasLocalQueryResultByToken(queryToken);\n }\n\n private createDisabledUnsubscribe<T>(): Unsubscribe<T> {\n const disabledUnsubscribe = (() => {}) as Unsubscribe<T>;\n const unsubscribeProps: RemoveCallSignature<Unsubscribe<T>> = {\n unsubscribe: disabledUnsubscribe,\n getCurrentValue: () => undefined,\n getQueryLogs: () => undefined,\n };\n Object.assign(disabledUnsubscribe, unsubscribeProps);\n return disabledUnsubscribe;\n }\n\n async close() {\n if (this.disabled) return;\n // prevent pending updates\n this.listeners.clear();\n this._closed = true;\n if (this._paginatedClient) {\n this._paginatedClient = undefined;\n }\n return this.client.close();\n }\n\n /**\n * Get the current JWT auth token and decoded claims.\n */\n getAuth(): { token: string; decoded: Record<string, any> } | undefined {\n if (this.disabled) return;\n return this.client.getCurrentAuthClaims();\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 (typically an 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 if (this.disabled) return;\n this.client.setAuth(\n fetchToken,\n onChange ??\n (() => {\n // Do nothing\n }),\n );\n }\n\n /**\n * @internal\n */\n setAdminAuth(token: string, identity?: UserIdentityAttributes) {\n if (this.closed) {\n throw new Error(\"ConvexClient has already been closed.\");\n }\n if (this.disabled) return;\n this.client.setAdminAuth(token, identity);\n }\n\n /**\n * @internal\n */\n _transition(\n {\n queries,\n paginatedQueries,\n }: Pick<ExtendedTransition, \"queries\" | \"paginatedQueries\">,\n callNewListeners = false,\n ) {\n const updatedQueries = [\n ...queries.map((q) => q.token),\n ...paginatedQueries.map((q) => q.token),\n ];\n\n // Deduping subscriptions happens in the BaseConvexClient, so not much to do here.\n\n // Call all callbacks in the order they were registered\n for (const queryInfo of this.listeners) {\n const { callback, queryToken, onError, hasEverRun } = queryInfo;\n const isPaginatedQuery = serializedQueryTokenIsPaginated(queryToken);\n\n // What does it mean to have a paginated query result ready? I think it's\n // always going to fire immediately.\n const hasResultReady = isPaginatedQuery\n ? !!this.paginatedClient.localQueryResultByToken(queryToken)\n : this.client.hasLocalQueryResultByToken(queryToken);\n\n if (\n updatedQueries.includes(queryToken) ||\n (callNewListeners && !hasEverRun && hasResultReady)\n ) {\n queryInfo.hasEverRun = true;\n let newValue;\n try {\n if (isPaginatedQuery) {\n newValue = this.paginatedClient.localQueryResultByToken(queryToken);\n } else {\n newValue = this.client.localQueryResultByToken(queryToken);\n }\n } catch (error) {\n if (!(error instanceof Error)) throw error;\n if (onError) {\n onError(\n error,\n \"Second argument to onUpdate onError is reserved for later use\",\n );\n } else {\n // Make some noise without unsubscribing or failing to call other callbacks.\n void Promise.reject(error);\n }\n continue;\n }\n callback(\n newValue,\n \"Second argument to onUpdate callback is reserved for later use\",\n );\n }\n }\n }\n\n /**\n * Execute a mutation function.\n *\n * @param mutation - A {@link server.FunctionReference} for the public mutation\n * to run.\n * @param args - An arguments object for the mutation.\n * @param options - A {@link MutationOptions} options object for the mutation.\n * @returns A promise of the mutation's result.\n */\n async mutation<Mutation extends FunctionReference<\"mutation\">>(\n mutation: Mutation,\n args: FunctionArgs<Mutation>,\n options?: MutationOptions,\n ): Promise<Awaited<FunctionReturnType<Mutation>>> {\n if (this.disabled) throw new Error(\"ConvexClient is disabled\");\n return await this.client.mutation(getFunctionName(mutation), args, options);\n }\n\n /**\n * Execute an action function.\n *\n * @param action - A {@link server.FunctionReference} for the public action\n * to run.\n * @param args - An arguments object for the action.\n * @returns A promise of the action's result.\n */\n async action<Action extends FunctionReference<\"action\">>(\n action: Action,\n args: FunctionArgs<Action>,\n ): Promise<Awaited<FunctionReturnType<Action>>> {\n if (this.disabled) throw new Error(\"ConvexClient is disabled\");\n return await this.client.action(getFunctionName(action), args);\n }\n\n /**\n * Fetch a query result once.\n *\n * @param query - A {@link server.FunctionReference} for the public query\n * to run.\n * @param args - An arguments object for the query.\n * @returns A promise of the query's result.\n */\n async query<Query extends FunctionReference<\"query\">>(\n query: Query,\n args: Query[\"_args\"],\n ): Promise<Awaited<Query[\"_returnType\"]>> {\n if (this.disabled) throw new Error(\"ConvexClient is disabled\");\n const value = this.client.localQueryResult(getFunctionName(query), args) as\n | Awaited<Query[\"_returnType\"]>\n | undefined;\n if (value !== undefined) return Promise.resolve(value);\n\n return new Promise((resolve, reject) => {\n const { unsubscribe } = this.onUpdate(\n query,\n args,\n (value) => {\n unsubscribe();\n resolve(value);\n },\n (e: Error) => {\n unsubscribe();\n reject(e);\n },\n );\n });\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 if (this.disabled) throw new Error(\"ConvexClient is disabled\");\n return this.client.connectionState();\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 if (this.disabled) return () => {};\n return this.client.subscribeToConnectionState(cb);\n }\n}\n\n// internal information tracked about each registered callback\ntype QueryInfo = {\n callback: (result: any, meta: unknown) => unknown;\n onError: ((e: Error, meta: unknown) => unknown) | undefined;\n unsubscribe: () => void;\n queryToken: QueryToken | PaginatedQueryToken;\n hasEverRun: boolean;\n // query, args and paginationOptions are just here for debugging, the queryToken is authoritative\n query: FunctionReference<\"query\">;\n args: any;\n paginationOptions: { initialNumItems: number; id: number } | undefined;\n};\n\n// helps to construct objects with a call signature\ntype RemoveCallSignature<T> = Omit<T, never>;\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { validateDeploymentUrl } from \"../common/index.js\";\nimport {\n FunctionArgs,\n FunctionReference,\n FunctionReturnType,\n PaginationResult,\n} from \"../server/index.js\";\nimport { getFunctionName } from \"../server/api.js\";\nimport { AuthTokenFetcher } from \"./sync/authentication_manager.js\";\nimport {\n BaseConvexClient,\n BaseConvexClientOptions,\n ConnectionState,\n MutationOptions,\n} from \"./sync/client.js\";\nimport {\n ExtendedTransition,\n PaginatedQueryClient,\n} from \"./sync/paginated_query_client.js\";\nimport { PaginatedQueryResult } from \"./sync/pagination.js\";\nimport { UserIdentityAttributes } from \"./sync/protocol.js\";\nimport {\n PaginatedQueryToken,\n QueryToken,\n serializedQueryTokenIsPaginated,\n} from \"./sync/udf_path_utils.js\";\n\n// In Node.js builds this points to a bundled WebSocket implementation. If no\n// WebSocket implementation is manually specified or globally available,\n// this one is used.\nlet defaultWebSocketConstructor: typeof WebSocket | undefined;\n\n/** internal */\nexport function setDefaultWebSocketConstructor(ws: typeof WebSocket) {\n defaultWebSocketConstructor = ws;\n}\n\nexport type ConvexClientOptions = BaseConvexClientOptions & {\n /**\n * `disabled` makes onUpdate callback registration a no-op and actions,\n * mutations and one-shot queries throw. Setting disabled to true may be\n * useful for server-side rendering, where subscriptions don't make sense.\n */\n disabled?: boolean;\n /**\n * Whether to prompt users in browsers about queued or in-flight mutations.\n * This only works in environments where `window.onbeforeunload` is available.\n *\n * Defaults to true when `window` is defined, otherwise false.\n */\n unsavedChangesWarning?: boolean;\n};\n\n/**\n * Stops callbacks from running.\n *\n * @public\n */\nexport type Unsubscribe<T> = {\n /** Stop calling callback when query results changes. If this is the last listener on this query, stop received updates. */\n (): void;\n /** Stop calling callback when query results changes. If this is the last listener on this query, stop received updates. */\n unsubscribe(): void;\n /** Get the last known value, possibly with local optimistic updates applied. */\n getCurrentValue(): T | undefined;\n /** @internal */\n getQueryLogs(): string[] | undefined;\n};\n\n/**\n * Subscribes to Convex query functions and executes mutations and actions over a WebSocket.\n *\n * Optimistic updates for mutations are not provided for this client.\n * Third party clients may choose to wrap {@link browser.BaseConvexClient} for additional control.\n *\n * ```ts\n * const client = new ConvexClient(\"https://happy-otter-123.convex.cloud\");\n * const unsubscribe = client.onUpdate(api.messages.list, {}, (messages) => {\n * console.log(messages[0].body);\n * });\n * ```\n *\n * @public\n */\nexport class ConvexClient {\n private listeners: Set<QueryInfo>;\n private _client: BaseConvexClient | undefined;\n private _paginatedClient: PaginatedQueryClient | undefined;\n // A synthetic server event to run callbacks the first time\n private callNewListenersWithCurrentValuesTimer:\n | ReturnType<typeof setTimeout>\n | undefined;\n private _closed: boolean;\n private _disabled: boolean;\n /**\n * Once closed no registered callbacks will fire again.\n */\n get closed(): boolean {\n return this._closed;\n }\n get client(): BaseConvexClient {\n if (this._client) return this._client;\n throw new Error(\"ConvexClient is disabled\");\n }\n /**\n * @internal\n */\n get paginatedClient(): PaginatedQueryClient {\n if (this._paginatedClient) return this._paginatedClient;\n throw new Error(\"ConvexClient is disabled\");\n }\n get disabled(): boolean {\n return this._disabled;\n }\n\n /**\n * Construct a client and immediately initiate a WebSocket connection to the passed address.\n *\n * @public\n */\n constructor(address: string, options: ConvexClientOptions = {}) {\n if (options.skipConvexDeploymentUrlCheck !== true) {\n validateDeploymentUrl(address);\n }\n const { disabled, ...baseOptions } = options;\n this._closed = false;\n this._disabled = !!disabled;\n if (\n defaultWebSocketConstructor &&\n !(\"webSocketConstructor\" in baseOptions) &&\n typeof WebSocket === \"undefined\"\n ) {\n baseOptions.webSocketConstructor = defaultWebSocketConstructor;\n }\n if (\n typeof window === \"undefined\" &&\n !(\"unsavedChangesWarning\" in baseOptions)\n ) {\n baseOptions.unsavedChangesWarning = false;\n }\n if (!this.disabled) {\n this._client = new BaseConvexClient(\n address,\n () => {}, // NOP, let the paginated query client do it all\n baseOptions,\n );\n this._paginatedClient = new PaginatedQueryClient(\n this._client,\n (transition) => this._transition(transition),\n );\n }\n this.listeners = new Set();\n }\n\n /**\n * Call a callback whenever a new result for a query is received. The callback\n * will run soon after being registered if a result for the query is already\n * in memory.\n *\n * The return value is an {@link Unsubscribe} object which is both a function\n * an an object with properties. Both of the patterns below work with this object:\n *\n *```ts\n * // call the return value as a function\n * const unsubscribe = client.onUpdate(api.messages.list, {}, (messages) => {\n * console.log(messages);\n * });\n * unsubscribe();\n *\n * // unpack the return value into its properties\n * const {\n * getCurrentValue,\n * unsubscribe,\n * } = client.onUpdate(api.messages.list, {}, (messages) => {\n * console.log(messages);\n * });\n *```\n *\n * @param query - A {@link server.FunctionReference} for the public query to run.\n * @param args - The arguments to run the query with.\n * @param callback - Function to call when the query result updates.\n * @param onError - Function to call when the query result updates with an error.\n * If not provided, errors will be thrown instead of calling the callback.\n *\n * @return an {@link Unsubscribe} function to stop calling the onUpdate function.\n */\n onUpdate<Query extends FunctionReference<\"query\">>(\n query: Query,\n args: FunctionArgs<Query>,\n callback: (result: FunctionReturnType<Query>) => unknown,\n onError?: (e: Error) => unknown,\n ): Unsubscribe<Query[\"_returnType\"]> {\n if (this.disabled) {\n return this.createDisabledUnsubscribe<Query[\"_returnType\"]>();\n }\n\n // BaseConvexClient takes care of deduplicating queries subscriptions...\n const { queryToken, unsubscribe } = this.client.subscribe(\n getFunctionName(query),\n args,\n );\n\n // ...but we still need to bookkeep callbacks to actually call them.\n const queryInfo: QueryInfo = {\n queryToken,\n callback,\n onError,\n unsubscribe,\n hasEverRun: false,\n query,\n args,\n paginationOptions: undefined,\n };\n this.listeners.add(queryInfo);\n\n // If the callback is registered for a query with a result immediately available\n // schedule a fake transition to call the callback soon instead of waiting for\n // a new server update (which could take seconds or days).\n if (\n this.queryResultReady(queryToken) &&\n this.callNewListenersWithCurrentValuesTimer === undefined\n ) {\n this.callNewListenersWithCurrentValuesTimer = setTimeout(\n () => this.callNewListenersWithCurrentValues(),\n 0,\n );\n }\n\n const unsubscribeProps: RemoveCallSignature<\n Unsubscribe<Query[\"_returnType\"]>\n > = {\n unsubscribe: () => {\n if (this.closed) {\n // all unsubscribes already ran\n return;\n }\n this.listeners.delete(queryInfo);\n unsubscribe();\n },\n getCurrentValue: () => this.client.localQueryResultByToken(queryToken),\n getQueryLogs: () => this.client.localQueryLogs(queryToken),\n };\n const ret = unsubscribeProps.unsubscribe as Unsubscribe<\n Query[\"_returnType\"]\n >;\n Object.assign(ret, unsubscribeProps);\n return ret;\n }\n\n /**\n * Call a callback whenever a new result for a paginated query is received.\n *\n * This is an experimental preview: the final API may change.\n * In particular, caching behavior, page splitting, and required paginated query options\n * may change.\n *\n * @param query - A {@link server.FunctionReference} for the public query to run.\n * @param args - The arguments to run the query with.\n * @param options - Options for the paginated query including initialNumItems and id.\n * @param callback - Function to call when the query result updates.\n * @param onError - Function to call when the query result updates with an error.\n *\n * @return an {@link Unsubscribe} function to stop calling the callback.\n */\n onPaginatedUpdate_experimental<Query extends FunctionReference<\"query\">>(\n query: Query,\n args: FunctionArgs<Query>,\n options: { initialNumItems: number },\n callback: (result: PaginationResult<FunctionReturnType<Query>>) => unknown,\n onError?: (e: Error) => unknown,\n ): Unsubscribe<PaginatedQueryResult<FunctionReturnType<Query>[]>> {\n if (this.disabled) {\n return this.createDisabledUnsubscribe<\n PaginatedQueryResult<FunctionReturnType<Query>>\n >();\n }\n\n const paginationOptions = {\n initialNumItems: options.initialNumItems,\n id: -1,\n };\n\n const { paginatedQueryToken, unsubscribe } = this.paginatedClient.subscribe(\n getFunctionName(query),\n args,\n // Simple client doesn't use IDs, there's no expectation that these queries remain separate.\n paginationOptions,\n );\n\n const queryInfo: QueryInfo = {\n queryToken: paginatedQueryToken,\n callback,\n onError,\n unsubscribe,\n hasEverRun: false,\n query,\n args,\n paginationOptions,\n };\n this.listeners.add(queryInfo);\n\n // If the callback is registered for a query with a result immediately available\n // schedule a fake transition to call the callback soon instead of waiting for\n // a new server update (which could take seconds or days).\n if (\n !!this.paginatedClient.localQueryResultByToken(paginatedQueryToken) &&\n this.callNewListenersWithCurrentValuesTimer === undefined\n ) {\n this.callNewListenersWithCurrentValuesTimer = setTimeout(\n () => this.callNewListenersWithCurrentValues(),\n 0,\n );\n }\n\n const unsubscribeProps: RemoveCallSignature<\n Unsubscribe<PaginatedQueryResult<FunctionReturnType<Query>[]>>\n > = {\n unsubscribe: () => {\n if (this.closed) {\n // all unsubscribes already ran\n return;\n }\n this.listeners.delete(queryInfo);\n unsubscribe();\n },\n getCurrentValue: () => {\n const result = this.paginatedClient.localQueryResult(\n getFunctionName(query),\n args,\n paginationOptions,\n );\n // cast to apply the specific function type\n return result as\n | PaginatedQueryResult<FunctionReturnType<Query>>\n | undefined;\n },\n getQueryLogs: () => [], // Paginated queries don't aggregate their logs\n };\n const ret = unsubscribeProps.unsubscribe as Unsubscribe<\n PaginatedQueryResult<FunctionReturnType<Query>>\n >;\n Object.assign(ret, unsubscribeProps);\n return ret;\n }\n\n // Run all callbacks that have never been run before if they have a query\n // result available now.\n private callNewListenersWithCurrentValues() {\n this.callNewListenersWithCurrentValuesTimer = undefined;\n this._transition({ queries: [], paginatedQueries: [] }, true);\n }\n\n private queryResultReady(queryToken: QueryToken): boolean {\n return this.client.hasLocalQueryResultByToken(queryToken);\n }\n\n private createDisabledUnsubscribe<T>(): Unsubscribe<T> {\n const disabledUnsubscribe = (() => {}) as Unsubscribe<T>;\n const unsubscribeProps: RemoveCallSignature<Unsubscribe<T>> = {\n unsubscribe: disabledUnsubscribe,\n getCurrentValue: () => undefined,\n getQueryLogs: () => undefined,\n };\n Object.assign(disabledUnsubscribe, unsubscribeProps);\n return disabledUnsubscribe;\n }\n\n async close() {\n if (this.disabled) return;\n // prevent pending updates\n this.listeners.clear();\n this._closed = true;\n if (this._paginatedClient) {\n this._paginatedClient = undefined;\n }\n return this.client.close();\n }\n\n /**\n * Get the current JWT auth token and decoded claims.\n */\n getAuth(): { token: string; decoded: Record<string, any> } | undefined {\n if (this.disabled) return;\n return this.client.getCurrentAuthClaims();\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 (typically an 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 if (this.disabled) return;\n this.client.setAuth(\n fetchToken,\n onChange ??\n (() => {\n // Do nothing\n }),\n );\n }\n\n /**\n * @internal\n */\n setAdminAuth(token: string, identity?: UserIdentityAttributes) {\n if (this.closed) {\n throw new Error(\"ConvexClient has already been closed.\");\n }\n if (this.disabled) return;\n this.client.setAdminAuth(token, identity);\n }\n\n /**\n * @internal\n */\n _transition(\n {\n queries,\n paginatedQueries,\n }: Pick<ExtendedTransition, \"queries\" | \"paginatedQueries\">,\n callNewListeners = false,\n ) {\n const updatedQueries = [\n ...queries.map((q) => q.token),\n ...paginatedQueries.map((q) => q.token),\n ];\n\n // Deduping subscriptions happens in the BaseConvexClient, so not much to do here.\n\n // Call all callbacks in the order they were registered\n for (const queryInfo of this.listeners) {\n const { callback, queryToken, onError, hasEverRun } = queryInfo;\n const isPaginatedQuery = serializedQueryTokenIsPaginated(queryToken);\n\n // What does it mean to have a paginated query result ready? I think it's\n // always going to fire immediately.\n const hasResultReady = isPaginatedQuery\n ? !!this.paginatedClient.localQueryResultByToken(queryToken)\n : this.client.hasLocalQueryResultByToken(queryToken);\n\n if (\n updatedQueries.includes(queryToken) ||\n (callNewListeners && !hasEverRun && hasResultReady)\n ) {\n queryInfo.hasEverRun = true;\n let newValue;\n try {\n if (isPaginatedQuery) {\n newValue = this.paginatedClient.localQueryResultByToken(queryToken);\n } else {\n newValue = this.client.localQueryResultByToken(queryToken);\n }\n } catch (error) {\n if (!(error instanceof Error)) throw error;\n if (onError) {\n onError(\n error,\n \"Second argument to onUpdate onError is reserved for later use\",\n );\n } else {\n // Make some noise without unsubscribing or failing to call other callbacks.\n void Promise.reject(error);\n }\n continue;\n }\n callback(\n newValue,\n \"Second argument to onUpdate callback is reserved for later use\",\n );\n }\n }\n }\n\n /**\n * Execute a mutation function.\n *\n * @param mutation - A {@link server.FunctionReference} for the public mutation\n * to run.\n * @param args - An arguments object for the mutation.\n * @param options - A {@link MutationOptions} options object for the mutation.\n * @returns A promise of the mutation's result.\n */\n async mutation<Mutation extends FunctionReference<\"mutation\">>(\n mutation: Mutation,\n args: FunctionArgs<Mutation>,\n options?: MutationOptions,\n ): Promise<Awaited<FunctionReturnType<Mutation>>> {\n if (this.disabled) throw new Error(\"ConvexClient is disabled\");\n return await this.client.mutation(getFunctionName(mutation), args, options);\n }\n\n /**\n * Execute an action function.\n *\n * @param action - A {@link server.FunctionReference} for the public action\n * to run.\n * @param args - An arguments object for the action.\n * @returns A promise of the action's result.\n */\n async action<Action extends FunctionReference<\"action\">>(\n action: Action,\n args: FunctionArgs<Action>,\n ): Promise<Awaited<FunctionReturnType<Action>>> {\n if (this.disabled) throw new Error(\"ConvexClient is disabled\");\n return await this.client.action(getFunctionName(action), args);\n }\n\n /**\n * Fetch a query result once.\n *\n * @param query - A {@link server.FunctionReference} for the public query\n * to run.\n * @param args - An arguments object for the query.\n * @returns A promise of the query's result.\n */\n async query<Query extends FunctionReference<\"query\">>(\n query: Query,\n args: Query[\"_args\"],\n ): Promise<Awaited<Query[\"_returnType\"]>> {\n if (this.disabled) throw new Error(\"ConvexClient is disabled\");\n const value = this.client.localQueryResult(getFunctionName(query), args) as\n | Awaited<Query[\"_returnType\"]>\n | undefined;\n if (value !== undefined) return Promise.resolve(value);\n\n return new Promise((resolve, reject) => {\n const { unsubscribe } = this.onUpdate(\n query,\n args,\n (value) => {\n unsubscribe();\n resolve(value);\n },\n (e: Error) => {\n unsubscribe();\n reject(e);\n },\n );\n });\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 if (this.disabled) throw new Error(\"ConvexClient is disabled\");\n return this.client.connectionState();\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 if (this.disabled) return () => {};\n return this.client.subscribeToConnectionState(cb);\n }\n}\n\n// internal information tracked about each registered callback\ntype QueryInfo = {\n callback: (result: any, meta: unknown) => unknown;\n onError: ((e: Error, meta: unknown) => unknown) | undefined;\n unsubscribe: () => void;\n queryToken: QueryToken | PaginatedQueryToken;\n hasEverRun: boolean;\n // query, args and paginationOptions are just here for debugging, the queryToken is authoritative\n query: FunctionReference<\"query\">;\n args: any;\n paginationOptions: { initialNumItems: number; id: number } | undefined;\n};\n\n// helps to construct objects with a call signature\ntype RemoveCallSignature<T> = Omit<T, never>;\n"],
5
+ "mappings": "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",
6
6
  "names": ["value"]
7
7
  }
@@ -44,6 +44,7 @@ class AuthenticationManager {
44
44
  __publicField(this, "clearAuth");
45
45
  __publicField(this, "logger");
46
46
  __publicField(this, "refreshTokenLeewaySeconds");
47
+ __publicField(this, "initialAuthTokenReuse");
47
48
  // Track last value to avoid redundant calls
48
49
  __publicField(this, "lastRefreshChange");
49
50
  // Number of times we have attempted to confirm the latest token. We retry up
@@ -58,6 +59,7 @@ class AuthenticationManager {
58
59
  this.clearAuth = callbacks.clearAuth;
59
60
  this.logger = config.logger;
60
61
  this.refreshTokenLeewaySeconds = config.refreshTokenLeewaySeconds;
62
+ this.initialAuthTokenReuse = config.initialAuthTokenReuse;
61
63
  this.lastRefreshChange = false;
62
64
  }
63
65
  notifyRefreshChange(isRefreshing) {
@@ -113,7 +115,12 @@ class AuthenticationManager {
113
115
  this.syncState.markAuthCompletion();
114
116
  if (this.authState.state === "waitingForServerConfirmationOfCachedToken") {
115
117
  this._logVerbose("server confirmed auth token is valid");
116
- void this.refetchToken();
118
+ const cachedToken = this.syncState.getAuth()?.value;
119
+ if (this.initialAuthTokenReuse && cachedToken) {
120
+ this.scheduleTokenRefetch(cachedToken, serverMessage.clientClockSkew);
121
+ } else {
122
+ void this.refetchToken();
123
+ }
117
124
  this.authState.config.onAuthChange(true);
118
125
  return;
119
126
  }
@@ -244,7 +251,7 @@ class AuthenticationManager {
244
251
  );
245
252
  this.tryRestartSocket();
246
253
  }
247
- scheduleTokenRefetch(token) {
254
+ scheduleTokenRefetch(token, clientClockSkewMs) {
248
255
  if (this.authState.state === "noAuth") {
249
256
  return;
250
257
  }
@@ -262,13 +269,23 @@ class AuthenticationManager {
262
269
  );
263
270
  return;
264
271
  }
265
- const tokenValiditySeconds = exp - iat;
266
- if (tokenValiditySeconds <= 2) {
272
+ const fullLifetimeSeconds = exp - iat;
273
+ if (fullLifetimeSeconds <= 2) {
267
274
  this.logger.error(
268
275
  "Auth token does not live long enough, cannot refetch the token"
269
276
  );
270
277
  return;
271
278
  }
279
+ let tokenValiditySeconds;
280
+ if (clientClockSkewMs !== void 0) {
281
+ const estimatedServerNowSeconds = (Date.now() - clientClockSkewMs) / 1e3;
282
+ tokenValiditySeconds = exp - estimatedServerNowSeconds;
283
+ if (tokenValiditySeconds <= 0) {
284
+ tokenValiditySeconds = 0;
285
+ }
286
+ } else {
287
+ tokenValiditySeconds = fullLifetimeSeconds;
288
+ }
272
289
  let delay = Math.min(
273
290
  MAXIMUM_REFRESH_DELAY,
274
291
  (tokenValiditySeconds - this.refreshTokenLeewaySeconds) * 1e3
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/browser/sync/authentication_manager.ts"],
4
- "sourcesContent": ["import { Logger } from \"../logging.js\";\nimport { LocalSyncState } from \"./local_state.js\";\nimport { AuthError, IdentityVersion, Transition } from \"./protocol.js\";\nimport { jwtDecode } from \"../../vendor/jwt-decode/index.js\";\n\n// setTimout uses 32 bit integer, so it can only\n// schedule about 24 days in the future.\nconst MAXIMUM_REFRESH_DELAY = 20 * 24 * 60 * 60 * 1000; // 20 days\n\nconst MAX_TOKEN_CONFIRMATION_ATTEMPTS = 2;\n\n/**\n * An async function returning a JWT. Depending on the auth providers\n * configured in convex/auth.config.ts, this may be a JWT-encoded OpenID\n * Connect Identity Token or a traditional JWT.\n *\n * `forceRefreshToken` is `true` if the server rejected a previously\n * returned token or the token is anticipated to expiring soon\n * based on its `exp` time.\n *\n * See {@link ConvexReactClient.setAuth}.\n *\n * @public\n */\nexport type AuthTokenFetcher = (args: {\n forceRefreshToken: boolean;\n}) => Promise<string | null | undefined>;\n\n/**\n * What is provided to the client.\n */\ntype AuthConfig = {\n fetchToken: AuthTokenFetcher;\n onAuthChange: (isAuthenticated: boolean) => void;\n onRefreshChange?: ((isRefreshing: boolean) => void) | undefined;\n};\n\n/**\n * In general we take 3 steps:\n * 1. Fetch a possibly cached token\n * 2. Immediately fetch a fresh token without using a cache\n * 3. Repeat step 2 before the end of the fresh token's lifetime\n *\n * When we fetch without using a cache we know when the token\n * will expire, and can schedule refetching it.\n *\n * If we get an error before a scheduled refetch, we go back\n * to step 2.\n */\ntype AuthState =\n | { state: \"noAuth\" }\n | {\n state: \"waitingForServerConfirmationOfCachedToken\";\n config: AuthConfig;\n hasRetried: boolean;\n }\n | {\n state: \"initialRefetch\";\n config: AuthConfig;\n }\n | {\n state: \"waitingForServerConfirmationOfFreshToken\";\n config: AuthConfig;\n hadAuth: boolean;\n token: string;\n }\n | {\n state: \"waitingForScheduledRefetch\";\n config: AuthConfig;\n refetchTokenTimeoutId: ReturnType<typeof setTimeout>;\n }\n // Special/weird state when we got a valid token\n // but could not fetch a new one.\n | {\n state: \"notRefetching\";\n config: AuthConfig;\n };\n\n/**\n * Handles the state transitions for auth. The server is the source\n * of truth.\n */\nexport class AuthenticationManager {\n private authState: AuthState = { state: \"noAuth\" };\n // Used to detect races involving `setConfig` calls\n // while a token is being fetched.\n private configVersion = 0;\n // Shared by the BaseClient so that the auth manager can easily inspect it\n private readonly syncState: LocalSyncState;\n // Passed down by BaseClient, sends a message to the server\n private readonly authenticate: (token: string) => IdentityVersion;\n private readonly stopSocket: () => Promise<void>;\n private readonly tryRestartSocket: () => void;\n private readonly pauseSocket: () => void;\n private readonly resumeSocket: () => void;\n // Passed down by BaseClient, sends a message to the server\n private readonly clearAuth: () => void;\n private readonly logger: Logger;\n private readonly refreshTokenLeewaySeconds: number;\n // Track last value to avoid redundant calls\n private lastRefreshChange: boolean;\n // Number of times we have attempted to confirm the latest token. We retry up\n // to `MAX_TOKEN_CONFIRMATION_ATTEMPTS` times.\n private tokenConfirmationAttempts = 0;\n constructor(\n syncState: LocalSyncState,\n callbacks: {\n authenticate: (token: string) => IdentityVersion;\n stopSocket: () => Promise<void>;\n tryRestartSocket: () => void;\n pauseSocket: () => void;\n resumeSocket: () => void;\n clearAuth: () => void;\n },\n config: {\n refreshTokenLeewaySeconds: number;\n logger: Logger;\n },\n ) {\n this.syncState = syncState;\n this.authenticate = callbacks.authenticate;\n this.stopSocket = callbacks.stopSocket;\n this.tryRestartSocket = callbacks.tryRestartSocket;\n this.pauseSocket = callbacks.pauseSocket;\n this.resumeSocket = callbacks.resumeSocket;\n this.clearAuth = callbacks.clearAuth;\n this.logger = config.logger;\n this.refreshTokenLeewaySeconds = config.refreshTokenLeewaySeconds;\n this.lastRefreshChange = false;\n }\n\n private notifyRefreshChange(isRefreshing: boolean) {\n if (\n this.authState.state !== \"noAuth\" &&\n this.authState.state !== \"initialRefetch\" &&\n this.authState.config.onRefreshChange &&\n this.lastRefreshChange !== isRefreshing\n ) {\n this.lastRefreshChange = isRefreshing;\n this.authState.config.onRefreshChange(isRefreshing);\n }\n }\n\n async setConfig(\n fetchToken: AuthTokenFetcher,\n onChange: (isAuthenticated: boolean) => void,\n onRefreshChange?: (isRefreshing: boolean) => void,\n ) {\n this.resetAuthState();\n this._logVerbose(\"pausing WS for auth token fetch\");\n this.pauseSocket();\n const token = await this.fetchTokenAndGuardAgainstRace(fetchToken, {\n forceRefreshToken: false,\n });\n if (token.isFromOutdatedConfig) {\n return;\n }\n const config: AuthConfig = {\n fetchToken,\n onAuthChange: onChange,\n onRefreshChange,\n };\n if (token.value) {\n this.setAuthState({\n state: \"waitingForServerConfirmationOfCachedToken\",\n config,\n hasRetried: false,\n });\n this.authenticate(token.value);\n } else {\n this.setAuthState({\n state: \"initialRefetch\",\n config,\n });\n // Try again with `forceRefreshToken: true`\n await this.refetchToken();\n }\n this._logVerbose(\"resuming WS after auth token fetch\");\n this.resumeSocket();\n }\n\n onTransition(serverMessage: Transition) {\n if (\n !this.syncState.isCurrentOrNewerAuthVersion(\n serverMessage.endVersion.identity,\n )\n ) {\n // This is a stale transition - client has moved on to\n // a newer auth version.\n return;\n }\n if (\n serverMessage.endVersion.identity <= serverMessage.startVersion.identity\n ) {\n // This transition did not change auth - it is not a response to Authenticate.\n return;\n }\n\n this._logVerbose(\n `auth state is ${this.authState.state} when handling transition`,\n );\n\n // This transition advanced the auth version, which means the token used was valid\n // and the client and server auth states are in sync.\n this.syncState.markAuthCompletion();\n\n if (this.authState.state === \"waitingForServerConfirmationOfCachedToken\") {\n this._logVerbose(\"server confirmed auth token is valid\");\n void this.refetchToken();\n this.authState.config.onAuthChange(true);\n return;\n }\n if (this.authState.state === \"waitingForServerConfirmationOfFreshToken\") {\n this._logVerbose(\"server confirmed new auth token is valid\");\n this.notifyRefreshChange(false);\n this.scheduleTokenRefetch(this.authState.token);\n this.tokenConfirmationAttempts = 0;\n if (!this.authState.hadAuth) {\n this.authState.config.onAuthChange(true);\n }\n }\n }\n\n onAuthError(serverMessage: AuthError) {\n // If the AuthError is not due to updating the token, and we're currently\n // waiting on the result of a token update, ignore.\n if (\n serverMessage.authUpdateAttempted === false &&\n (this.authState.state === \"waitingForServerConfirmationOfFreshToken\" ||\n this.authState.state === \"waitingForServerConfirmationOfCachedToken\")\n ) {\n this._logVerbose(\"ignoring non-auth token expired error\");\n return;\n }\n const { baseVersion } = serverMessage;\n // Versioned AuthErrors are ignored if the client advanced to\n // a newer auth identity\n // Error are reporting the previous version, since the server\n // didn't advance, hence `+ 1`.\n if (!this.syncState.isCurrentOrNewerAuthVersion(baseVersion + 1)) {\n this._logVerbose(\"ignoring auth error for previous auth attempt\");\n return;\n }\n void this.tryToReauthenticate(serverMessage);\n return;\n }\n\n // This is similar to `refetchToken` defined below, in fact we\n // don't represent them as different states, but it is different\n // in that we pause the WebSocket so that mutations\n // don't retry with bad auth.\n private async tryToReauthenticate(serverMessage: AuthError) {\n this._logVerbose(`attempting to reauthenticate: ${serverMessage.error}`);\n if (\n // No way to fetch another token, kaboom\n this.authState.state === \"noAuth\" ||\n // We failed on a fresh token. After a small number of retries, we give up\n // and clear the auth state to avoid infinite retries.\n (this.authState.state === \"waitingForServerConfirmationOfFreshToken\" &&\n this.tokenConfirmationAttempts >= MAX_TOKEN_CONFIRMATION_ATTEMPTS)\n ) {\n this.logger.error(\n `Failed to authenticate: \"${serverMessage.error}\", check your server auth config`,\n );\n if (this.syncState.hasAuth()) {\n this.syncState.clearAuth();\n }\n if (this.authState.state !== \"noAuth\") {\n this.setAndReportAuthFailed(this.authState.config.onAuthChange);\n }\n return;\n }\n if (this.authState.state === \"waitingForServerConfirmationOfFreshToken\") {\n this.tokenConfirmationAttempts++;\n this._logVerbose(\n `retrying reauthentication, ${MAX_TOKEN_CONFIRMATION_ATTEMPTS - this.tokenConfirmationAttempts} attempts remaining`,\n );\n }\n\n this.notifyRefreshChange(true);\n await this.stopSocket();\n if ((this.authState.state as AuthState[\"state\"]) === \"noAuth\") {\n // setConfig() or stop() ran during the await.\n return;\n }\n const token = await this.fetchTokenAndGuardAgainstRace(\n this.authState.config.fetchToken,\n {\n forceRefreshToken: true,\n },\n );\n if (token.isFromOutdatedConfig) {\n return;\n }\n\n if (token.value && this.syncState.isNewAuth(token.value)) {\n this.authenticate(token.value);\n this.setAuthState({\n state: \"waitingForServerConfirmationOfFreshToken\",\n config: this.authState.config,\n token: token.value,\n hadAuth:\n this.authState.state === \"notRefetching\" ||\n this.authState.state === \"waitingForScheduledRefetch\",\n });\n } else {\n this._logVerbose(\"reauthentication failed, could not fetch a new token\");\n if (this.syncState.hasAuth()) {\n this.syncState.clearAuth();\n }\n this.setAndReportAuthFailed(this.authState.config.onAuthChange);\n }\n this.tryRestartSocket();\n }\n\n // Force refetch the token and schedule another refetch\n // before the token expires - an active client should never\n // need to reauthenticate.\n private async refetchToken() {\n if (this.authState.state === \"noAuth\") {\n return;\n }\n this._logVerbose(\"refetching auth token\");\n const token = await this.fetchTokenAndGuardAgainstRace(\n this.authState.config.fetchToken,\n {\n forceRefreshToken: true,\n },\n );\n if (token.isFromOutdatedConfig) {\n return;\n }\n\n if (token.value) {\n if (this.syncState.isNewAuth(token.value)) {\n this.setAuthState({\n state: \"waitingForServerConfirmationOfFreshToken\",\n hadAuth: this.syncState.hasAuth(),\n token: token.value,\n config: this.authState.config,\n });\n this.authenticate(token.value);\n } else {\n this.setAuthState({\n state: \"notRefetching\",\n config: this.authState.config,\n });\n }\n } else {\n this._logVerbose(\"refetching token failed\");\n if (this.syncState.hasAuth()) {\n this.clearAuth();\n }\n this.setAndReportAuthFailed(this.authState.config.onAuthChange);\n }\n // Restart in case this refetch was triggered via schedule during\n // a reauthentication attempt.\n this._logVerbose(\n \"restarting WS after auth token fetch (if currently stopped)\",\n );\n this.tryRestartSocket();\n }\n\n private scheduleTokenRefetch(token: string) {\n if (this.authState.state === \"noAuth\") {\n return;\n }\n const decodedToken = this.decodeToken(token);\n if (!decodedToken) {\n // This is no longer really possible, because\n // we wait on server response before scheduling token refetch,\n // and the server currently requires JWT tokens.\n this.logger.error(\n \"Auth token is not a valid JWT, cannot refetch the token\",\n );\n return;\n }\n // iat: issued at time, UTC seconds timestamp at which the JWT was issued\n // exp: expiration time, UTC seconds timestamp at which the JWT will expire\n const { iat, exp } = decodedToken as { iat?: number; exp?: number };\n if (!iat || !exp) {\n this.logger.error(\n \"Auth token does not have required fields, cannot refetch the token\",\n );\n return;\n }\n // Because the client and server clocks may be out of sync,\n // we only know that the token will expire after `exp - iat`,\n // and since we just fetched a fresh one we know when that\n // will happen.\n const tokenValiditySeconds = exp - iat;\n if (tokenValiditySeconds <= 2) {\n this.logger.error(\n \"Auth token does not live long enough, cannot refetch the token\",\n );\n return;\n }\n // Attempt to refresh the token `refreshTokenLeewaySeconds` before it expires,\n // or immediately if the token is already expiring soon.\n let delay = Math.min(\n MAXIMUM_REFRESH_DELAY,\n (tokenValiditySeconds - this.refreshTokenLeewaySeconds) * 1000,\n );\n if (delay <= 0) {\n // Refetch immediately, but this might be due to configuring a `refreshTokenLeewaySeconds`\n // that is too large compared to the token's actual lifetime.\n this.logger.warn(\n `Refetching auth token immediately, configured leeway ${this.refreshTokenLeewaySeconds}s is larger than the token's lifetime ${tokenValiditySeconds}s`,\n );\n delay = 0;\n }\n const refetchTokenTimeoutId = setTimeout(() => {\n this._logVerbose(\"running scheduled token refetch\");\n void this.refetchToken();\n }, delay);\n this.setAuthState({\n state: \"waitingForScheduledRefetch\",\n refetchTokenTimeoutId,\n config: this.authState.config,\n });\n this._logVerbose(\n `scheduled preemptive auth token refetching in ${delay}ms`,\n );\n }\n\n // Protects against simultaneous calls to `setConfig`\n // while we're fetching a token\n private async fetchTokenAndGuardAgainstRace(\n fetchToken: AuthTokenFetcher,\n fetchArgs: {\n forceRefreshToken: boolean;\n },\n ) {\n const originalConfigVersion = ++this.configVersion;\n this._logVerbose(\n `fetching token with config version ${originalConfigVersion}`,\n );\n const token = await fetchToken(fetchArgs);\n if (this.configVersion !== originalConfigVersion) {\n // This is a stale config\n this._logVerbose(\n `stale config version, expected ${originalConfigVersion}, got ${this.configVersion}`,\n );\n return { isFromOutdatedConfig: true };\n }\n return { isFromOutdatedConfig: false, value: token };\n }\n\n stop() {\n this.resetAuthState();\n // Bump this in case we are mid-token-fetch when we get stopped\n this.configVersion++;\n this._logVerbose(`config version bumped to ${this.configVersion}`);\n }\n\n private setAndReportAuthFailed(\n onAuthChange: (authenticated: boolean) => void,\n ) {\n onAuthChange(false);\n this.resetAuthState();\n }\n\n // The sole path to `state === \"noAuth\"`; consumers rely on this firing\n // `notifyRefreshChange(false)` to pair any in-flight `(true)`. May run\n // when refresh state is already false.\n private resetAuthState() {\n this.notifyRefreshChange(false);\n this.setAuthState({ state: \"noAuth\" });\n }\n\n private setAuthState(newAuth: AuthState) {\n const authStateForLog =\n newAuth.state === \"waitingForServerConfirmationOfFreshToken\"\n ? {\n hadAuth: newAuth.hadAuth,\n state: newAuth.state,\n token: `...${newAuth.token.slice(-7)}`,\n }\n : { state: newAuth.state };\n this._logVerbose(\n `setting auth state to ${JSON.stringify(authStateForLog)}`,\n );\n switch (newAuth.state) {\n case \"waitingForScheduledRefetch\":\n case \"notRefetching\":\n case \"noAuth\":\n this.tokenConfirmationAttempts = 0;\n break;\n case \"waitingForServerConfirmationOfFreshToken\":\n case \"waitingForServerConfirmationOfCachedToken\":\n case \"initialRefetch\":\n break;\n default: {\n newAuth satisfies never;\n }\n }\n if (this.authState.state === \"waitingForScheduledRefetch\") {\n // TODO: this side-effect would be better situated with scheduling refetch\n clearTimeout(this.authState.refetchTokenTimeoutId);\n }\n this.authState = newAuth;\n }\n\n private decodeToken(token: string) {\n try {\n return jwtDecode(token);\n } catch (e) {\n this._logVerbose(\n `Error decoding token: ${e instanceof Error ? e.message : \"Unknown error\"}`,\n );\n return null;\n }\n }\n\n private _logVerbose(message: string) {\n this.logger.logVerbose(`${message} [v${this.configVersion}]`);\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Logger } from \"../logging.js\";\nimport { LocalSyncState } from \"./local_state.js\";\nimport { AuthError, IdentityVersion, Transition } from \"./protocol.js\";\nimport { jwtDecode } from \"../../vendor/jwt-decode/index.js\";\n\n// setTimout uses 32 bit integer, so it can only\n// schedule about 24 days in the future.\nconst MAXIMUM_REFRESH_DELAY = 20 * 24 * 60 * 60 * 1000; // 20 days\n\nconst MAX_TOKEN_CONFIRMATION_ATTEMPTS = 2;\n\n/**\n * An async function returning a JWT. Depending on the auth providers\n * configured in convex/auth.config.ts, this may be a JWT-encoded OpenID\n * Connect Identity Token or a traditional JWT.\n *\n * `forceRefreshToken` is `true` if the server rejected a previously\n * returned token or the token is anticipated to expiring soon\n * based on its `exp` time.\n *\n * See {@link ConvexReactClient.setAuth}.\n *\n * @public\n */\nexport type AuthTokenFetcher = (args: {\n forceRefreshToken: boolean;\n}) => Promise<string | null | undefined>;\n\n/**\n * What is provided to the client.\n */\ntype AuthConfig = {\n fetchToken: AuthTokenFetcher;\n onAuthChange: (isAuthenticated: boolean) => void;\n onRefreshChange?: ((isRefreshing: boolean) => void) | undefined;\n};\n\n/**\n * By default we take 3 steps:\n * 1. Fetch a possibly cached token and send it to the server\n * 2. Immediately fetch a fresh token without using a cache\n * 3. Repeat step 2 before the end of the fresh token's lifetime\n *\n * When `initialAuthTokenReuse` is enabled, step 2 is skipped: the\n * cached token is reused and a refetch is scheduled based on its\n * remaining lifetime (estimated via the server's clock skew). This\n * avoids a second Authenticate message that would cause the server\n * to re-execute all authenticated queries.\n *\n * If the server rejects a token before a scheduled refetch, we\n * immediately fetch a fresh token and retry.\n */\ntype AuthState =\n | { state: \"noAuth\" }\n | {\n state: \"waitingForServerConfirmationOfCachedToken\";\n config: AuthConfig;\n hasRetried: boolean;\n }\n | {\n state: \"initialRefetch\";\n config: AuthConfig;\n }\n | {\n state: \"waitingForServerConfirmationOfFreshToken\";\n config: AuthConfig;\n hadAuth: boolean;\n token: string;\n }\n | {\n state: \"waitingForScheduledRefetch\";\n config: AuthConfig;\n refetchTokenTimeoutId: ReturnType<typeof setTimeout>;\n }\n // Special/weird state when we got a valid token\n // but could not fetch a new one.\n | {\n state: \"notRefetching\";\n config: AuthConfig;\n };\n\n/**\n * Handles the state transitions for auth. The server is the source\n * of truth.\n */\nexport class AuthenticationManager {\n private authState: AuthState = { state: \"noAuth\" };\n // Used to detect races involving `setConfig` calls\n // while a token is being fetched.\n private configVersion = 0;\n // Shared by the BaseClient so that the auth manager can easily inspect it\n private readonly syncState: LocalSyncState;\n // Passed down by BaseClient, sends a message to the server\n private readonly authenticate: (token: string) => IdentityVersion;\n private readonly stopSocket: () => Promise<void>;\n private readonly tryRestartSocket: () => void;\n private readonly pauseSocket: () => void;\n private readonly resumeSocket: () => void;\n // Passed down by BaseClient, sends a message to the server\n private readonly clearAuth: () => void;\n private readonly logger: Logger;\n private readonly refreshTokenLeewaySeconds: number;\n private readonly initialAuthTokenReuse: boolean;\n // Track last value to avoid redundant calls\n private lastRefreshChange: boolean;\n // Number of times we have attempted to confirm the latest token. We retry up\n // to `MAX_TOKEN_CONFIRMATION_ATTEMPTS` times.\n private tokenConfirmationAttempts = 0;\n constructor(\n syncState: LocalSyncState,\n callbacks: {\n authenticate: (token: string) => IdentityVersion;\n stopSocket: () => Promise<void>;\n tryRestartSocket: () => void;\n pauseSocket: () => void;\n resumeSocket: () => void;\n clearAuth: () => void;\n },\n config: {\n refreshTokenLeewaySeconds: number;\n logger: Logger;\n initialAuthTokenReuse: boolean;\n },\n ) {\n this.syncState = syncState;\n this.authenticate = callbacks.authenticate;\n this.stopSocket = callbacks.stopSocket;\n this.tryRestartSocket = callbacks.tryRestartSocket;\n this.pauseSocket = callbacks.pauseSocket;\n this.resumeSocket = callbacks.resumeSocket;\n this.clearAuth = callbacks.clearAuth;\n this.logger = config.logger;\n this.refreshTokenLeewaySeconds = config.refreshTokenLeewaySeconds;\n this.initialAuthTokenReuse = config.initialAuthTokenReuse;\n this.lastRefreshChange = false;\n }\n\n private notifyRefreshChange(isRefreshing: boolean) {\n if (\n this.authState.state !== \"noAuth\" &&\n this.authState.state !== \"initialRefetch\" &&\n this.authState.config.onRefreshChange &&\n this.lastRefreshChange !== isRefreshing\n ) {\n this.lastRefreshChange = isRefreshing;\n this.authState.config.onRefreshChange(isRefreshing);\n }\n }\n\n async setConfig(\n fetchToken: AuthTokenFetcher,\n onChange: (isAuthenticated: boolean) => void,\n onRefreshChange?: (isRefreshing: boolean) => void,\n ) {\n this.resetAuthState();\n this._logVerbose(\"pausing WS for auth token fetch\");\n this.pauseSocket();\n const token = await this.fetchTokenAndGuardAgainstRace(fetchToken, {\n forceRefreshToken: false,\n });\n if (token.isFromOutdatedConfig) {\n return;\n }\n const config: AuthConfig = {\n fetchToken,\n onAuthChange: onChange,\n onRefreshChange,\n };\n if (token.value) {\n this.setAuthState({\n state: \"waitingForServerConfirmationOfCachedToken\",\n config,\n hasRetried: false,\n });\n this.authenticate(token.value);\n } else {\n this.setAuthState({\n state: \"initialRefetch\",\n config,\n });\n // Try again with `forceRefreshToken: true`\n await this.refetchToken();\n }\n this._logVerbose(\"resuming WS after auth token fetch\");\n this.resumeSocket();\n }\n\n onTransition(serverMessage: Transition) {\n if (\n !this.syncState.isCurrentOrNewerAuthVersion(\n serverMessage.endVersion.identity,\n )\n ) {\n // This is a stale transition - client has moved on to\n // a newer auth version.\n return;\n }\n if (\n serverMessage.endVersion.identity <= serverMessage.startVersion.identity\n ) {\n // This transition did not change auth - it is not a response to Authenticate.\n return;\n }\n\n this._logVerbose(\n `auth state is ${this.authState.state} when handling transition`,\n );\n\n // This transition advanced the auth version, which means the token used was valid\n // and the client and server auth states are in sync.\n this.syncState.markAuthCompletion();\n\n if (this.authState.state === \"waitingForServerConfirmationOfCachedToken\") {\n this._logVerbose(\"server confirmed auth token is valid\");\n const cachedToken = this.syncState.getAuth()?.value;\n if (this.initialAuthTokenReuse && cachedToken) {\n this.scheduleTokenRefetch(cachedToken, serverMessage.clientClockSkew);\n } else {\n void this.refetchToken();\n }\n this.authState.config.onAuthChange(true);\n return;\n }\n if (this.authState.state === \"waitingForServerConfirmationOfFreshToken\") {\n this._logVerbose(\"server confirmed new auth token is valid\");\n this.notifyRefreshChange(false);\n this.scheduleTokenRefetch(this.authState.token);\n this.tokenConfirmationAttempts = 0;\n if (!this.authState.hadAuth) {\n this.authState.config.onAuthChange(true);\n }\n }\n }\n\n onAuthError(serverMessage: AuthError) {\n // If the AuthError is not due to updating the token, and we're currently\n // waiting on the result of a token update, ignore.\n if (\n serverMessage.authUpdateAttempted === false &&\n (this.authState.state === \"waitingForServerConfirmationOfFreshToken\" ||\n this.authState.state === \"waitingForServerConfirmationOfCachedToken\")\n ) {\n this._logVerbose(\"ignoring non-auth token expired error\");\n return;\n }\n const { baseVersion } = serverMessage;\n // Versioned AuthErrors are ignored if the client advanced to\n // a newer auth identity\n // Error are reporting the previous version, since the server\n // didn't advance, hence `+ 1`.\n if (!this.syncState.isCurrentOrNewerAuthVersion(baseVersion + 1)) {\n this._logVerbose(\"ignoring auth error for previous auth attempt\");\n return;\n }\n void this.tryToReauthenticate(serverMessage);\n return;\n }\n\n // This is similar to `refetchToken` defined below, in fact we\n // don't represent them as different states, but it is different\n // in that we pause the WebSocket so that mutations\n // don't retry with bad auth.\n private async tryToReauthenticate(serverMessage: AuthError) {\n this._logVerbose(`attempting to reauthenticate: ${serverMessage.error}`);\n if (\n // No way to fetch another token, kaboom\n this.authState.state === \"noAuth\" ||\n // We failed on a fresh token. After a small number of retries, we give up\n // and clear the auth state to avoid infinite retries.\n (this.authState.state === \"waitingForServerConfirmationOfFreshToken\" &&\n this.tokenConfirmationAttempts >= MAX_TOKEN_CONFIRMATION_ATTEMPTS)\n ) {\n this.logger.error(\n `Failed to authenticate: \"${serverMessage.error}\", check your server auth config`,\n );\n if (this.syncState.hasAuth()) {\n this.syncState.clearAuth();\n }\n if (this.authState.state !== \"noAuth\") {\n this.setAndReportAuthFailed(this.authState.config.onAuthChange);\n }\n return;\n }\n if (this.authState.state === \"waitingForServerConfirmationOfFreshToken\") {\n this.tokenConfirmationAttempts++;\n this._logVerbose(\n `retrying reauthentication, ${MAX_TOKEN_CONFIRMATION_ATTEMPTS - this.tokenConfirmationAttempts} attempts remaining`,\n );\n }\n\n this.notifyRefreshChange(true);\n await this.stopSocket();\n if ((this.authState.state as AuthState[\"state\"]) === \"noAuth\") {\n // setConfig() or stop() ran during the await.\n return;\n }\n const token = await this.fetchTokenAndGuardAgainstRace(\n this.authState.config.fetchToken,\n {\n forceRefreshToken: true,\n },\n );\n if (token.isFromOutdatedConfig) {\n return;\n }\n\n if (token.value && this.syncState.isNewAuth(token.value)) {\n this.authenticate(token.value);\n this.setAuthState({\n state: \"waitingForServerConfirmationOfFreshToken\",\n config: this.authState.config,\n token: token.value,\n hadAuth:\n this.authState.state === \"notRefetching\" ||\n this.authState.state === \"waitingForScheduledRefetch\",\n });\n } else {\n this._logVerbose(\"reauthentication failed, could not fetch a new token\");\n if (this.syncState.hasAuth()) {\n this.syncState.clearAuth();\n }\n this.setAndReportAuthFailed(this.authState.config.onAuthChange);\n }\n this.tryRestartSocket();\n }\n\n // Force refetch the token and schedule another refetch\n // before the token expires - an active client should never\n // need to reauthenticate.\n private async refetchToken() {\n if (this.authState.state === \"noAuth\") {\n return;\n }\n this._logVerbose(\"refetching auth token\");\n const token = await this.fetchTokenAndGuardAgainstRace(\n this.authState.config.fetchToken,\n {\n forceRefreshToken: true,\n },\n );\n if (token.isFromOutdatedConfig) {\n return;\n }\n\n if (token.value) {\n if (this.syncState.isNewAuth(token.value)) {\n this.setAuthState({\n state: \"waitingForServerConfirmationOfFreshToken\",\n hadAuth: this.syncState.hasAuth(),\n token: token.value,\n config: this.authState.config,\n });\n this.authenticate(token.value);\n } else {\n this.setAuthState({\n state: \"notRefetching\",\n config: this.authState.config,\n });\n }\n } else {\n this._logVerbose(\"refetching token failed\");\n if (this.syncState.hasAuth()) {\n this.clearAuth();\n }\n this.setAndReportAuthFailed(this.authState.config.onAuthChange);\n }\n // Restart in case this refetch was triggered via schedule during\n // a reauthentication attempt.\n this._logVerbose(\n \"restarting WS after auth token fetch (if currently stopped)\",\n );\n this.tryRestartSocket();\n }\n\n private scheduleTokenRefetch(token: string, clientClockSkewMs?: number) {\n if (this.authState.state === \"noAuth\") {\n return;\n }\n const decodedToken = this.decodeToken(token);\n if (!decodedToken) {\n // This is no longer really possible, because\n // we wait on server response before scheduling token refetch,\n // and the server currently requires JWT tokens.\n this.logger.error(\n \"Auth token is not a valid JWT, cannot refetch the token\",\n );\n return;\n }\n // iat: issued at time, UTC seconds timestamp at which the JWT was issued\n // exp: expiration time, UTC seconds timestamp at which the JWT will expire\n const { iat, exp } = decodedToken as { iat?: number; exp?: number };\n if (!iat || !exp) {\n this.logger.error(\n \"Auth token does not have required fields, cannot refetch the token\",\n );\n return;\n }\n const fullLifetimeSeconds = exp - iat;\n if (fullLifetimeSeconds <= 2) {\n this.logger.error(\n \"Auth token does not live long enough, cannot refetch the token\",\n );\n return;\n }\n let tokenValiditySeconds: number;\n if (clientClockSkewMs !== undefined) {\n // For cached tokens we use the server's clock skew estimate to\n // compute remaining lifetime. The server computes\n // clientClockSkew = clientTs - serverReceiveTs, so it's positive\n // when the client is ahead and negative when behind. To recover\n // server time: serverNow = Date.now() - clientClockSkew.\n // Both Date.now() and setTimeout use the same client clock, so\n // the scheduled delay is accurate regardless of absolute offset.\n // Connect latency inflates the skew toward \"client behind,\" which\n // is conservative \u2014 we schedule the refetch slightly early.\n const estimatedServerNowSeconds = (Date.now() - clientClockSkewMs) / 1000;\n tokenValiditySeconds = exp - estimatedServerNowSeconds;\n if (tokenValiditySeconds <= 0) {\n tokenValiditySeconds = 0;\n }\n } else {\n // For freshly-fetched tokens, `exp - iat` is the full lifetime\n // starting from now.\n tokenValiditySeconds = fullLifetimeSeconds;\n }\n // Attempt to refresh the token `refreshTokenLeewaySeconds` before it expires,\n // or immediately if the token is already expiring soon.\n let delay = Math.min(\n MAXIMUM_REFRESH_DELAY,\n (tokenValiditySeconds - this.refreshTokenLeewaySeconds) * 1000,\n );\n if (delay <= 0) {\n // Refetch immediately, but this might be due to configuring a `refreshTokenLeewaySeconds`\n // that is too large compared to the token's actual lifetime.\n this.logger.warn(\n `Refetching auth token immediately, configured leeway ${this.refreshTokenLeewaySeconds}s is larger than the token's lifetime ${tokenValiditySeconds}s`,\n );\n delay = 0;\n }\n const refetchTokenTimeoutId = setTimeout(() => {\n this._logVerbose(\"running scheduled token refetch\");\n void this.refetchToken();\n }, delay);\n this.setAuthState({\n state: \"waitingForScheduledRefetch\",\n refetchTokenTimeoutId,\n config: this.authState.config,\n });\n this._logVerbose(\n `scheduled preemptive auth token refetching in ${delay}ms`,\n );\n }\n\n // Protects against simultaneous calls to `setConfig`\n // while we're fetching a token\n private async fetchTokenAndGuardAgainstRace(\n fetchToken: AuthTokenFetcher,\n fetchArgs: {\n forceRefreshToken: boolean;\n },\n ) {\n const originalConfigVersion = ++this.configVersion;\n this._logVerbose(\n `fetching token with config version ${originalConfigVersion}`,\n );\n const token = await fetchToken(fetchArgs);\n if (this.configVersion !== originalConfigVersion) {\n // This is a stale config\n this._logVerbose(\n `stale config version, expected ${originalConfigVersion}, got ${this.configVersion}`,\n );\n return { isFromOutdatedConfig: true };\n }\n return { isFromOutdatedConfig: false, value: token };\n }\n\n stop() {\n this.resetAuthState();\n // Bump this in case we are mid-token-fetch when we get stopped\n this.configVersion++;\n this._logVerbose(`config version bumped to ${this.configVersion}`);\n }\n\n private setAndReportAuthFailed(\n onAuthChange: (authenticated: boolean) => void,\n ) {\n onAuthChange(false);\n this.resetAuthState();\n }\n\n // The sole path to `state === \"noAuth\"`; consumers rely on this firing\n // `notifyRefreshChange(false)` to pair any in-flight `(true)`. May run\n // when refresh state is already false.\n private resetAuthState() {\n this.notifyRefreshChange(false);\n this.setAuthState({ state: \"noAuth\" });\n }\n\n private setAuthState(newAuth: AuthState) {\n const authStateForLog =\n newAuth.state === \"waitingForServerConfirmationOfFreshToken\"\n ? {\n hadAuth: newAuth.hadAuth,\n state: newAuth.state,\n token: `...${newAuth.token.slice(-7)}`,\n }\n : { state: newAuth.state };\n this._logVerbose(\n `setting auth state to ${JSON.stringify(authStateForLog)}`,\n );\n switch (newAuth.state) {\n case \"waitingForScheduledRefetch\":\n case \"notRefetching\":\n case \"noAuth\":\n this.tokenConfirmationAttempts = 0;\n break;\n case \"waitingForServerConfirmationOfFreshToken\":\n case \"waitingForServerConfirmationOfCachedToken\":\n case \"initialRefetch\":\n break;\n default: {\n newAuth satisfies never;\n }\n }\n if (this.authState.state === \"waitingForScheduledRefetch\") {\n // TODO: this side-effect would be better situated with scheduling refetch\n clearTimeout(this.authState.refetchTokenTimeoutId);\n }\n this.authState = newAuth;\n }\n\n private decodeToken(token: string) {\n try {\n return jwtDecode(token);\n } catch (e) {\n this._logVerbose(\n `Error decoding token: ${e instanceof Error ? e.message : \"Unknown error\"}`,\n );\n return null;\n }\n }\n\n private _logVerbose(message: string) {\n this.logger.logVerbose(`${message} [v${this.configVersion}]`);\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -153,7 +153,8 @@ class BaseConvexClient {
153
153
  },
154
154
  {
155
155
  logger: this.logger,
156
- refreshTokenLeewaySeconds: authRefreshTokenLeewaySeconds
156
+ refreshTokenLeewaySeconds: authRefreshTokenLeewaySeconds,
157
+ initialAuthTokenReuse: options.initialAuthTokenReuse ?? false
157
158
  }
158
159
  );
159
160
  this.optimisticQueryResults = new import_optimistic_updates_impl.OptimisticQueryResults();