musora-content-services 2.125.0 → 2.126.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/.coderabbit.yaml +0 -0
- package/.editorconfig +0 -0
- package/.github/pull_request_template.md +0 -0
- package/.github/workflows/conventional-commits.yaml +0 -0
- package/.github/workflows/docs.js.yml +0 -0
- package/.github/workflows/node.js.yml +0 -0
- package/.prettierignore +0 -0
- package/.prettierrc +0 -0
- package/CHANGELOG.md +8 -0
- package/README.md +0 -0
- package/babel.config.cjs +0 -0
- package/jest.config.js +0 -0
- package/jsdoc.json +0 -0
- package/package.json +1 -1
- package/src/constants/award-assets.js +0 -0
- package/src/constants/membership-permissions.ts +0 -0
- package/src/filterBuilder.js +0 -0
- package/src/infrastructure/http/HttpClient.ts +0 -0
- package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
- package/src/infrastructure/http/index.ts +0 -0
- package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
- package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
- package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
- package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
- package/src/infrastructure/http/interfaces/RequestOptions.ts +0 -0
- package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
- package/src/lib/ads/monoid.ts +0 -0
- package/src/lib/ads/semigroup.ts +0 -0
- package/src/lib/brands.ts +0 -0
- package/src/lib/lastUpdated.js +0 -0
- package/src/lib/sanity/filter.ts +0 -0
- package/src/lib/sanity/query.ts +0 -0
- package/src/services/api/types.js +0 -0
- package/src/services/api/types.ts +0 -0
- package/src/services/awards/award-callbacks.js +0 -0
- package/src/services/awards/internal/.indexignore +0 -0
- package/src/services/awards/internal/award-definitions.js +0 -0
- package/src/services/awards/internal/award-events.js +0 -0
- package/src/services/awards/internal/award-manager.js +0 -0
- package/src/services/awards/internal/certificate-builder.js +0 -0
- package/src/services/awards/internal/completion-data-generator.js +0 -0
- package/src/services/awards/internal/content-progress-observer.js +0 -0
- package/src/services/awards/internal/image-utils.js +0 -0
- package/src/services/awards/internal/message-generator.js +0 -0
- package/src/services/awards/internal/types.js +0 -0
- package/src/services/awards/types.d.ts +0 -0
- package/src/services/awards/types.js +0 -0
- package/src/services/config.js +0 -0
- package/src/services/content/artist.ts +0 -0
- package/src/services/content/content.ts +0 -0
- package/src/services/content/genre.ts +0 -0
- package/src/services/content/instructor.ts +0 -0
- package/src/services/content-org/content-org.js +0 -0
- package/src/services/content-org/guided-courses.ts +0 -0
- package/src/services/content-org/playlists-types.js +0 -0
- package/src/services/content-org/playlists.js +0 -0
- package/src/services/contentAggregator.js +0 -0
- package/src/services/contentLikes.js +0 -0
- package/src/services/dataContext.js +0 -0
- package/src/services/dateUtils.js +0 -0
- package/src/services/eventsAPI.js +0 -0
- package/src/services/forums/categories.ts +0 -0
- package/src/services/forums/forums.ts +0 -0
- package/src/services/forums/threads.ts +0 -0
- package/src/services/forums/types.ts +0 -0
- package/src/services/gamification/awards.ts +0 -0
- package/src/services/gamification/gamification.js +0 -0
- package/src/services/imageSRCBuilder.js +0 -0
- package/src/services/imageSRCVerify.js +0 -0
- package/src/services/liveTesting.ts +0 -0
- package/src/services/permissions/PermissionsAdapterFactory.ts +0 -0
- package/src/services/permissions/PermissionsV1Adapter.ts +0 -0
- package/src/services/permissions/PermissionsV2Adapter.ts +0 -0
- package/src/services/permissions/README.md +0 -0
- package/src/services/permissions/index.ts +0 -0
- package/src/services/progress-events.js +0 -0
- package/src/services/progress-row/rows/.indexignore +0 -0
- package/src/services/progress-row/rows/method-card.js +0 -0
- package/src/services/railcontent.js +0 -0
- package/src/services/reporting/README.md +0 -0
- package/src/services/reporting/types.ts +0 -0
- package/src/services/sentry/.indexignore +0 -0
- package/src/services/sentry/index.ts +0 -0
- package/src/services/sync/.indexignore +0 -0
- package/src/services/sync/adapters/factory.ts +0 -0
- package/src/services/sync/adapters/lokijs.ts +0 -0
- package/src/services/sync/adapters/sqlite.ts +0 -0
- package/src/services/sync/context/index.ts +0 -0
- package/src/services/sync/context/providers/base.ts +0 -0
- package/src/services/sync/context/providers/connectivity.ts +0 -0
- package/src/services/sync/context/providers/durability.ts +0 -0
- package/src/services/sync/context/providers/index.ts +0 -0
- package/src/services/sync/context/providers/session.ts +0 -0
- package/src/services/sync/context/providers/tabs.ts +0 -0
- package/src/services/sync/context/providers/visibility.ts +0 -0
- package/src/services/sync/database/factory.ts +0 -0
- package/src/services/sync/effects/index.ts +1 -0
- package/src/services/sync/effects/logout-warning.ts +0 -0
- package/src/services/sync/effects/push-failure-notification.ts +34 -0
- package/src/services/sync/errors/boundary.ts +0 -0
- package/src/services/sync/errors/index.ts +0 -0
- package/src/services/sync/errors/validators.ts +0 -0
- package/src/services/sync/fetch.ts +83 -14
- package/src/services/sync/index.ts +0 -0
- package/src/services/sync/manager.ts +4 -0
- package/src/services/sync/models/Base.ts +0 -0
- package/src/services/sync/models/ContentLike.ts +0 -0
- package/src/services/sync/models/ContentProgress.ts +0 -0
- package/src/services/sync/models/Practice.ts +0 -0
- package/src/services/sync/models/PracticeDayNote.ts +0 -0
- package/src/services/sync/models/UserAwardProgress.ts +0 -0
- package/src/services/sync/models/index.ts +0 -0
- package/src/services/sync/repositories/base.ts +0 -0
- package/src/services/sync/repositories/content-likes.ts +0 -0
- package/src/services/sync/repositories/index.ts +0 -0
- package/src/services/sync/repositories/practice-day-notes.ts +0 -0
- package/src/services/sync/repositories/practices.ts +0 -0
- package/src/services/sync/repositories/user-award-progress.ts +0 -0
- package/src/services/sync/repository-proxy.ts +0 -0
- package/src/services/sync/retry.ts +12 -4
- package/src/services/sync/run-scope.ts +0 -0
- package/src/services/sync/schema/index.ts +0 -0
- package/src/services/sync/serializers/index.ts +0 -0
- package/src/services/sync/serializers/model.ts +0 -0
- package/src/services/sync/serializers/raw.ts +0 -0
- package/src/services/sync/store/index.ts +26 -17
- package/src/services/sync/store/push-coalescer.ts +0 -0
- package/src/services/sync/store-configs.ts +0 -0
- package/src/services/sync/strategies/base.ts +0 -0
- package/src/services/sync/strategies/index.ts +0 -0
- package/src/services/sync/strategies/initial.ts +0 -0
- package/src/services/sync/strategies/polling.ts +0 -0
- package/src/services/sync/telemetry/flood-prevention.ts +0 -0
- package/src/services/sync/telemetry/index.ts +5 -3
- package/src/services/sync/telemetry/sampling.ts +0 -0
- package/src/services/sync/utils/event-emitter.ts +0 -0
- package/src/services/sync/utils/index.ts +0 -0
- package/src/services/sync/utils/throttle.ts +0 -0
- package/src/services/sync/utils/timers.ts +0 -0
- package/src/services/types.js +0 -0
- package/src/services/user/account.ts +0 -0
- package/src/services/user/chat.js +0 -0
- package/src/services/user/interests.js +0 -0
- package/src/services/user/management.js +0 -0
- package/src/services/user/memberships.ts +0 -0
- package/src/services/user/notifications.js +0 -0
- package/src/services/user/onboarding.ts +0 -0
- package/src/services/user/payments.ts +0 -0
- package/src/services/user/permissions.js +0 -0
- package/src/services/user/profile.js +0 -0
- package/src/services/user/streakCalculator.ts +0 -0
- package/src/services/user/types.d.ts +0 -0
- package/src/services/user/types.js +0 -0
- package/src/services/user/user-management-system.js +0 -0
- package/test/HttpClient.test.js +0 -0
- package/test/awards/award-alacarte-observer.test.js +0 -0
- package/test/awards/award-auto-refresh.test.js +0 -0
- package/test/awards/award-calculations.test.js +0 -0
- package/test/awards/award-certificate-display.test.js +0 -0
- package/test/awards/award-collection-edge-cases.test.js +0 -0
- package/test/awards/award-collection-filtering.test.js +0 -0
- package/test/awards/award-completion-flow.test.js +0 -0
- package/test/awards/award-exclusion-handling.test.js +0 -0
- package/test/awards/award-multi-lesson.test.js +0 -0
- package/test/awards/award-observer-integration.test.js +0 -0
- package/test/awards/award-query-messages.test.js +0 -0
- package/test/awards/award-user-collection.test.js +0 -0
- package/test/awards/duplicate-prevention.test.js +0 -0
- package/test/awards/helpers/completion-mock.js +0 -0
- package/test/awards/helpers/index.js +0 -0
- package/test/awards/helpers/mock-setup.js +0 -0
- package/test/awards/helpers/progress-emitter.js +0 -0
- package/test/awards/message-generator.test.js +0 -0
- package/test/content.test.js +0 -0
- package/test/contentLikes.test.js +0 -0
- package/test/contentProgress.test.js +0 -0
- package/test/dataContext.test.js +0 -0
- package/test/forum.test.js +0 -0
- package/test/imageSRCBuilder.test.js +0 -0
- package/test/imageSRCVerify.test.js +0 -0
- package/test/initializeTests.js +0 -0
- package/test/learningPaths.test.js +0 -0
- package/test/lib/__snapshots__/filter.test.ts.snap +0 -0
- package/test/lib/filter.test.ts +0 -0
- package/test/lib/lastUpdated.test.js +0 -0
- package/test/lib/query.test.ts +0 -0
- package/test/live/contentProgressLive.test.js +0 -0
- package/test/live/railcontentLive.test.js +0 -0
- package/test/localStorageMock.js +0 -0
- package/test/log.js +0 -0
- package/test/mockData/award-definitions.js +0 -0
- package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
- package/test/mockData/mockData_progress_content.json +0 -0
- package/test/mockData/mockData_sanity_progress_content.json +0 -0
- package/test/mockData/mockData_user_practices.json +0 -0
- package/test/notifications.test.js +0 -0
- package/test/progressRows.test.js +0 -0
- package/test/sanityQueryService.test.js +0 -0
- package/test/streakMessage.test.js +0 -0
- package/test/sync/adapter.ts +0 -0
- package/test/sync/models/award-database-integration.test.js +0 -0
- package/test/user/permissions.test.js +0 -0
- package/test/userActivity.test.js +0 -0
- package/tools/generate-index.cjs +0 -0
- package/.claude/settings.local.json +0 -8
- package/TECHNICAL_DEBT.md +0 -482
- package/url, +0 -0
package/.coderabbit.yaml
CHANGED
|
File without changes
|
package/.editorconfig
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/.prettierignore
CHANGED
|
File without changes
|
package/.prettierrc
CHANGED
|
File without changes
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [2.126.0](https://github.com/railroadmedia/musora-content-services/compare/v2.125.0...v2.126.0) (2026-01-29)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* adds sync store push blocking ([#754](https://github.com/railroadmedia/musora-content-services/issues/754)) ([e9a7c23](https://github.com/railroadmedia/musora-content-services/commit/e9a7c23c91f5e707c08c4e0765af52213ba6263b))
|
|
11
|
+
* melon push failure notifs ([#753](https://github.com/railroadmedia/musora-content-services/issues/753)) ([cce60ad](https://github.com/railroadmedia/musora-content-services/commit/cce60ad3e618106473fd0c7da141aebaced835d5))
|
|
12
|
+
|
|
5
13
|
## [2.125.0](https://github.com/railroadmedia/musora-content-services/compare/v2.122.7...v2.125.0) (2026-01-29)
|
|
6
14
|
|
|
7
15
|
|
package/README.md
CHANGED
|
File without changes
|
package/babel.config.cjs
CHANGED
|
File without changes
|
package/jest.config.js
CHANGED
|
File without changes
|
package/jsdoc.json
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
package/src/filterBuilder.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/lib/ads/monoid.ts
CHANGED
|
File without changes
|
package/src/lib/ads/semigroup.ts
CHANGED
|
File without changes
|
package/src/lib/brands.ts
CHANGED
|
File without changes
|
package/src/lib/lastUpdated.js
CHANGED
|
File without changes
|
package/src/lib/sanity/filter.ts
CHANGED
|
File without changes
|
package/src/lib/sanity/query.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/services/config.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -4,3 +4,4 @@ import type SyncStore from "../store"
|
|
|
4
4
|
export type SyncEffect = (context: SyncContext, stores: SyncStore[]) => () => void
|
|
5
5
|
|
|
6
6
|
export { default as createLogoutWarningEffect } from './logout-warning'
|
|
7
|
+
export { default as createPushFailureNotificationEffect } from './push-failure-notification'
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type SyncEffect } from '.'
|
|
2
|
+
|
|
3
|
+
const NOTIFICATION_COOLDOWN = 60_000 * 10 // 10 mins
|
|
4
|
+
const MUTE_PERIOD = 60_000 * 60 * 3 // 3 hours
|
|
5
|
+
|
|
6
|
+
const createPushFailureNotificationEffect = (notifyCallback: (opts: { mute: () => void }) => void) => {
|
|
7
|
+
let lastNotifiedAt = 0
|
|
8
|
+
let mutedUntil = 0
|
|
9
|
+
|
|
10
|
+
const mute = () => {
|
|
11
|
+
mutedUntil = Date.now() + MUTE_PERIOD
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const pushFailureToast: SyncEffect = function (_context, stores) {
|
|
15
|
+
const maybeNotify = () => {
|
|
16
|
+
const now = Date.now()
|
|
17
|
+
if (now - lastNotifiedAt < NOTIFICATION_COOLDOWN) return
|
|
18
|
+
if (mutedUntil && now < mutedUntil) return
|
|
19
|
+
|
|
20
|
+
lastNotifiedAt = now
|
|
21
|
+
notifyCallback({ mute })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const teardowns = stores.map((store) => store.on('failedPush', maybeNotify))
|
|
25
|
+
|
|
26
|
+
return () => {
|
|
27
|
+
teardowns.forEach((teardown) => teardown())
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return pushFailureToast
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default createPushFailureNotificationEffect
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -4,7 +4,27 @@ import { EpochMs } from "."
|
|
|
4
4
|
import { globalConfig } from '../config.js'
|
|
5
5
|
import { RecordId } from "@nozbe/watermelondb"
|
|
6
6
|
import BaseModel from "./models/Base"
|
|
7
|
-
import
|
|
7
|
+
import SyncContext from "./context"
|
|
8
|
+
import { SyncTelemetry } from "./telemetry"
|
|
9
|
+
|
|
10
|
+
export type BlockingState = {
|
|
11
|
+
enabled: boolean
|
|
12
|
+
}
|
|
13
|
+
export type SyncPull = (
|
|
14
|
+
tableName: string,
|
|
15
|
+
intendedUserId: number,
|
|
16
|
+
context: SyncContext,
|
|
17
|
+
signal: AbortSignal,
|
|
18
|
+
previousFetchToken: SyncToken | null
|
|
19
|
+
) => Promise<SyncPullResponse>
|
|
20
|
+
export type SyncPush = (
|
|
21
|
+
tableName: string,
|
|
22
|
+
intendedUserId: number,
|
|
23
|
+
context: SyncContext,
|
|
24
|
+
payload: PushPayload,
|
|
25
|
+
signal: AbortSignal,
|
|
26
|
+
blockingState: BlockingState
|
|
27
|
+
) => Promise<SyncPushResponse>
|
|
8
28
|
|
|
9
29
|
interface RawPullResponse {
|
|
10
30
|
meta: {
|
|
@@ -23,7 +43,7 @@ interface RawPushResponse {
|
|
|
23
43
|
}
|
|
24
44
|
|
|
25
45
|
export type SyncResponse = SyncPushResponse | SyncPullResponse
|
|
26
|
-
export type SyncPushResponse = SyncPushSuccessResponse | SyncPushFetchFailureResponse | SyncPushFailureResponse
|
|
46
|
+
export type SyncPushResponse = SyncPushSuccessResponse | SyncPushFetchFailureResponse | SyncPushAbortResponse | SyncPushBlockedResponse | SyncPushFailureResponse
|
|
27
47
|
|
|
28
48
|
type SyncPushSuccessResponse = SyncResponseBase & {
|
|
29
49
|
ok: true
|
|
@@ -34,6 +54,15 @@ type SyncPushFetchFailureResponse = SyncResponseBase & {
|
|
|
34
54
|
failureType: 'fetch'
|
|
35
55
|
isRetryable: boolean
|
|
36
56
|
}
|
|
57
|
+
type SyncPushAbortResponse = SyncResponseBase & {
|
|
58
|
+
ok: false,
|
|
59
|
+
failureType: 'abort'
|
|
60
|
+
}
|
|
61
|
+
type SyncPushBlockedResponse = SyncResponseBase & {
|
|
62
|
+
ok: false,
|
|
63
|
+
failureType: 'blocked'
|
|
64
|
+
isRetryable: boolean
|
|
65
|
+
}
|
|
37
66
|
type SyncPushFailureResponse = SyncResponseBase & {
|
|
38
67
|
ok: false,
|
|
39
68
|
failureType: 'error'
|
|
@@ -63,7 +92,7 @@ interface SyncStorePushResultBase {
|
|
|
63
92
|
type: 'success' | 'failure'
|
|
64
93
|
}
|
|
65
94
|
|
|
66
|
-
export type SyncPullResponse = SyncPullSuccessResponse | SyncPullFailureResponse | SyncPullFetchFailureResponse
|
|
95
|
+
export type SyncPullResponse = SyncPullSuccessResponse | SyncPullFailureResponse | SyncPullAbortResponse | SyncPullFetchFailureResponse
|
|
67
96
|
|
|
68
97
|
type SyncPullSuccessResponse = SyncResponseBase & {
|
|
69
98
|
ok: true
|
|
@@ -82,6 +111,10 @@ type SyncPullFailureResponse = SyncResponseBase & {
|
|
|
82
111
|
failureType: 'error'
|
|
83
112
|
originalError: Error
|
|
84
113
|
}
|
|
114
|
+
type SyncPullAbortResponse = SyncResponseBase & {
|
|
115
|
+
ok: false,
|
|
116
|
+
failureType: 'abort'
|
|
117
|
+
}
|
|
85
118
|
export interface SyncResponseBase {
|
|
86
119
|
ok: boolean
|
|
87
120
|
}
|
|
@@ -118,8 +151,8 @@ interface ServerPushPayload {
|
|
|
118
151
|
}[]
|
|
119
152
|
}
|
|
120
153
|
|
|
121
|
-
export function makeFetchRequest(input: RequestInfo, init?: RequestInit): (userId: number,
|
|
122
|
-
return (userId,
|
|
154
|
+
export function makeFetchRequest(input: RequestInfo, init?: RequestInit): (userId: number, context: SyncContext) => Request {
|
|
155
|
+
return (userId, context) => new Request(globalConfig.baseUrl + input, {
|
|
123
156
|
...init,
|
|
124
157
|
headers: {
|
|
125
158
|
...init?.headers,
|
|
@@ -127,18 +160,18 @@ export function makeFetchRequest(input: RequestInfo, init?: RequestInit): (userI
|
|
|
127
160
|
'Authorization': `Bearer ${globalConfig.sessionConfig.token}`
|
|
128
161
|
} : {},
|
|
129
162
|
'Content-Type': 'application/json',
|
|
130
|
-
'X-Sync-Client-Id': session.getClientId(),
|
|
131
|
-
...(session.getSessionId() ? {
|
|
132
|
-
'X-Sync-Client-Session-Id': session.getSessionId()!
|
|
163
|
+
'X-Sync-Client-Id': context.session.getClientId(),
|
|
164
|
+
...(context.session.getSessionId() ? {
|
|
165
|
+
'X-Sync-Client-Session-Id': context.session.getSessionId()!
|
|
133
166
|
} : {}),
|
|
134
167
|
'X-Sync-Intended-User-Id': userId.toString()
|
|
135
168
|
}
|
|
136
169
|
})
|
|
137
170
|
}
|
|
138
171
|
|
|
139
|
-
export function handlePull(callback: (userId: number,
|
|
140
|
-
return async function(
|
|
141
|
-
const generatedRequest = callback(userId,
|
|
172
|
+
export function handlePull(callback: (userId: number, context: SyncContext) => Request): SyncPull {
|
|
173
|
+
return async function(_tableName, userId, context, signal, lastFetchToken) {
|
|
174
|
+
const generatedRequest = callback(userId, context)
|
|
142
175
|
const url = serializePullUrlQuery(generatedRequest.url, lastFetchToken)
|
|
143
176
|
const request = new Request(url, {
|
|
144
177
|
credentials: 'include',
|
|
@@ -150,6 +183,19 @@ export function handlePull(callback: (userId: number, session: BaseSessionProvid
|
|
|
150
183
|
try {
|
|
151
184
|
response = await fetch(request)
|
|
152
185
|
} catch (e) {
|
|
186
|
+
if (e.name === 'AbortError') {
|
|
187
|
+
return {
|
|
188
|
+
ok: false,
|
|
189
|
+
failureType: 'abort'
|
|
190
|
+
}
|
|
191
|
+
} else if (e instanceof TypeError) {
|
|
192
|
+
return {
|
|
193
|
+
ok: false,
|
|
194
|
+
failureType: 'fetch',
|
|
195
|
+
isRetryable: context.connectivity.getValue() !== false
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
153
199
|
return {
|
|
154
200
|
ok: false,
|
|
155
201
|
failureType: 'error',
|
|
@@ -190,9 +236,9 @@ export function handlePull(callback: (userId: number, session: BaseSessionProvid
|
|
|
190
236
|
}
|
|
191
237
|
}
|
|
192
238
|
|
|
193
|
-
export function handlePush(callback: (userId: number,
|
|
194
|
-
return async function(userId
|
|
195
|
-
const generatedRequest = callback(userId,
|
|
239
|
+
export function handlePush(callback: (userId: number, context: SyncContext) => Request): SyncPush {
|
|
240
|
+
return async function(tableName, userId, context, payload, signal, blockingState) {
|
|
241
|
+
const generatedRequest = callback(userId, context)
|
|
196
242
|
const serverPayload = serializePushPayload(payload)
|
|
197
243
|
const request = new Request(generatedRequest, {
|
|
198
244
|
credentials: 'include',
|
|
@@ -200,10 +246,33 @@ export function handlePush(callback: (userId: number, session: BaseSessionProvid
|
|
|
200
246
|
signal
|
|
201
247
|
})
|
|
202
248
|
|
|
249
|
+
if (blockingState.enabled) {
|
|
250
|
+
SyncTelemetry.getInstance().debug(`[sync:${tableName}] Push blocked`)
|
|
251
|
+
await new Promise(resolve => setTimeout(resolve, Math.random() * 1000 + 30))
|
|
252
|
+
return {
|
|
253
|
+
ok: false,
|
|
254
|
+
failureType: 'blocked',
|
|
255
|
+
isRetryable: true
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
203
259
|
let response: Response | null = null
|
|
204
260
|
try {
|
|
205
261
|
response = await fetch(request)
|
|
206
262
|
} catch (e) {
|
|
263
|
+
if (e.name === 'AbortError') {
|
|
264
|
+
return {
|
|
265
|
+
ok: false,
|
|
266
|
+
failureType: 'abort'
|
|
267
|
+
}
|
|
268
|
+
} else if (e instanceof TypeError) {
|
|
269
|
+
return {
|
|
270
|
+
ok: false,
|
|
271
|
+
failureType: 'fetch',
|
|
272
|
+
isRetryable: context.connectivity.getValue() !== false
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
207
276
|
return {
|
|
208
277
|
ok: false,
|
|
209
278
|
failureType: 'error',
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -11,7 +11,7 @@ export default class SyncRetry {
|
|
|
11
11
|
private backoffUntil = 0
|
|
12
12
|
private failureCount = 0
|
|
13
13
|
|
|
14
|
-
private unsubscribeConnectivity: () => void
|
|
14
|
+
private unsubscribeConnectivity: (() => void) | null = null
|
|
15
15
|
|
|
16
16
|
constructor(private readonly context: SyncContext, private readonly telemetry: SyncTelemetry) {}
|
|
17
17
|
|
|
@@ -26,13 +26,18 @@ export default class SyncRetry {
|
|
|
26
26
|
|
|
27
27
|
stop() {
|
|
28
28
|
this.unsubscribeConnectivity?.()
|
|
29
|
+
this.unsubscribeConnectivity = null
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* Runs the given syncFn with automatic retries.
|
|
33
34
|
* Returns the first successful result or the last failed result after retries.
|
|
34
35
|
*/
|
|
35
|
-
async request<T extends SyncResponse>(
|
|
36
|
+
async request<T extends SyncResponse>(
|
|
37
|
+
spanOpts: StartSpanOptions,
|
|
38
|
+
syncFn: (span: Span) => Promise<T>,
|
|
39
|
+
options: { onFail?: () => void } = {}
|
|
40
|
+
) {
|
|
36
41
|
let attempt = 0
|
|
37
42
|
|
|
38
43
|
while (true) {
|
|
@@ -64,9 +69,12 @@ export default class SyncRetry {
|
|
|
64
69
|
this.resetBackoff()
|
|
65
70
|
return result
|
|
66
71
|
} else {
|
|
67
|
-
if (
|
|
72
|
+
if ('isRetryable' in result && result.isRetryable) {
|
|
68
73
|
this.scheduleBackoff()
|
|
69
|
-
if (attempt >= this.MAX_ATTEMPTS)
|
|
74
|
+
if (attempt >= this.MAX_ATTEMPTS) {
|
|
75
|
+
options.onFail?.()
|
|
76
|
+
return result
|
|
77
|
+
}
|
|
70
78
|
} else {
|
|
71
79
|
return result
|
|
72
80
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|