@abloatai/ablo 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -3
- package/CHANGELOG.md +34 -0
- package/README.md +104 -88
- package/dist/BaseSyncedStore.d.ts +140 -266
- package/dist/BaseSyncedStore.js +338 -739
- package/dist/Database.d.ts +62 -77
- package/dist/Database.js +106 -127
- package/dist/{ObjectPool.d.ts → InstanceCache.d.ts} +10 -13
- package/dist/{ObjectPool.js → InstanceCache.js} +91 -83
- package/dist/LazyReferenceCollection.d.ts +11 -15
- package/dist/LazyReferenceCollection.js +16 -15
- package/dist/Model.d.ts +37 -52
- package/dist/Model.js +52 -69
- package/dist/ModelRegistry.d.ts +46 -25
- package/dist/ModelRegistry.js +32 -30
- package/dist/NetworkMonitor.d.ts +5 -6
- package/dist/NetworkMonitor.js +6 -7
- package/dist/SyncClient.d.ts +119 -109
- package/dist/SyncClient.js +303 -224
- package/dist/SyncEngineContext.d.ts +1 -3
- package/dist/SyncEngineContext.js +1 -2
- package/dist/adapters/alwaysOnline.d.ts +6 -8
- package/dist/adapters/alwaysOnline.js +6 -8
- package/dist/adapters/inMemoryStorage.d.ts +9 -9
- package/dist/adapters/inMemoryStorage.js +9 -9
- package/dist/agent/Agent.d.ts +39 -31
- package/dist/agent/Agent.js +35 -23
- package/dist/agent/index.d.ts +4 -4
- package/dist/agent/index.js +5 -5
- package/dist/agent/session.d.ts +47 -44
- package/dist/agent/session.js +37 -48
- package/dist/agent/types.d.ts +26 -31
- package/dist/agent/types.js +6 -7
- package/dist/ai-sdk/coordinatedTool.d.ts +108 -0
- package/dist/ai-sdk/{coordinated-tool.js → coordinatedTool.js} +44 -38
- package/dist/ai-sdk/coordinationContext.d.ts +46 -0
- package/dist/ai-sdk/{coordination-context.js → coordinationContext.js} +30 -31
- package/dist/ai-sdk/index.d.ts +25 -22
- package/dist/ai-sdk/index.js +25 -22
- package/dist/ai-sdk/wrap.d.ts +7 -8
- package/dist/ai-sdk/wrap.js +2 -2
- package/dist/auth/credentialPolicy.d.ts +74 -71
- package/dist/auth/credentialPolicy.js +51 -56
- package/dist/auth/credentialSource.d.ts +7 -18
- package/dist/auth/credentialSource.js +10 -18
- package/dist/auth/index.d.ts +59 -58
- package/dist/auth/index.js +34 -40
- package/dist/auth/schemas.d.ts +5 -4
- package/dist/auth/schemas.js +5 -4
- package/dist/batching/index.d.ts +19 -21
- package/dist/batching/index.js +14 -17
- package/dist/cli.cjs +483 -369
- package/dist/client/Ablo.d.ts +107 -836
- package/dist/client/Ablo.js +174 -833
- package/dist/client/ApiClient.d.ts +44 -20
- package/dist/client/ApiClient.js +193 -44
- package/dist/client/auth.d.ts +51 -60
- package/dist/client/auth.js +137 -110
- package/dist/client/claimHeartbeatLoop.d.ts +50 -0
- package/dist/client/claimHeartbeatLoop.js +88 -0
- package/dist/client/consoleLogger.d.ts +35 -0
- package/dist/client/consoleLogger.js +44 -0
- package/dist/client/createInternalComponents.d.ts +14 -17
- package/dist/client/createInternalComponents.js +26 -31
- package/dist/client/createModelProxy.d.ts +130 -120
- package/dist/client/createModelProxy.js +158 -124
- package/dist/client/credentialEndpoint.d.ts +61 -0
- package/dist/client/credentialEndpoint.js +86 -0
- package/dist/client/functionalUpdate.d.ts +29 -27
- package/dist/client/functionalUpdate.js +21 -21
- package/dist/client/hostedEndpoints.d.ts +21 -0
- package/dist/client/hostedEndpoints.js +21 -0
- package/dist/client/httpClient.d.ts +58 -54
- package/dist/client/httpClient.js +29 -31
- package/dist/client/identity.d.ts +15 -20
- package/dist/client/identity.js +49 -59
- package/dist/client/modelRegistration.d.ts +10 -0
- package/dist/client/modelRegistration.js +301 -0
- package/dist/client/options.d.ts +373 -0
- package/dist/client/options.js +6 -0
- package/dist/client/registerDataSource.d.ts +9 -9
- package/dist/client/registerDataSource.js +15 -16
- package/dist/client/resourceTypes.d.ts +333 -0
- package/dist/client/resourceTypes.js +7 -0
- package/dist/client/schemaConfig.d.ts +44 -0
- package/dist/client/schemaConfig.js +176 -0
- package/dist/client/sessionMint.d.ts +17 -13
- package/dist/client/sessionMint.js +26 -31
- package/dist/client/validateAbloOptions.d.ts +12 -14
- package/dist/client/validateAbloOptions.js +9 -10
- package/dist/client/writeOptionsSchema.d.ts +18 -16
- package/dist/client/writeOptionsSchema.js +23 -20
- package/dist/client/wsMutationExecutor.d.ts +28 -0
- package/dist/client/wsMutationExecutor.js +71 -0
- package/dist/context.d.ts +6 -4
- package/dist/context.js +6 -7
- package/dist/coordination/index.d.ts +13 -4
- package/dist/coordination/index.js +29 -4
- package/dist/coordination/schema.d.ts +176 -128
- package/dist/coordination/schema.js +197 -133
- package/dist/coordination/trace.d.ts +9 -11
- package/dist/coordination/trace.js +13 -15
- package/dist/core/DatabaseManager.d.ts +5 -8
- package/dist/core/DatabaseManager.js +38 -40
- package/dist/core/QueryProcessor.d.ts +7 -9
- package/dist/core/QueryProcessor.js +27 -34
- package/dist/core/QueryView.d.ts +17 -5
- package/dist/core/QueryView.js +6 -7
- package/dist/core/StoreManager.d.ts +14 -16
- package/dist/core/StoreManager.js +26 -25
- package/dist/core/ViewRegistry.d.ts +5 -5
- package/dist/core/ViewRegistry.js +4 -4
- package/dist/core/index.d.ts +18 -13
- package/dist/core/index.js +32 -26
- package/dist/core/openIDBWithTimeout.d.ts +38 -36
- package/dist/core/openIDBWithTimeout.js +57 -54
- package/dist/core/queryUtils.d.ts +45 -0
- package/dist/core/queryUtils.js +69 -0
- package/dist/core/storeContract.d.ts +145 -0
- package/dist/core/storeContract.js +12 -0
- package/dist/environment.d.ts +28 -0
- package/dist/environment.js +21 -0
- package/dist/errorCodes.d.ts +118 -101
- package/dist/errorCodes.js +277 -260
- package/dist/errors.d.ts +170 -165
- package/dist/errors.js +161 -151
- package/dist/index.d.ts +30 -27
- package/dist/index.js +90 -82
- package/dist/interfaces/index.d.ts +108 -133
- package/dist/interfaces/index.js +5 -4
- package/dist/keys/index.d.ts +27 -29
- package/dist/keys/index.js +59 -49
- package/dist/mutators/RecordingTransaction.d.ts +16 -16
- package/dist/mutators/RecordingTransaction.js +31 -37
- package/dist/mutators/Transaction.d.ts +18 -26
- package/dist/mutators/Transaction.js +14 -20
- package/dist/mutators/UndoManager.d.ts +122 -131
- package/dist/mutators/UndoManager.js +149 -155
- package/dist/mutators/defineMutators.d.ts +24 -37
- package/dist/mutators/defineMutators.js +14 -20
- package/dist/mutators/inverseOp.d.ts +12 -15
- package/dist/mutators/inverseOp.js +12 -15
- package/dist/mutators/mutateActions.d.ts +10 -9
- package/dist/mutators/mutateActions.js +1 -1
- package/dist/mutators/readerActions.d.ts +9 -8
- package/dist/mutators/readerActions.js +2 -2
- package/dist/mutators/undoApply.d.ts +31 -27
- package/dist/mutators/undoApply.js +26 -24
- package/dist/policy/index.d.ts +5 -3
- package/dist/policy/index.js +5 -3
- package/dist/policy/types.d.ts +105 -101
- package/dist/policy/types.js +67 -66
- package/dist/query/client.d.ts +32 -16
- package/dist/query/client.js +103 -72
- package/dist/query/types.d.ts +37 -60
- package/dist/query/types.js +13 -33
- package/dist/react/AbloProvider.d.ts +7 -11
- package/dist/react/AbloProvider.js +24 -17
- package/dist/react/context.d.ts +27 -146
- package/dist/react/context.js +9 -10
- package/dist/react/index.d.ts +41 -42
- package/dist/react/index.js +37 -38
- package/dist/react/internalContext.d.ts +17 -19
- package/dist/react/useAblo.d.ts +23 -22
- package/dist/react/useAblo.js +17 -15
- package/dist/react/useCurrentUserId.d.ts +8 -7
- package/dist/react/useCurrentUserId.js +8 -7
- package/dist/react/useErrorListener.d.ts +7 -7
- package/dist/react/useErrorListener.js +11 -12
- package/dist/react/useMutationFailureListener.d.ts +8 -8
- package/dist/react/useMutationFailureListener.js +9 -9
- package/dist/react/useMutators.d.ts +11 -11
- package/dist/react/useMutators.js +10 -4
- package/dist/react/useReactive.js +2 -3
- package/dist/react/useSyncStatus.d.ts +4 -6
- package/dist/react/useUndoScope.d.ts +7 -9
- package/dist/react/useUndoScope.js +3 -3
- package/dist/schema/coordination.d.ts +21 -25
- package/dist/schema/coordination.js +21 -25
- package/dist/schema/ddl.d.ts +43 -39
- package/dist/schema/ddl.js +75 -68
- package/dist/schema/ddlLock.d.ts +35 -0
- package/dist/schema/ddlLock.js +46 -0
- package/dist/schema/diff.d.ts +99 -61
- package/dist/schema/diff.js +43 -34
- package/dist/schema/field.d.ts +37 -42
- package/dist/schema/field.js +36 -49
- package/dist/schema/generate.d.ts +12 -12
- package/dist/schema/generate.js +12 -12
- package/dist/schema/index.d.ts +5 -4
- package/dist/schema/index.js +29 -21
- package/dist/schema/model.d.ts +121 -146
- package/dist/schema/model.js +24 -35
- package/dist/schema/openapi.d.ts +10 -9
- package/dist/schema/openapi.js +7 -1
- package/dist/schema/queries.d.ts +30 -32
- package/dist/schema/queries.js +24 -25
- package/dist/schema/relation.d.ts +89 -99
- package/dist/schema/relation.js +13 -13
- package/dist/schema/residency.d.ts +38 -0
- package/dist/schema/residency.js +30 -0
- package/dist/schema/roles.d.ts +45 -27
- package/dist/schema/roles.js +52 -21
- package/dist/schema/schema.d.ts +36 -45
- package/dist/schema/schema.js +42 -39
- package/dist/schema/select.d.ts +13 -13
- package/dist/schema/select.js +13 -13
- package/dist/schema/serialize.d.ts +36 -39
- package/dist/schema/serialize.js +27 -31
- package/dist/schema/sugar.d.ts +17 -32
- package/dist/schema/sugar.js +14 -29
- package/dist/schema/{sync-delta-row.d.ts → syncDeltaRow.d.ts} +27 -50
- package/dist/schema/syncDeltaRow.js +89 -0
- package/dist/schema/tenancy.d.ts +44 -46
- package/dist/schema/tenancy.js +46 -48
- package/dist/server/adapter.d.ts +58 -58
- package/dist/server/adapter.js +13 -14
- package/dist/server/commit.d.ts +60 -64
- package/dist/server/index.d.ts +9 -10
- package/dist/server/index.js +1 -1
- package/dist/server/readConfig.d.ts +70 -0
- package/dist/server/readConfig.js +8 -0
- package/dist/server/storageMode.d.ts +23 -0
- package/dist/server/storageMode.js +17 -0
- package/dist/source/adapter.d.ts +31 -26
- package/dist/source/adapter.js +10 -10
- package/dist/source/adapters/drizzle.d.ts +28 -23
- package/dist/source/adapters/drizzle.js +34 -28
- package/dist/source/adapters/kysely.d.ts +27 -25
- package/dist/source/adapters/kysely.js +28 -26
- package/dist/source/adapters/memory.d.ts +8 -7
- package/dist/source/adapters/memory.js +10 -9
- package/dist/source/adapters/prisma.d.ts +13 -12
- package/dist/source/adapters/prisma.js +27 -29
- package/dist/source/conformance.d.ts +18 -11
- package/dist/source/conformance.js +27 -19
- package/dist/source/connector.d.ts +31 -32
- package/dist/source/connector.js +30 -28
- package/dist/source/connectorProtocol.d.ts +160 -0
- package/dist/source/connectorProtocol.js +162 -0
- package/dist/source/contract.d.ts +26 -27
- package/dist/source/contract.js +28 -29
- package/dist/source/factory.d.ts +94 -0
- package/dist/source/factory.js +268 -0
- package/dist/source/index.d.ts +10 -462
- package/dist/source/index.js +17 -421
- package/dist/source/migrations.d.ts +9 -9
- package/dist/source/migrations.js +9 -9
- package/dist/source/next.d.ts +10 -11
- package/dist/source/next.js +7 -8
- package/dist/source/pushQueue.d.ts +70 -48
- package/dist/source/pushQueue.js +36 -29
- package/dist/source/signing.d.ts +88 -0
- package/dist/source/signing.js +159 -0
- package/dist/source/types.d.ts +351 -0
- package/dist/source/types.js +43 -0
- package/dist/stores/ObjectStore.d.ts +11 -12
- package/dist/stores/ObjectStore.js +34 -35
- package/dist/stores/ObjectStoreContract.d.ts +12 -15
- package/dist/stores/SyncActionStore.d.ts +8 -12
- package/dist/stores/SyncActionStore.js +77 -46
- package/dist/surface.d.ts +28 -21
- package/dist/surface.js +28 -20
- package/dist/sync/{BootstrapHelper.d.ts → BootstrapFetcher.d.ts} +37 -45
- package/dist/sync/{BootstrapHelper.js → BootstrapFetcher.js} +101 -80
- package/dist/sync/ConnectionManager.d.ts +47 -50
- package/dist/sync/ConnectionManager.js +74 -70
- package/dist/sync/NetworkProbe.d.ts +27 -31
- package/dist/sync/NetworkProbe.js +67 -72
- package/dist/sync/{HydrationCoordinator.d.ts → OnDemandLoader.d.ts} +49 -36
- package/dist/sync/{HydrationCoordinator.js → OnDemandLoader.js} +79 -54
- package/dist/sync/{AreaOfInterestManager.d.ts → SubscriptionManager.d.ts} +45 -59
- package/dist/sync/{AreaOfInterestManager.js → SubscriptionManager.js} +44 -52
- package/dist/sync/SyncWebSocket.d.ts +175 -250
- package/dist/sync/SyncWebSocket.js +431 -769
- package/dist/sync/awaitClaimGrant.d.ts +18 -18
- package/dist/sync/awaitClaimGrant.js +38 -30
- package/dist/sync/bootstrapApply.d.ts +70 -0
- package/dist/sync/bootstrapApply.js +73 -0
- package/dist/sync/commitFrames.d.ts +44 -0
- package/dist/sync/commitFrames.js +94 -0
- package/dist/sync/createClaimStream.d.ts +23 -22
- package/dist/sync/createClaimStream.js +108 -25
- package/dist/sync/createPresenceStream.d.ts +19 -18
- package/dist/sync/createPresenceStream.js +25 -26
- package/dist/sync/createSnapshot.d.ts +13 -17
- package/dist/sync/createSnapshot.js +20 -26
- package/dist/sync/credentialLifecycle.d.ts +175 -0
- package/dist/sync/credentialLifecycle.js +322 -0
- package/dist/sync/deltaPipeline.d.ts +113 -0
- package/dist/sync/deltaPipeline.js +261 -0
- package/dist/sync/groupChange.d.ts +113 -0
- package/dist/sync/groupChange.js +242 -0
- package/dist/sync/heartbeat.d.ts +63 -0
- package/dist/sync/heartbeat.js +91 -0
- package/dist/sync/participants.d.ts +27 -27
- package/dist/sync/schemas.d.ts +3 -2
- package/dist/sync/schemas.js +14 -10
- package/dist/sync/syncCursor.d.ts +40 -0
- package/dist/sync/syncCursor.js +55 -0
- package/dist/sync/syncPlan.d.ts +54 -0
- package/dist/sync/syncPlan.js +50 -0
- package/dist/sync/syncPosition.d.ts +54 -49
- package/dist/sync/syncPosition.js +57 -52
- package/dist/sync/wsFrameHandlers.d.ts +116 -0
- package/dist/sync/wsFrameHandlers.js +374 -0
- package/dist/testing/fixtures/bootstrap.d.ts +21 -17
- package/dist/testing/fixtures/bootstrap.js +12 -6
- package/dist/testing/fixtures/deltas.d.ts +31 -34
- package/dist/testing/fixtures/deltas.js +30 -33
- package/dist/testing/fixtures/models.d.ts +11 -10
- package/dist/testing/fixtures/models.js +12 -10
- package/dist/testing/helpers/{react-wrapper.d.ts → reactWrapper.d.ts} +13 -10
- package/dist/testing/helpers/{react-wrapper.js → reactWrapper.js} +15 -12
- package/dist/testing/helpers/{sync-engine-harness.d.ts → syncEngineHarness.d.ts} +17 -18
- package/dist/testing/helpers/{sync-engine-harness.js → syncEngineHarness.js} +14 -11
- package/dist/testing/helpers/wait.d.ts +13 -8
- package/dist/testing/helpers/wait.js +13 -8
- package/dist/testing/index.d.ts +4 -4
- package/dist/testing/index.js +3 -3
- package/dist/testing/mocks/MockMutationExecutor.d.ts +18 -17
- package/dist/testing/mocks/MockMutationExecutor.js +15 -14
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +8 -8
- package/dist/testing/mocks/MockNetworkMonitor.js +8 -8
- package/dist/testing/mocks/MockSyncContext.d.ts +21 -34
- package/dist/testing/mocks/MockSyncContext.js +16 -45
- package/dist/testing/mocks/MockSyncStore.d.ts +14 -14
- package/dist/testing/mocks/MockSyncStore.js +11 -11
- package/dist/testing/mocks/MockWebSocket.d.ts +28 -24
- package/dist/testing/mocks/MockWebSocket.js +22 -21
- package/dist/transactions/TransactionQueue.d.ts +190 -221
- package/dist/transactions/TransactionQueue.js +424 -822
- package/dist/transactions/TransactionStore.d.ts +20 -0
- package/dist/transactions/TransactionStore.js +53 -0
- package/dist/transactions/UnconfirmedWrites.d.ts +82 -0
- package/dist/transactions/UnconfirmedWrites.js +104 -0
- package/dist/transactions/coalesceRules.d.ts +58 -0
- package/dist/transactions/coalesceRules.js +140 -0
- package/dist/transactions/commitPayload.d.ts +130 -0
- package/dist/transactions/commitPayload.js +143 -0
- package/dist/transactions/deltaConfirmation.d.ts +58 -0
- package/dist/transactions/deltaConfirmation.js +215 -0
- package/dist/transactions/optimisticApply.d.ts +49 -0
- package/dist/transactions/optimisticApply.js +65 -0
- package/dist/transactions/replayValidation.d.ts +99 -0
- package/dist/transactions/replayValidation.js +111 -0
- package/dist/types/global.d.ts +46 -41
- package/dist/types/global.js +20 -19
- package/dist/types/index.d.ts +74 -80
- package/dist/types/index.js +22 -27
- package/dist/types/modelData.d.ts +10 -0
- package/dist/types/modelData.js +9 -0
- package/dist/types/participant.d.ts +20 -0
- package/dist/types/participant.js +10 -0
- package/dist/types/streams.d.ts +216 -209
- package/dist/types/streams.js +7 -7
- package/dist/utils/asyncIterator.d.ts +25 -32
- package/dist/utils/asyncIterator.js +25 -32
- package/dist/utils/duration.d.ts +12 -15
- package/dist/utils/duration.js +12 -15
- package/dist/utils/mobxSetup.d.ts +53 -0
- package/dist/utils/{mobx-setup.js → mobxSetup.js} +44 -100
- package/dist/webhooks/events.d.ts +21 -16
- package/dist/webhooks/events.js +10 -8
- package/dist/webhooks/index.d.ts +5 -7
- package/dist/webhooks/index.js +5 -7
- package/dist/{schema/sync-delta-wire.d.ts → wire/delta.d.ts} +58 -41
- package/dist/wire/delta.js +114 -0
- package/dist/wire/errorEnvelope.d.ts +35 -27
- package/dist/wire/errorEnvelope.js +38 -32
- package/dist/wire/frames.d.ts +150 -67
- package/dist/wire/frames.js +48 -1
- package/dist/wire/index.d.ts +18 -13
- package/dist/wire/index.js +36 -13
- package/dist/wire/listEnvelope.d.ts +16 -23
- package/dist/wire/listEnvelope.js +7 -6
- package/dist/wire/protocol.d.ts +38 -0
- package/dist/wire/protocol.js +38 -0
- package/dist/wire/protocolVersion.d.ts +60 -0
- package/dist/wire/protocolVersion.js +67 -0
- package/docs/api-keys.md +4 -3
- package/docs/coordination.md +59 -0
- package/docs/examples/existing-python-backend.md +3 -3
- package/docs/identity.md +4 -4
- package/docs/integration-guide.md +1 -1
- package/docs/react.md +1 -1
- package/docs/sessions.md +5 -7
- package/package.json +24 -21
- package/dist/ai-sdk/coordinated-tool.d.ts +0 -101
- package/dist/ai-sdk/coordination-context.d.ts +0 -52
- package/dist/client/index.d.ts +0 -36
- package/dist/client/index.js +0 -33
- package/dist/config/index.d.ts +0 -10
- package/dist/config/index.js +0 -12
- package/dist/core/query-utils.d.ts +0 -34
- package/dist/core/query-utils.js +0 -59
- package/dist/interfaces/headless.d.ts +0 -95
- package/dist/interfaces/headless.js +0 -41
- package/dist/query/index.d.ts +0 -6
- package/dist/query/index.js +0 -5
- package/dist/realtime/index.d.ts +0 -10
- package/dist/realtime/index.js +0 -9
- package/dist/schema/plane.d.ts +0 -23
- package/dist/schema/plane.js +0 -19
- package/dist/schema/sync-delta-row.js +0 -103
- package/dist/schema/sync-delta-wire.js +0 -102
- package/dist/server/next.d.ts +0 -51
- package/dist/server/next.js +0 -47
- package/dist/server/read-config.d.ts +0 -67
- package/dist/server/read-config.js +0 -8
- package/dist/server/storage-mode.d.ts +0 -1
- package/dist/server/storage-mode.js +0 -18
- package/dist/source/connector-protocol.d.ts +0 -159
- package/dist/source/connector-protocol.js +0 -161
- package/dist/sync/OfflineFlush.d.ts +0 -9
- package/dist/sync/OfflineFlush.js +0 -22
- package/dist/sync/OfflineTransactionStore.d.ts +0 -37
- package/dist/sync/OfflineTransactionStore.js +0 -263
- package/dist/transactions/OptimisticEchoTracker.d.ts +0 -82
- package/dist/transactions/OptimisticEchoTracker.js +0 -104
- package/dist/transactions/index.d.ts +0 -16
- package/dist/transactions/index.js +0 -7
- package/dist/transactions/mutation-error-handler.d.ts +0 -5
- package/dist/transactions/mutation-error-handler.js +0 -39
- package/dist/utils/mobx-setup.d.ts +0 -42
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Owns the sync engine's connection lifecycle: the state machine that carries a
|
|
3
|
+
* client from a healthy connection, through a dropout, and back to live. It
|
|
4
|
+
* watches the browser's online/offline and visibility events, probes real
|
|
5
|
+
* connectivity and session validity with {@link probeNetwork}, applies retry
|
|
6
|
+
* backoff with a ceiling and jitter, and runs a watchdog for the cases where
|
|
7
|
+
* the browser never fires an event (VPN flips, captive portals). When it
|
|
8
|
+
* decides to recover, it drives the reconnect sequence — bootstrap, then
|
|
9
|
+
* WebSocket connect — through the {@link ConnectionCallbacks.onReconnect}
|
|
10
|
+
* callback and reacts to the outcome.
|
|
6
11
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* - Retry backoff with ceiling, jitter, and offline-aware parking
|
|
12
|
-
* - Watchdog for browser events that never fire (VPN, captive portal)
|
|
13
|
-
* - The reconnect → bootstrap → WebSocket connect sequence
|
|
14
|
-
*
|
|
15
|
-
* What it DOES NOT own:
|
|
16
|
-
* - The actual bootstrap / IndexedDB / ObjectPool work — that lives in
|
|
17
|
-
* `BaseSyncedStore.performReconnect()`. This class calls it via the
|
|
18
|
-
* `onReconnect` callback and reacts to the outcome.
|
|
19
|
-
*
|
|
20
|
-
* Designed to be embedded by `BaseSyncedStore`: one instance per store,
|
|
21
|
-
* started on first successful connect, disposed on teardown.
|
|
12
|
+
* It deliberately does not perform the bootstrap, local-storage, or object-pool
|
|
13
|
+
* work itself; that lives in the embedding store, which the manager reaches
|
|
14
|
+
* only through its callbacks. One manager is created per store, started on the
|
|
15
|
+
* first successful connect and disposed on teardown.
|
|
22
16
|
*
|
|
23
17
|
* CONNECTED ──(socket drop)──► PROBING_NETWORK ──► RECONNECTING ──► CONNECTED
|
|
24
18
|
* │ │ │
|
|
@@ -29,20 +23,15 @@
|
|
|
29
23
|
* ▼
|
|
30
24
|
* WAITING_FOR_NETWORK
|
|
31
25
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* 1006 is browser-local and carries no connectivity signal, so on a
|
|
42
|
-
* healthy machine no `online`/`offline` event ever fires — parking in
|
|
43
|
-
* `offline` stranded recovery until the 30s watchdog, long enough for
|
|
44
|
-
* queued commits to roll back. Only a genuine OS-level `NETWORK_LOST`
|
|
45
|
-
* parks in `offline` and waits for the `online` event.
|
|
26
|
+
* Three behaviors are worth calling out. A network return or tab focus during a
|
|
27
|
+
* backoff delay jumps straight to probing instead of waiting out the full
|
|
28
|
+
* interval. Reaching the retry ceiling while the browser reports offline parks
|
|
29
|
+
* in `waiting_for_network` rather than hard-reloading a browser that has no
|
|
30
|
+
* network. And a socket drop (close code 1006) goes straight to probing rather
|
|
31
|
+
* than the passive `offline` state — 1006 is generated locally and carries no
|
|
32
|
+
* connectivity signal, so on a healthy machine no online/offline event ever
|
|
33
|
+
* fires; only a genuine operating-system network loss parks in `offline` to
|
|
34
|
+
* wait for the `online` event.
|
|
46
35
|
*/
|
|
47
36
|
import { makeAutoObservable, runInAction } from 'mobx';
|
|
48
37
|
import { getContext } from '../context.js';
|
|
@@ -158,13 +147,13 @@ export class ConnectionManager {
|
|
|
158
147
|
// work.
|
|
159
148
|
return 'offline';
|
|
160
149
|
case 'WS_DISCONNECTED':
|
|
161
|
-
// The socket died (typically code 1006) but the
|
|
162
|
-
// almost certainly fine — 1006 is generated locally when
|
|
163
|
-
//
|
|
164
|
-
// fires no online/offline event. Probe
|
|
165
|
-
// landing in the passive `offline` dead-end (which only escaped
|
|
166
|
-
// the 30s watchdog, long after queued commits rolled back). The
|
|
167
|
-
// probe fast-fails if we genuinely
|
|
150
|
+
// The socket died (typically code 1006) but the operating-system
|
|
151
|
+
// network is almost certainly fine — 1006 is generated locally when
|
|
152
|
+
// the TCP connection vanishes and carries no connectivity signal, so
|
|
153
|
+
// the browser fires no online/offline event. Probe immediately rather
|
|
154
|
+
// than landing in the passive `offline` dead-end (which only escaped
|
|
155
|
+
// via the 30s watchdog, long after queued commits rolled back). The
|
|
156
|
+
// probe fast-fails if we genuinely are offline → waiting_for_network.
|
|
168
157
|
return 'probing_network';
|
|
169
158
|
case 'WS_SESSION_ERROR':
|
|
170
159
|
case 'BOOTSTRAP_FAILED_SESSION':
|
|
@@ -233,15 +222,15 @@ export class ConnectionManager {
|
|
|
233
222
|
return null;
|
|
234
223
|
}
|
|
235
224
|
case 'refreshing_credential':
|
|
236
|
-
// Re-minting the short-lived access key (the
|
|
237
|
-
//
|
|
225
|
+
// Re-minting the short-lived access key (the `ek_`/`rk_`). The login is
|
|
226
|
+
// presumed valid; this is not a sign-out state.
|
|
238
227
|
switch (event.type) {
|
|
239
228
|
case 'CREDENTIAL_REFRESHED':
|
|
240
229
|
// Fresh key in hand — re-probe so we reconnect with it.
|
|
241
230
|
return 'probing_network';
|
|
242
231
|
case 'BOOTSTRAP_FAILED_SESSION':
|
|
243
232
|
// The re-mint hit a genuine 401/403: the long-lived login itself is
|
|
244
|
-
// gone.
|
|
233
|
+
// gone. This is the only path from here to sign-out.
|
|
245
234
|
return 'session_expired';
|
|
246
235
|
case 'RECONNECT_FAILED':
|
|
247
236
|
// Couldn't reach the mint endpoint (offline/5xx/throw) — transient.
|
|
@@ -336,39 +325,40 @@ export class ConnectionManager {
|
|
|
336
325
|
this.callbacks?.onDisconnectWebSocket();
|
|
337
326
|
break;
|
|
338
327
|
case 'probing_network':
|
|
339
|
-
// A socket drop (`WS_DISCONNECTED`)
|
|
340
|
-
// starts immediately. Tear the dead socket down
|
|
328
|
+
// A socket drop (`WS_DISCONNECTED`) lands here directly so recovery
|
|
329
|
+
// starts immediately. Tear the dead socket down first — that is what
|
|
341
330
|
// sets SyncWebSocket's `isManualClose=true` and suppresses its own
|
|
342
|
-
//
|
|
343
|
-
//
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
// (
|
|
331
|
+
// reconnect, keeping this machine the single reconnect authority on the
|
|
332
|
+
// human path. The teardown runs synchronously inside the `disconnected`
|
|
333
|
+
// emit, before `SyncWebSocket.onclose` checks the flag, so the timing
|
|
334
|
+
// matches the previous `offline`-entry teardown. We gate on the drop
|
|
335
|
+
// event specifically: the other paths into `probing_network`
|
|
336
|
+
// (tab-focus re-validation, handshake retry, backoff elapse) must not
|
|
348
337
|
// tear down a socket that may still be live.
|
|
349
338
|
if (event.type === 'WS_DISCONNECTED') {
|
|
350
339
|
this.callbacks?.onDisconnectWebSocket();
|
|
351
340
|
}
|
|
352
|
-
this.runProbe();
|
|
341
|
+
void this.runProbe().catch(this.captureEffectFailure('probe'));
|
|
353
342
|
break;
|
|
354
343
|
case 'waiting_for_network':
|
|
355
344
|
this.scheduleBackoff();
|
|
356
345
|
break;
|
|
357
346
|
case 'reconnecting':
|
|
358
|
-
this.runReconnect();
|
|
347
|
+
void this.runReconnect().catch(this.captureEffectFailure('reconnect'));
|
|
359
348
|
break;
|
|
360
349
|
case 'refreshing_credential':
|
|
361
|
-
this.runRefreshCredential();
|
|
350
|
+
void this.runRefreshCredential().catch(this.captureEffectFailure('refresh-credential'));
|
|
362
351
|
break;
|
|
363
352
|
case 'backoff':
|
|
364
353
|
this.scheduleBackoff();
|
|
365
354
|
break;
|
|
366
355
|
case 'auth_blocked':
|
|
367
|
-
// Stop — reachable but the credential
|
|
368
|
-
// api_key_required
|
|
369
|
-
// reconnecting nor re-
|
|
370
|
-
// manual retry
|
|
371
|
-
// and
|
|
356
|
+
// Stop here — the server is reachable but rejected the credential (for
|
|
357
|
+
// example api_key_required or jwt_issuer_untrusted from the data plane).
|
|
358
|
+
// Neither reconnecting nor re-authenticating fixes it. Drop the socket
|
|
359
|
+
// and wait for a manual retry or a re-probe. This deliberately does not
|
|
360
|
+
// call onSessionExpired (no sign-out) and does not reconnect — that is
|
|
361
|
+
// the whole point of the state.
|
|
372
362
|
this.clearBackoffTimer();
|
|
373
363
|
this.callbacks?.onDisconnectWebSocket();
|
|
374
364
|
getContext().observability.breadcrumb('Auth blocked — reachable but credential rejected; not reconnecting or signing out', 'sync.offline', 'error');
|
|
@@ -382,6 +372,21 @@ export class ConnectionManager {
|
|
|
382
372
|
}
|
|
383
373
|
}
|
|
384
374
|
// ── Async operations ─────────────────────────────────────────────────
|
|
375
|
+
/**
|
|
376
|
+
* FSM effects are fire-and-forget by design (`onEnterState` is sync). Each
|
|
377
|
+
* effect catches its own operational failures and maps them onto an FSM
|
|
378
|
+
* event, so a rejection here means the machine wedged mid-transition
|
|
379
|
+
* (`send()` or a transition listener threw) with no retry timer armed past
|
|
380
|
+
* it — capture it instead of losing the reconnect loop silently.
|
|
381
|
+
*/
|
|
382
|
+
captureEffectFailure(effect) {
|
|
383
|
+
return (error) => {
|
|
384
|
+
getContext().observability.captureWebSocketError({
|
|
385
|
+
context: `connection-fsm-${effect}`,
|
|
386
|
+
error: error instanceof Error ? error.message : String(error),
|
|
387
|
+
});
|
|
388
|
+
};
|
|
389
|
+
}
|
|
385
390
|
async runProbe() {
|
|
386
391
|
try {
|
|
387
392
|
const result = await probeNetwork({
|
|
@@ -587,16 +592,15 @@ export class ConnectionManager {
|
|
|
587
592
|
this.watchdogTimer = setInterval(() => {
|
|
588
593
|
if (this.disposed)
|
|
589
594
|
return;
|
|
590
|
-
// "Stuck"
|
|
591
|
-
// waiting_for_network, backoff).
|
|
592
|
-
// `navigator.onLine === true
|
|
593
|
-
//
|
|
594
|
-
//
|
|
595
|
-
//
|
|
596
|
-
//
|
|
597
|
-
//
|
|
598
|
-
//
|
|
599
|
-
// so an unconditional retry costs nothing in the genuine case.
|
|
595
|
+
// "Stuck" means parked in a non-active recovery state (offline,
|
|
596
|
+
// waiting_for_network, backoff). This deliberately does not gate on
|
|
597
|
+
// `navigator.onLine === true`: per MDN, `navigator.onLine` is only
|
|
598
|
+
// reliable when it returns false ("definitely offline"), and even that
|
|
599
|
+
// lies after laptop wake or VPN flips. Gating the watchdog on `onLine`
|
|
600
|
+
// was what stranded the machine in `offline` forever — when the browser
|
|
601
|
+
// briefly reported offline and never re-fired the `online` event, there
|
|
602
|
+
// was no escape. The probe itself fast-fails when truly offline, so an
|
|
603
|
+
// unconditional retry costs nothing in the genuine case.
|
|
600
604
|
const isStuck = this.state !== 'connected' &&
|
|
601
605
|
this.state !== 'session_expired' &&
|
|
602
606
|
this.state !== 'probing_network' &&
|
|
@@ -609,7 +613,7 @@ export class ConnectionManager {
|
|
|
609
613
|
// means definitely offline"), and reloading while truly offline
|
|
610
614
|
// would either serve cached content forever or fail and leave
|
|
611
615
|
// the user with a broken page.
|
|
612
|
-
const browserOnline = typeof navigator === 'undefined' || navigator.onLine
|
|
616
|
+
const browserOnline = typeof navigator === 'undefined' || navigator.onLine;
|
|
613
617
|
if (this.stuckCycles >= MAX_STUCK_CYCLES_BEFORE_RELOAD && browserOnline) {
|
|
614
618
|
getContext().logger.debug('[ConnectionManager] Watchdog: sustained stuck — hard reload');
|
|
615
619
|
getContext().observability.breadcrumb('Watchdog hard reload', 'sync.offline', 'error');
|
|
@@ -1,36 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Detects real network and session connectivity for the sync engine. It exists
|
|
3
|
+
* because `navigator.onLine` is unreliable: it reports true whenever the device
|
|
4
|
+
* has a local network connection, even with no route to the internet, and after
|
|
5
|
+
* sleep/wake it can report true before Wi-Fi or DNS are working again.
|
|
3
6
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* This module provides an authenticated probe against the sync server to verify
|
|
9
|
-
* real connectivity + credential validity in a single round-trip. The probe
|
|
10
|
-
* hits `/api/auth/check`, which runs the SAME auth middleware as the WebSocket
|
|
11
|
-
* upgrade path, and classifies the response into a single {@link ProbeOutcome}
|
|
12
|
-
* via the closed recovery taxonomy ({@link classifyRecovery}):
|
|
7
|
+
* The probe makes one authenticated request to the sync server's
|
|
8
|
+
* `/api/auth/check` endpoint — which runs the same auth middleware as the
|
|
9
|
+
* WebSocket upgrade — and classifies the response into a single
|
|
10
|
+
* {@link ProbeOutcome} through the recovery taxonomy ({@link classifyRecovery}):
|
|
13
11
|
* 204 No Content → `reachable` (credential valid)
|
|
14
|
-
* 401 `apikey_expired` (ephemeral key) → `credential_stale` (re-mint
|
|
12
|
+
* 401 `apikey_expired` (ephemeral key) → `credential_stale` (re-mint and retry, no sign-out)
|
|
15
13
|
* 401 `session_expired` / bare 401 → `session_expired` (sign out)
|
|
16
|
-
* 401/403 credential-type/config/perm → `auth_blocked` (stop
|
|
17
|
-
* network
|
|
14
|
+
* 401/403 credential-type/config/perm → `auth_blocked` (stop; no loop, no sign-out)
|
|
15
|
+
* network failure / offline → `unreachable`
|
|
18
16
|
*
|
|
19
|
-
* This closes a real gap
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* the user to sign-in.
|
|
17
|
+
* This closes a real gap. The browser's WebSocket API hides the HTTP status of
|
|
18
|
+
* a failed handshake, so a 401 on the upgrade surfaces only as close code 1006.
|
|
19
|
+
* Without this HTTP probe, the client cannot tell an auth failure from a network
|
|
20
|
+
* blip, and loops reconnecting forever instead of sending the user to sign in.
|
|
24
21
|
*
|
|
25
22
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine
|
|
26
23
|
*/
|
|
27
24
|
import { z } from 'zod';
|
|
28
25
|
import { type AuthTokenGetter } from '../auth/credentialSource.js';
|
|
29
26
|
/**
|
|
30
|
-
* The
|
|
31
|
-
* credential
|
|
32
|
-
*
|
|
33
|
-
*
|
|
27
|
+
* The complete set of probe outcomes. Each value carries both reachability and
|
|
28
|
+
* credential state, so {@link ConnectionManager} can branch on one exhaustive
|
|
29
|
+
* discriminant instead of piecing the situation together from several booleans.
|
|
30
|
+
* It mirrors the {@link RecoveryClass} taxonomy at the connectivity layer.
|
|
34
31
|
*/
|
|
35
32
|
export declare const PROBE_OUTCOMES: readonly ["reachable", "unreachable", "session_expired", "credential_stale", "auth_blocked"];
|
|
36
33
|
/** Zod enum derived from {@link PROBE_OUTCOMES}. */
|
|
@@ -59,8 +56,9 @@ export declare const probeResultSchema: z.ZodObject<{
|
|
|
59
56
|
export type ProbeResult = z.infer<typeof probeResultSchema>;
|
|
60
57
|
export interface NetworkProbeOptions {
|
|
61
58
|
/**
|
|
62
|
-
* Sync-server base URL (HTTP or WS scheme accepted). If omitted,
|
|
63
|
-
*
|
|
59
|
+
* Sync-server base URL (HTTP or WS scheme accepted). If omitted, the probe
|
|
60
|
+
* targets the canonical hosted endpoint ({@link ABLO_DEFAULT_BASE_URL}) —
|
|
61
|
+
* the same default `Ablo()` resolves.
|
|
64
62
|
*/
|
|
65
63
|
baseUrl?: string;
|
|
66
64
|
/**
|
|
@@ -73,13 +71,11 @@ export interface NetworkProbeOptions {
|
|
|
73
71
|
authToken?: string | null;
|
|
74
72
|
}
|
|
75
73
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* ConnectionStore can make the right state transition without guessing.
|
|
74
|
+
* Probes the sync server with a lightweight HEAD request, returning both
|
|
75
|
+
* reachability and session status in a single call so {@link ConnectionManager}
|
|
76
|
+
* can pick the right state transition without guessing.
|
|
80
77
|
*
|
|
81
|
-
* @param input The sync-server base URL (HTTP or WS scheme accepted), or
|
|
82
|
-
* options bag
|
|
83
|
-
* for backwards compatibility.
|
|
78
|
+
* @param input The sync-server base URL (an HTTP or WS scheme is accepted), or
|
|
79
|
+
* an options bag. A bare string is also accepted.
|
|
84
80
|
*/
|
|
85
81
|
export declare function probeNetwork(input?: string | NetworkProbeOptions): Promise<ProbeResult>;
|
|
@@ -1,26 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Detects real network and session connectivity for the sync engine. It exists
|
|
3
|
+
* because `navigator.onLine` is unreliable: it reports true whenever the device
|
|
4
|
+
* has a local network connection, even with no route to the internet, and after
|
|
5
|
+
* sleep/wake it can report true before Wi-Fi or DNS are working again.
|
|
3
6
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* This module provides an authenticated probe against the sync server to verify
|
|
9
|
-
* real connectivity + credential validity in a single round-trip. The probe
|
|
10
|
-
* hits `/api/auth/check`, which runs the SAME auth middleware as the WebSocket
|
|
11
|
-
* upgrade path, and classifies the response into a single {@link ProbeOutcome}
|
|
12
|
-
* via the closed recovery taxonomy ({@link classifyRecovery}):
|
|
7
|
+
* The probe makes one authenticated request to the sync server's
|
|
8
|
+
* `/api/auth/check` endpoint — which runs the same auth middleware as the
|
|
9
|
+
* WebSocket upgrade — and classifies the response into a single
|
|
10
|
+
* {@link ProbeOutcome} through the recovery taxonomy ({@link classifyRecovery}):
|
|
13
11
|
* 204 No Content → `reachable` (credential valid)
|
|
14
|
-
* 401 `apikey_expired` (ephemeral key) → `credential_stale` (re-mint
|
|
12
|
+
* 401 `apikey_expired` (ephemeral key) → `credential_stale` (re-mint and retry, no sign-out)
|
|
15
13
|
* 401 `session_expired` / bare 401 → `session_expired` (sign out)
|
|
16
|
-
* 401/403 credential-type/config/perm → `auth_blocked` (stop
|
|
17
|
-
* network
|
|
14
|
+
* 401/403 credential-type/config/perm → `auth_blocked` (stop; no loop, no sign-out)
|
|
15
|
+
* network failure / offline → `unreachable`
|
|
18
16
|
*
|
|
19
|
-
* This closes a real gap
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* the user to sign-in.
|
|
17
|
+
* This closes a real gap. The browser's WebSocket API hides the HTTP status of
|
|
18
|
+
* a failed handshake, so a 401 on the upgrade surfaces only as close code 1006.
|
|
19
|
+
* Without this HTTP probe, the client cannot tell an auth failure from a network
|
|
20
|
+
* blip, and loops reconnecting forever instead of sending the user to sign in.
|
|
24
21
|
*
|
|
25
22
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine
|
|
26
23
|
*/
|
|
@@ -28,25 +25,26 @@ import { z } from 'zod';
|
|
|
28
25
|
import { getContext } from '../context.js';
|
|
29
26
|
import { classifyRecovery } from '../errors.js';
|
|
30
27
|
import { withAuthHeaders } from '../auth/credentialSource.js';
|
|
28
|
+
import { ABLO_DEFAULT_BASE_URL } from '../client/hostedEndpoints.js';
|
|
31
29
|
/**
|
|
32
|
-
* The
|
|
33
|
-
* credential
|
|
34
|
-
*
|
|
35
|
-
*
|
|
30
|
+
* The complete set of probe outcomes. Each value carries both reachability and
|
|
31
|
+
* credential state, so {@link ConnectionManager} can branch on one exhaustive
|
|
32
|
+
* discriminant instead of piecing the situation together from several booleans.
|
|
33
|
+
* It mirrors the {@link RecoveryClass} taxonomy at the connectivity layer.
|
|
36
34
|
*/
|
|
37
35
|
export const PROBE_OUTCOMES = [
|
|
38
36
|
/** Server reachable and the access credential is currently valid. */
|
|
39
37
|
'reachable',
|
|
40
|
-
/** Could not reach the server (offline
|
|
38
|
+
/** Could not reach the server (offline, DNS, TLS, or timeout). */
|
|
41
39
|
'unreachable',
|
|
42
|
-
/** Reachable, but the long-lived login is gone
|
|
40
|
+
/** Reachable, but the long-lived login is gone. Terminal: sign out. */
|
|
43
41
|
'session_expired',
|
|
44
|
-
/** Reachable, but the ephemeral access key (`ek_`/`rk_`) expired
|
|
45
|
-
* re-mint a fresh key from the still-valid login and retry
|
|
42
|
+
/** Reachable, but the ephemeral access key (`ek_`/`rk_`) expired. Silently
|
|
43
|
+
* re-mint a fresh key from the still-valid login and retry; not a sign-out. */
|
|
46
44
|
'credential_stale',
|
|
47
|
-
/** Reachable, but the credential
|
|
48
|
-
* untrusted issuer, no
|
|
49
|
-
* helps. Distinct from a sign-out. */
|
|
45
|
+
/** Reachable, but the credential's type or configuration was rejected (wrong
|
|
46
|
+
* key kind, untrusted issuer, no organization, or a 403). Stop: neither
|
|
47
|
+
* reconnecting nor re-authenticating helps. Distinct from a sign-out. */
|
|
50
48
|
'auth_blocked',
|
|
51
49
|
];
|
|
52
50
|
/** Zod enum derived from {@link PROBE_OUTCOMES}. */
|
|
@@ -61,44 +59,40 @@ const PROBE_TIMEOUT_MS = 4000;
|
|
|
61
59
|
/**
|
|
62
60
|
* Derive the probe URL from a sync-server base URL. Accepts `ws://`,
|
|
63
61
|
* `wss://`, `http://`, `https://`, or a bare host — mirrors the
|
|
64
|
-
* normalisation in `
|
|
62
|
+
* normalisation in `BootstrapFetcher` / `createSyncEngine`.
|
|
65
63
|
*/
|
|
66
64
|
function resolveProbeUrl(baseUrl) {
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
const resolved = baseUrl ??
|
|
70
|
-
(typeof process !== 'undefined' ? process.env?.NEXT_PUBLIC_GO_SERVER_URL : undefined) ??
|
|
71
|
-
'http://localhost:8080';
|
|
65
|
+
// No explicit baseUrl → probe the canonical hosted endpoint, matching the
|
|
66
|
+
// `Ablo()` default.
|
|
67
|
+
const resolved = baseUrl ?? ABLO_DEFAULT_BASE_URL;
|
|
72
68
|
// Normalize ws → http so fetch() accepts the URL. Strip any trailing slash
|
|
73
69
|
// so we don't produce `//api/auth/check`.
|
|
74
70
|
const httpBase = resolved.replace(/^ws/, 'http').replace(/\/+$/, '');
|
|
75
71
|
return `${httpBase}/api/auth/check`;
|
|
76
72
|
}
|
|
77
73
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* ConnectionStore can make the right state transition without guessing.
|
|
74
|
+
* Probes the sync server with a lightweight HEAD request, returning both
|
|
75
|
+
* reachability and session status in a single call so {@link ConnectionManager}
|
|
76
|
+
* can pick the right state transition without guessing.
|
|
82
77
|
*
|
|
83
|
-
* @param input The sync-server base URL (HTTP or WS scheme accepted), or
|
|
84
|
-
* options bag
|
|
85
|
-
* for backwards compatibility.
|
|
78
|
+
* @param input The sync-server base URL (an HTTP or WS scheme is accepted), or
|
|
79
|
+
* an options bag. A bare string is also accepted.
|
|
86
80
|
*/
|
|
87
81
|
export async function probeNetwork(input) {
|
|
88
82
|
const baseUrl = typeof input === 'string' ? input : input?.baseUrl;
|
|
89
83
|
const getAuthToken = typeof input === 'string' ? undefined : input?.getAuthToken;
|
|
90
84
|
const authToken = typeof input === 'string' ? undefined : input?.authToken;
|
|
91
85
|
const url = resolveProbeUrl(baseUrl);
|
|
92
|
-
// Fast-fail: if navigator.onLine is false, skip the probe entirely.
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
86
|
+
// Fast-fail: if navigator.onLine is false, skip the probe entirely. This is
|
|
87
|
+
// the one case where navigator.onLine is reliable (MDN: "false means
|
|
88
|
+
// definitely offline"). Use `=== false` rather than `!onLine` because Node
|
|
89
|
+
// 22+ exposes `navigator` with `onLine === undefined`, and `!undefined` is
|
|
90
|
+
// true, which would short-circuit the probe server-side.
|
|
97
91
|
if (typeof navigator !== 'undefined' && navigator.onLine === false) {
|
|
98
92
|
return { outcome: 'unreachable', latencyMs: null };
|
|
99
93
|
}
|
|
100
94
|
const controller = new AbortController();
|
|
101
|
-
const timeout = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
|
|
95
|
+
const timeout = setTimeout(() => { controller.abort(); }, PROBE_TIMEOUT_MS);
|
|
102
96
|
const start = performance.now();
|
|
103
97
|
try {
|
|
104
98
|
const headers = withAuthHeaders(getAuthToken, { 'Cache-Control': 'no-cache' }, authToken);
|
|
@@ -109,13 +103,13 @@ export async function probeNetwork(input) {
|
|
|
109
103
|
headers,
|
|
110
104
|
});
|
|
111
105
|
const latencyMs = Math.round(performance.now() - start);
|
|
112
|
-
// The probe is a HEAD (no body), but the
|
|
113
|
-
// <code>` on every auth rejection. Route the code through
|
|
114
|
-
// recovery taxonomy so each failure mode gets its correct outcome
|
|
115
|
-
//
|
|
116
|
-
// (`access_credential_expiry`) must re-mint,
|
|
117
|
-
//
|
|
118
|
-
// credential
|
|
106
|
+
// The probe is a HEAD request (no body), but the server sets
|
|
107
|
+
// `X-Auth-Failure: <code>` on every auth rejection. Route the code through
|
|
108
|
+
// the recovery taxonomy so each failure mode gets its correct outcome. That
|
|
109
|
+
// distinction is the whole point: an expired ephemeral key
|
|
110
|
+
// (`access_credential_expiry`) must re-mint, not sign the user out the way a
|
|
111
|
+
// genuine login expiry (`session_expiry`) does, and not wedge the way a
|
|
112
|
+
// credential type or configuration rejection (`auth_blocked`) does.
|
|
119
113
|
const authFailure = response.headers.get('x-auth-failure');
|
|
120
114
|
if (authFailure) {
|
|
121
115
|
const recovery = classifyRecovery(authFailure);
|
|
@@ -137,10 +131,11 @@ export async function probeNetwork(input) {
|
|
|
137
131
|
case 'auth_blocked':
|
|
138
132
|
case 'permission':
|
|
139
133
|
case 'none':
|
|
140
|
-
// A non-expiry auth rejection — wrong credential type
|
|
141
|
-
// or an auth-tagged code this SDK
|
|
142
|
-
// the same rejected credential and
|
|
143
|
-
// rather than reconnect-loop or sign
|
|
134
|
+
// A non-expiry auth rejection — wrong credential type or config, a
|
|
135
|
+
// 403, or an auth-tagged code this SDK does not recognise.
|
|
136
|
+
// Re-authenticating re-mints the same rejected credential and
|
|
137
|
+
// retrying will not help, so stop rather than reconnect-loop or sign
|
|
138
|
+
// the user out.
|
|
144
139
|
getContext().logger.debug('[NetworkProbe] Reachable but auth-blocked (non-retryable, non-expiry)', {
|
|
145
140
|
status: response.status,
|
|
146
141
|
code: authFailure,
|
|
@@ -159,20 +154,20 @@ export async function probeNetwork(input) {
|
|
|
159
154
|
}
|
|
160
155
|
}
|
|
161
156
|
else if (response.status === 401) {
|
|
162
|
-
// Bare 401 with no
|
|
163
|
-
//
|
|
157
|
+
// Bare 401 with no readable structured code. This is ambiguous and must
|
|
158
|
+
// not sign the user out on its own — two common causes are both
|
|
164
159
|
// recoverable, and only one is a real logout:
|
|
165
|
-
// 1. The server
|
|
166
|
-
// custom header on a cross-origin response
|
|
167
|
-
//
|
|
168
|
-
// null
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
160
|
+
// 1. The server did send `X-Auth-Failure: apikey_expired`, but it is a
|
|
161
|
+
// custom header on a cross-origin response the server did not list in
|
|
162
|
+
// `Access-Control-Expose-Headers`, so the browser stripped it to
|
|
163
|
+
// null. The access key just needs a re-mint.
|
|
164
|
+
// 2. A genuinely expired access key on a non-Ablo proxy or cookie path.
|
|
165
|
+
// So route to `credential_stale`: the state machine attempts a re-mint,
|
|
166
|
+
// and the only way to actually sign out is that re-mint resolving `null`
|
|
167
|
+
// (the login is truly gone). If no refresher is wired, the bounded attempt
|
|
168
|
+
// counter falls through to `auth_blocked` (stop) — still never a spurious
|
|
169
|
+
// logout. The invariant holds: null is the only terminal path, never a
|
|
170
|
+
// bare 401.
|
|
176
171
|
getContext().logger.info('[NetworkProbe] Server reachable, bare 401 — re-mint (not sign-out)', {
|
|
177
172
|
latencyMs,
|
|
178
173
|
});
|