@taskforcehq/taskforce 0.3.303 → 0.3.304
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/dist/Taskforce.module.css +102 -5
- package/dist/TaskforceCore.js +514 -212
- package/dist/TaskforceCore.test.js +1172 -133
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/TaskSettings.js +58 -38
- package/dist/components/features/TaskSettings.test.js +139 -40
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +3 -3
- package/dist/components/task/TaskActionHeader.test.js +8 -0
- package/dist/components/task/TaskCard.js +16 -1
- package/dist/components/task/TaskCard.test.js +31 -0
- package/dist/components/task/TaskForm.d.ts +2 -1
- package/dist/components/task/TaskForm.js +22 -4
- package/dist/components/task/TaskForm.test.js +46 -0
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +31 -160
- package/dist/components/task/TaskStatusActions.d.ts +6 -1
- package/dist/components/task/TaskStatusActions.js +8 -3
- package/dist/components/task/TaskStatusActions.test.js +9 -0
- package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
- package/dist/components/task/taskKanbanDropRules.js +187 -0
- package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
- package/dist/components/task/taskKanbanDropRules.test.js +114 -0
- package/dist/components/ui/Modal.d.ts +1 -1
- package/dist/components/ui/Modal.js +1 -0
- package/dist/components/views/AppShellHeader.js +5 -1
- package/dist/components/views/AppShellHeader.test.js +4 -0
- package/dist/components/views/PlanComparisonPage.d.ts +3 -2
- package/dist/components/views/PlanComparisonPage.js +4 -2
- package/dist/components/views/PlanComparisonPage.test.js +4 -0
- package/dist/components/views/PlansPage.d.ts +5 -1
- package/dist/components/views/PlansPage.js +39 -12
- package/dist/components/views/PlansPage.test.js +59 -2
- package/dist/components/views/StandaloneLayout.js +113 -38
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/PlanEntitlementService.d.ts +3 -1
- package/dist/core/PlanEntitlementService.js +61 -11
- package/dist/core/SignupMonetizationSettings.test.js +37 -2
- package/dist/core/TaskActivityService.d.ts +4 -1
- package/dist/core/TaskActivityService.js +45 -0
- package/dist/core/TaskAuditTimeline.test.js +52 -1
- package/dist/core/Taskforce.d.ts +15 -11
- package/dist/core/Taskforce.js +43 -2
- package/dist/core/WorkspacePlanMode.test.js +65 -0
- package/dist/documentReviews/service.d.ts +1 -1
- package/dist/documentReviews/types.d.ts +1 -1
- package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
- package/dist/hooks/sync/auth.d.ts +37 -0
- package/dist/hooks/sync/auth.js +61 -0
- package/dist/hooks/sync/bootstrap.d.ts +34 -0
- package/dist/hooks/sync/bootstrap.js +52 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
- package/dist/hooks/sync/orchestratorShared.js +209 -0
- package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
- package/dist/hooks/sync/orchestratorShared.test.js +49 -0
- package/dist/hooks/sync/realtime.d.ts +25 -0
- package/dist/hooks/sync/realtime.js +60 -0
- package/dist/hooks/sync/recovery.d.ts +69 -0
- package/dist/hooks/sync/recovery.js +118 -0
- package/dist/hooks/sync/transfers.d.ts +122 -0
- package/dist/hooks/sync/transfers.js +431 -0
- package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
- package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
- package/dist/hooks/ui/useResourceExport.d.ts +3 -1
- package/dist/hooks/ui/useResourceExport.js +5 -2
- package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
- package/dist/hooks/useRealtimeSync.js +2 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +5 -4
- package/dist/hooks/useSyncOrchestrator.js +314 -873
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +568 -3
- package/dist/hooks/useTaskMutations.js +45 -6
- package/dist/hooks/useTaskMutations.test.js +147 -0
- package/dist/hooks/useTaskforce.d.ts +8 -2
- package/dist/hooks/useTaskforce.js +285 -812
- package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
- package/dist/hooks/useWorkspaceSyncController.js +22 -10
- package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
- package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
- package/dist/hooks/workspace/workspaceLocalState.js +100 -0
- package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
- package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
- package/dist/localization/locales/en-US.js +2 -2
- package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
- package/dist/mcp/canonicalAssetHelpers.js +259 -0
- package/dist/mcp/clientIntegrations.d.ts +12 -1
- package/dist/mcp/clientIntegrations.js +161 -6
- package/dist/mcp/clientIntegrations.test.js +76 -4
- package/dist/mcp/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +174 -476
- package/dist/mcp/runtime.test.js +81 -8
- package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
- package/dist/mcp/taskAttachmentHelpers.js +60 -0
- package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
- package/dist/mcp/taskAttachmentTypes.js +1 -0
- package/dist/plugins/vite.js +1 -1
- package/dist/runtime/appGateDefinitions.d.ts +1 -1
- package/dist/runtime/appGateDefinitions.js +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +46 -3
- package/dist/server/index.test.js +12 -1
- package/dist/server/routes/admin.js +13 -19
- package/dist/server/routes/auth.js +37 -2
- package/dist/server/routes/billing.js +18 -67
- package/dist/server/routes/billing.test.js +43 -0
- package/dist/server/routes/shared.d.ts +30 -0
- package/dist/server/routes/shared.js +26 -0
- package/dist/server/routes/sync.integration.test.js +20 -0
- package/dist/server/routes/sync.js +110 -237
- package/dist/server/routes/tasks.js +49 -7
- package/dist/server/routes.js +8 -19
- package/dist/server/routes.test.js +243 -5
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +1 -0
- package/dist/sync/collaborationSyncPayload.js +4 -0
- package/dist/sync/collaborationSyncPayload.test.js +8 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -1
- package/dist/sync/collaborationSyncState.js +19 -0
- package/dist/sync/contentSyncState.js +12 -10
- package/dist/sync/syncApplyHandlers.d.ts +87 -1
- package/dist/sync/syncApplyHandlers.js +370 -8
- package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
- package/dist/sync/syncApplyHandlers.test.js +377 -0
- package/dist/sync/syncService.d.ts +8 -1
- package/dist/sync/syncService.js +36 -2
- package/dist/sync/syncService.test.js +48 -0
- package/dist/sync/workspacePullFeed.d.ts +1 -0
- package/dist/sync/workspacePullFeed.js +66 -59
- package/dist/sync/workspacePullFeed.test.js +72 -4
- package/dist/sync/workspaceSyncModel.d.ts +22 -1
- package/dist/sync/workspaceSyncModel.js +65 -0
- package/dist/sync/workspaceSyncModel.test.js +119 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-C3GyzrWm.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-BjR6iBf9.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
- package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-DnFqdUME.js} +1 -1
- package/dist/ui/assets/index-DXUdtH1B.js +6 -0
- package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
- package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-I7ZwG1z_.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +3 -1
- package/scripts/playwright-auth.sh +4 -0
- package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
- package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
- package/dist/ui/assets/index-CLIMgR6g.js +0 -6
- package/dist/ui/assets/index-DneETxcu.css +0 -1
|
@@ -92,7 +92,7 @@ describe('TaskforceCore task audit timeline', () => {
|
|
|
92
92
|
cleanup();
|
|
93
93
|
}
|
|
94
94
|
});
|
|
95
|
-
it('suppresses task events
|
|
95
|
+
it('suppresses task events when skipTaskEvents is explicitly true', () => {
|
|
96
96
|
const { core, cleanup } = makeCore();
|
|
97
97
|
try {
|
|
98
98
|
const task = core.createTask({ title: 'Autosave task', createdBy: 'user' }, { workspaceId: 'workspace-a', actorRef: 'user' });
|
|
@@ -110,6 +110,57 @@ describe('TaskforceCore task audit timeline', () => {
|
|
|
110
110
|
cleanup();
|
|
111
111
|
}
|
|
112
112
|
});
|
|
113
|
+
it('suppresses title and description autosave events while preserving the saved task content', () => {
|
|
114
|
+
const { core, cleanup } = makeCore();
|
|
115
|
+
try {
|
|
116
|
+
const task = core.createTask({ title: 'Autosave title task', createdBy: 'user' }, { workspaceId: 'workspace-a', actorRef: 'user' });
|
|
117
|
+
core.updateTask(task.id, {
|
|
118
|
+
title: 'Autosave title task updated',
|
|
119
|
+
description: 'Draft body'
|
|
120
|
+
}, 'workspace-a', {
|
|
121
|
+
actorRef: 'user',
|
|
122
|
+
saveSource: 'autosave'
|
|
123
|
+
});
|
|
124
|
+
const events = core.listTaskEvents(task.id, 'workspace-a');
|
|
125
|
+
const hydrated = core.getTask(task.id, 'workspace-a');
|
|
126
|
+
expect(events.map((event) => event.action)).toEqual(['task-created']);
|
|
127
|
+
expect(hydrated?.title).toBe('Autosave title task updated');
|
|
128
|
+
expect(hydrated?.description).toBe('Draft body');
|
|
129
|
+
expect(hydrated?.activity?.some((item) => item.type === 'event' && item.event.action === 'task-updated')).toBe(false);
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
cleanup();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
it('keeps meaningful autosave updates in the activity feed while filtering noisy text changes', () => {
|
|
136
|
+
const { core, cleanup } = makeCore();
|
|
137
|
+
try {
|
|
138
|
+
const task = core.createTask({ title: 'Autosave assignee task', createdBy: 'user' }, { workspaceId: 'workspace-a', actorRef: 'user' });
|
|
139
|
+
core.updateTask(task.id, {
|
|
140
|
+
title: 'Autosave assignee task updated',
|
|
141
|
+
assignee: 'member-1'
|
|
142
|
+
}, 'workspace-a', {
|
|
143
|
+
actorRef: 'user',
|
|
144
|
+
saveSource: 'autosave'
|
|
145
|
+
});
|
|
146
|
+
const assignmentEvent = core.listTaskEvents(task.id, 'workspace-a').find((event) => event.action === 'task-updated' && event.details?.changes?.assignee);
|
|
147
|
+
expect(assignmentEvent).toEqual(expect.objectContaining({
|
|
148
|
+
action: 'task-updated',
|
|
149
|
+
details: {
|
|
150
|
+
changes: {
|
|
151
|
+
assignee: {
|
|
152
|
+
from: 'unassigned',
|
|
153
|
+
to: 'member-1',
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
}));
|
|
158
|
+
expect(assignmentEvent?.details?.changes?.title).toBeUndefined();
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
cleanup();
|
|
162
|
+
}
|
|
163
|
+
});
|
|
113
164
|
it('captures assignment changes in the core task update event details', () => {
|
|
114
165
|
const { core, cleanup } = makeCore();
|
|
115
166
|
try {
|
package/dist/core/Taskforce.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { WorkspaceAssetStore } from '../storage/workspaceAssetStore.js';
|
|
|
3
3
|
import { type SyncEventInput } from './SyncReconciliationService.js';
|
|
4
4
|
import { type FirstClassTaxonomyDisplayLabels, type WorkspaceTaxonomyState } from '../shared/taxonomyState.js';
|
|
5
5
|
import { type AiProfileSurfaceType } from '../shared/aiProfileSurfaceType.js';
|
|
6
|
+
import type { TaskActor } from '../shared/taskActor.js';
|
|
7
|
+
export type { TaskActor } from '../shared/taskActor.js';
|
|
6
8
|
export type { WorkspaceDeletedAssetRecord, WorkspaceDeleteSummary } from './types.js';
|
|
7
9
|
import type { GlobalTaskforceSettings, UserGlobalSyncSettings, WorkspaceDeleteSummary } from './types.js';
|
|
8
10
|
import type { AnnotatedAttachmentAnnotation, AnnotatedAttachmentPayload, AnnotatedAttachmentSession, AnnotatedAttachmentSessionRecord } from '../annotatedAttachments/types.js';
|
|
@@ -73,16 +75,6 @@ export interface DeletedTaskRecord {
|
|
|
73
75
|
}
|
|
74
76
|
export type TaskStatus = 'task' | 'on-hold' | 'in-progress' | 'review' | 'done' | 'cancelled';
|
|
75
77
|
export type TaskAssignee = string;
|
|
76
|
-
export interface TaskActor {
|
|
77
|
-
id: string;
|
|
78
|
-
kind: 'human' | 'ai';
|
|
79
|
-
label: string;
|
|
80
|
-
username: string;
|
|
81
|
-
icon: string;
|
|
82
|
-
color: string;
|
|
83
|
-
isNamed: boolean;
|
|
84
|
-
meta?: Record<string, unknown>;
|
|
85
|
-
}
|
|
86
78
|
export interface TaskItem {
|
|
87
79
|
id: string;
|
|
88
80
|
referenceNumber?: number | null;
|
|
@@ -755,6 +747,9 @@ export interface TaskforceSyncCapable {
|
|
|
755
747
|
workspaceId?: string | null;
|
|
756
748
|
includeDeleted?: boolean;
|
|
757
749
|
}): AnnotatedAttachmentSessionRecord[];
|
|
750
|
+
listTaskEventsForSync(input: {
|
|
751
|
+
workspaceId?: string | null;
|
|
752
|
+
}): TaskEvent[];
|
|
758
753
|
upsertAiProfileForSync(profile: AiProfileRecord): AiProfileRecord;
|
|
759
754
|
replaceWorkspaceUsersForSync(input: {
|
|
760
755
|
workspaceId: string;
|
|
@@ -765,6 +760,7 @@ export interface TaskforceSyncCapable {
|
|
|
765
760
|
upsertDocumentReviewSessionForSync(session: DocumentReviewSessionRecord): DocumentReviewSessionRecord;
|
|
766
761
|
upsertDocumentReviewCommentForSync(comment: DocumentReviewCommentSyncRecord): DocumentReviewCommentSyncRecord;
|
|
767
762
|
upsertAnnotatedAttachmentSessionForSync(session: AnnotatedAttachmentSessionRecord): AnnotatedAttachmentSessionRecord;
|
|
763
|
+
upsertTaskEventForSync(event: TaskEvent): TaskEvent;
|
|
768
764
|
deleteDocumentReviewSessionForSync(input: {
|
|
769
765
|
workspaceId?: string | null;
|
|
770
766
|
sessionId: string;
|
|
@@ -847,6 +843,7 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
|
|
|
847
843
|
private areTaskEventValuesEqual;
|
|
848
844
|
private normalizeTaskEventValue;
|
|
849
845
|
private buildTaskUpdateChanges;
|
|
846
|
+
private filterAutosaveTaskEventChanges;
|
|
850
847
|
private normalizeTaxonomyToken;
|
|
851
848
|
private normalizeStatusValue;
|
|
852
849
|
private sanitizeAssignee;
|
|
@@ -893,16 +890,18 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
|
|
|
893
890
|
effectiveAt?: string | null;
|
|
894
891
|
effectiveUntil?: string | null;
|
|
895
892
|
billingMirrorPlanId?: string | null;
|
|
896
|
-
source: 'registration' | 'checkout-start' | 'checkout-confirm' | 'stripe-webhook' | 'backfill' | 'legacy-plan-mode' | 'admin' | 'system';
|
|
893
|
+
source: 'registration' | 'invite-accept' | 'checkout-start' | 'checkout-confirm' | 'stripe-webhook' | 'backfill' | 'legacy-plan-mode' | 'admin' | 'system';
|
|
897
894
|
updatedBy: string;
|
|
898
895
|
createdAt?: string | null;
|
|
899
896
|
updatedAt?: string | null;
|
|
900
897
|
}): import("./types.js").AccountEntitlementSnapshot;
|
|
901
898
|
private resolveWorkspaceLimitForUser;
|
|
902
899
|
private resolveRegistrationOnboardingOutcome;
|
|
900
|
+
private ensureDefaultOnboardingEntitlement;
|
|
903
901
|
createUserBillingRecord(userId: string, options?: {
|
|
904
902
|
entitlementState?: string;
|
|
905
903
|
planId?: string | null;
|
|
904
|
+
effectiveUntil?: string | null;
|
|
906
905
|
}): void;
|
|
907
906
|
getUserEntitlement(userId: string): {
|
|
908
907
|
entitlementState: string;
|
|
@@ -1268,6 +1267,7 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
|
|
|
1268
1267
|
upsertDocumentReviewSessionForSync(session: DocumentReviewSessionRecord): DocumentReviewSessionRecord;
|
|
1269
1268
|
upsertDocumentReviewCommentForSync(comment: DocumentReviewCommentSyncRecord): DocumentReviewCommentSyncRecord;
|
|
1270
1269
|
upsertAnnotatedAttachmentSessionForSync(session: AnnotatedAttachmentSessionRecord): AnnotatedAttachmentSessionRecord;
|
|
1270
|
+
upsertTaskEventForSync(event: TaskEvent): TaskEvent;
|
|
1271
1271
|
deleteDocumentReviewSessionForSync(input: {
|
|
1272
1272
|
workspaceId?: string | null;
|
|
1273
1273
|
sessionId: string;
|
|
@@ -1668,6 +1668,9 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
|
|
|
1668
1668
|
updateWorkspaceUserRole(userId: string, workspaceId: string, role: AccessRole): boolean;
|
|
1669
1669
|
getTask(id: string, workspaceId?: string): TaskItem | null;
|
|
1670
1670
|
listTaskEvents(taskId: string, workspaceId?: string): TaskEvent[];
|
|
1671
|
+
listTaskEventsForSync(input: {
|
|
1672
|
+
workspaceId?: string | null;
|
|
1673
|
+
}): TaskEvent[];
|
|
1671
1674
|
getScheduleBoard(weekKey: string, showWeekends?: boolean, workspaceId?: string): ScheduleBoardResult;
|
|
1672
1675
|
updateTaskSchedule(id: string, input: {
|
|
1673
1676
|
scheduledDate: string | null;
|
|
@@ -1699,6 +1702,7 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
|
|
|
1699
1702
|
syncUpdatedAt?: string;
|
|
1700
1703
|
actorRef?: string;
|
|
1701
1704
|
skipTaskEvents?: boolean;
|
|
1705
|
+
saveSource?: 'manual' | 'autosave';
|
|
1702
1706
|
}): TaskItem | null;
|
|
1703
1707
|
addComment(id: string, text: string, author: string, workspaceId?: string, options?: {
|
|
1704
1708
|
persistBackup?: boolean;
|
package/dist/core/Taskforce.js
CHANGED
|
@@ -9,7 +9,7 @@ import { WorkspaceAssetStore } from '../storage/workspaceAssetStore.js';
|
|
|
9
9
|
import { AuthTokenService } from './AuthTokenService.js';
|
|
10
10
|
import { getAiProfileByTokenService, listAiProfilesService, mergeAiProfilesService, resolveAiProfileService, upsertAiProfileForSyncService, } from './AiProfileService.js';
|
|
11
11
|
import { syncCanonicalTaskAttachmentLinksService, } from './AttachmentLinkService.js';
|
|
12
|
-
import { addTaskEventService, buildTaskActivityService, listTaskEventsInternalService, replaceTaskCommentsService, replaceTaskEventsService, } from './TaskActivityService.js';
|
|
12
|
+
import { addTaskEventService, buildTaskActivityService, listTaskEventsInternalService, listWorkspaceTaskEventsService, replaceTaskCommentsService, replaceTaskEventsService, upsertTaskEventForSyncService, } from './TaskActivityService.js';
|
|
13
13
|
import { addDeletedTaskRecordService, getDeletedTaskService, listDeletedTasksService, mapDeletedTaskRowService, normalizeTaskSnapshotForRestoreService, pruneDeletedTasksService, purgeExpiredDeletedTasksService, } from './DeletedTaskLifecycleService.js';
|
|
14
14
|
import { applyWorkspaceSyncSnapshotService, deleteDocumentWatermarkService, getDocumentWatermarkService, listTasksForSyncService, listWorkspaceSyncDeletesSinceService, readPushIdempotencyService, recordSyncEventService, upsertDocumentWatermarkService, writePushIdempotencyService, } from './SyncReconciliationService.js';
|
|
15
15
|
import { getGlobalSettingsService, hasPersistedGlobalSettingsService, readGlobalConfigService, updateGlobalSettingsService, writeGlobalConfigService, } from './GlobalSettingsService.js';
|
|
@@ -666,6 +666,9 @@ export class TaskforceCore {
|
|
|
666
666
|
}
|
|
667
667
|
return changes;
|
|
668
668
|
}
|
|
669
|
+
filterAutosaveTaskEventChanges(changes) {
|
|
670
|
+
return Object.fromEntries(Object.entries(changes).filter(([field]) => field !== 'title' && field !== 'description'));
|
|
671
|
+
}
|
|
669
672
|
normalizeTaxonomyToken(value) {
|
|
670
673
|
return String(value || '')
|
|
671
674
|
.trim()
|
|
@@ -1217,6 +1220,29 @@ export class TaskforceCore {
|
|
|
1217
1220
|
resolveRegistrationOnboardingOutcome(selectedPlanVersionIdRaw) {
|
|
1218
1221
|
return this.planEntitlementService.resolveRegistrationOnboardingOutcome(selectedPlanVersionIdRaw);
|
|
1219
1222
|
}
|
|
1223
|
+
ensureDefaultOnboardingEntitlement(accountIdRaw, options) {
|
|
1224
|
+
const accountId = String(accountIdRaw || '').trim();
|
|
1225
|
+
if (!accountId) {
|
|
1226
|
+
throw new TaskforceRuleError('accountId is required', 400, 'ACCOUNT_ID_REQUIRED');
|
|
1227
|
+
}
|
|
1228
|
+
if (this.hasAccountEntitlement(accountId)) {
|
|
1229
|
+
return this.getAccountEntitlement(accountId);
|
|
1230
|
+
}
|
|
1231
|
+
const now = options?.now || new Date().toISOString();
|
|
1232
|
+
const onboardingOutcome = this.resolveRegistrationOnboardingOutcome();
|
|
1233
|
+
this.transitionCanonicalCommercialState({
|
|
1234
|
+
accountId,
|
|
1235
|
+
planVersionId: onboardingOutcome.planVersionId,
|
|
1236
|
+
state: onboardingOutcome.commercialState,
|
|
1237
|
+
effectiveAt: now,
|
|
1238
|
+
billingMirrorPlanId: onboardingOutcome.planId,
|
|
1239
|
+
source: options?.source || 'registration',
|
|
1240
|
+
updatedBy: options?.updatedBy || 'registration',
|
|
1241
|
+
createdAt: now,
|
|
1242
|
+
updatedAt: now
|
|
1243
|
+
});
|
|
1244
|
+
return this.getAccountEntitlement(accountId);
|
|
1245
|
+
}
|
|
1220
1246
|
createUserBillingRecord(userId, options) {
|
|
1221
1247
|
return this.planEntitlementService.createUserBillingRecord(userId, options);
|
|
1222
1248
|
}
|
|
@@ -4841,6 +4867,9 @@ export class TaskforceCore {
|
|
|
4841
4867
|
deletedAt: session.deletedAt,
|
|
4842
4868
|
});
|
|
4843
4869
|
}
|
|
4870
|
+
upsertTaskEventForSync(event) {
|
|
4871
|
+
return upsertTaskEventForSyncService(this.getTaskActivityServiceDeps(), event);
|
|
4872
|
+
}
|
|
4844
4873
|
deleteDocumentReviewSessionForSync(input) {
|
|
4845
4874
|
const deps = this.getDocumentReviewServiceDeps();
|
|
4846
4875
|
return deps.sessionStore.softDelete(input.sessionId, deps.normalizeWorkspaceId(input.workspaceId), input.deletedAt || undefined);
|
|
@@ -5795,6 +5824,11 @@ export class TaskforceCore {
|
|
|
5795
5824
|
updated_at = ?
|
|
5796
5825
|
WHERE tenant_id = ? AND id = ?
|
|
5797
5826
|
`).run(hashPassword(password), 'password', user.email, now, now, this.tenantId, consumed.userId);
|
|
5827
|
+
this.ensureDefaultOnboardingEntitlement(consumed.userId, {
|
|
5828
|
+
source: 'invite-accept',
|
|
5829
|
+
updatedBy: 'invite-accept',
|
|
5830
|
+
now
|
|
5831
|
+
});
|
|
5798
5832
|
return this.completeInviteWorkspaceActivation({
|
|
5799
5833
|
userId: consumed.userId,
|
|
5800
5834
|
workspaceId: inviteWorkspaceId
|
|
@@ -5953,6 +5987,10 @@ export class TaskforceCore {
|
|
|
5953
5987
|
const normalizedWorkspaceId = this.normalizeWorkspaceId(workspaceId);
|
|
5954
5988
|
return this.listTaskEventsInternal(taskId, normalizedWorkspaceId);
|
|
5955
5989
|
}
|
|
5990
|
+
listTaskEventsForSync(input) {
|
|
5991
|
+
const normalizedWorkspaceId = this.normalizeWorkspaceId(input.workspaceId || undefined);
|
|
5992
|
+
return listWorkspaceTaskEventsService(this.getTaskActivityServiceDeps(), normalizedWorkspaceId);
|
|
5993
|
+
}
|
|
5956
5994
|
getScheduleBoard(weekKey, showWeekends = false, workspaceId) {
|
|
5957
5995
|
const parsedWeek = this.parseIsoWeekKeyToDates(weekKey);
|
|
5958
5996
|
if (!parsedWeek) {
|
|
@@ -6510,7 +6548,10 @@ export class TaskforceCore {
|
|
|
6510
6548
|
if (!nextTask)
|
|
6511
6549
|
return null;
|
|
6512
6550
|
if (options?.skipTaskEvents !== true) {
|
|
6513
|
-
const
|
|
6551
|
+
const rawChanges = this.buildTaskUpdateChanges(existing, nextTask, patch);
|
|
6552
|
+
const changes = options?.saveSource === 'autosave'
|
|
6553
|
+
? this.filterAutosaveTaskEventChanges(rawChanges)
|
|
6554
|
+
: rawChanges;
|
|
6514
6555
|
if (Object.keys(changes).length > 0) {
|
|
6515
6556
|
let action = 'task-updated';
|
|
6516
6557
|
if (changes.status) {
|
|
@@ -23,6 +23,17 @@ function openCore(projectRoot) {
|
|
|
23
23
|
core.upsertPlanCatalog({ planId: 'starter', displayName: 'Starter' });
|
|
24
24
|
addFreeMapping('starter-v1');
|
|
25
25
|
core.upsertPlanCatalog({ planId: 'starter', displayName: 'Starter', status: 'active' });
|
|
26
|
+
core.updatePlanVersion({
|
|
27
|
+
planId: 'starter',
|
|
28
|
+
versionNumber: 1,
|
|
29
|
+
seatLimit: 1
|
|
30
|
+
});
|
|
31
|
+
core.updatePlanVersionFeatures({
|
|
32
|
+
planVersionId: 'starter-v1',
|
|
33
|
+
features: [
|
|
34
|
+
{ featureKey: 'workspace.switching', access: 'enabled' }
|
|
35
|
+
]
|
|
36
|
+
});
|
|
26
37
|
core.upsertPlanCatalog({ planId: 'collab', displayName: 'Collab' });
|
|
27
38
|
addFreeMapping('collab-v1');
|
|
28
39
|
core.upsertPlanCatalog({ planId: 'collab', displayName: 'Collab', status: 'active' });
|
|
@@ -214,4 +225,58 @@ describe('TaskforceCore account-level plan mode and invite revoke', () => {
|
|
|
214
225
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
215
226
|
}
|
|
216
227
|
});
|
|
228
|
+
it('grants the default onboarding entitlement to newly accepted invited accounts', () => {
|
|
229
|
+
const projectRoot = makeProjectRoot();
|
|
230
|
+
const core = openCore(projectRoot);
|
|
231
|
+
try {
|
|
232
|
+
core.updateGlobalSettings({
|
|
233
|
+
billing: {
|
|
234
|
+
onboardingPolicy: {
|
|
235
|
+
mode: 'auto_grant_free',
|
|
236
|
+
planVersionId: 'starter-v1'
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
core.createWorkspace({ workspaceId: 'workspace-invite', name: 'Invite Workspace' });
|
|
241
|
+
core.ensureBootstrapUserAccess({
|
|
242
|
+
userId: 'owner-user',
|
|
243
|
+
workspaceId: 'workspace-invite',
|
|
244
|
+
role: 'owner',
|
|
245
|
+
email: 'owner@example.com'
|
|
246
|
+
});
|
|
247
|
+
core.setUserAccountPlanMode('owner-user', 'team');
|
|
248
|
+
core.ensureBootstrapUserAccess({
|
|
249
|
+
userId: 'invited-user',
|
|
250
|
+
workspaceId: 'workspace-invite',
|
|
251
|
+
role: 'member',
|
|
252
|
+
email: 'invited@example.com'
|
|
253
|
+
});
|
|
254
|
+
core.setWorkspaceUserDisabled('invited-user', 'workspace-invite', true);
|
|
255
|
+
const db = core.db;
|
|
256
|
+
db.prepare(`
|
|
257
|
+
UPDATE workspace_memberships
|
|
258
|
+
SET status = 'invited'
|
|
259
|
+
WHERE tenant_id = ? AND user_id = ? AND workspace_id = ?
|
|
260
|
+
`).run('default', 'invited-user', 'workspace-invite');
|
|
261
|
+
const invite = core.requestInviteAcceptance({ userId: 'invited-user', workspaceId: 'workspace-invite' });
|
|
262
|
+
const accepted = core.acceptInviteWithToken({
|
|
263
|
+
token: String(invite.token),
|
|
264
|
+
password: 'Password123!'
|
|
265
|
+
});
|
|
266
|
+
expect(accepted.workspaceId).toBe('workspace-invite');
|
|
267
|
+
expect(core.hasAccountEntitlement('invited-user')).toBe(true);
|
|
268
|
+
expect(core.getAccountEntitlement('invited-user')).toEqual(expect.objectContaining({
|
|
269
|
+
planVersionId: 'starter-v1',
|
|
270
|
+
state: 'active'
|
|
271
|
+
}));
|
|
272
|
+
expect(core.getAccountAccessStatus('invited-user')).toEqual(expect.objectContaining({
|
|
273
|
+
gate: 'ok',
|
|
274
|
+
canAccessApp: true
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
core.close();
|
|
279
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
280
|
+
}
|
|
281
|
+
});
|
|
217
282
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TaskActor } from '../
|
|
1
|
+
import type { TaskActor } from '../shared/taskActor.js';
|
|
2
2
|
import type { WorkspaceAssetStore } from '../storage/workspaceAssetStore.js';
|
|
3
3
|
import { DocumentReviewCommentStore } from './DocumentReviewCommentStore.js';
|
|
4
4
|
import { DocumentReviewSessionStore } from './DocumentReviewSessionStore.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TaskActor } from '../
|
|
1
|
+
import type { TaskActor } from '../shared/taskActor.js';
|
|
2
2
|
export type DocumentReviewSessionStatus = 'open' | 'resolved';
|
|
3
3
|
export type DocumentReviewAnchorKind = 'document' | 'quote' | 'line-range';
|
|
4
4
|
export interface DocumentReviewDocumentAnchor {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type MutableRefObject } from 'react';
|
|
2
|
+
import { createDefaultAssigneeOptions } from '../../utils/assignees';
|
|
3
|
+
export type BootstrapPhase = 'idle' | 'auth' | 'workspace' | 'config' | 'ready' | 'stalled';
|
|
4
|
+
interface UseTaskforceAuthBootstrapArgs {
|
|
5
|
+
runtimeConfigReady: boolean;
|
|
6
|
+
shouldProbeCloudAuth: boolean;
|
|
7
|
+
authSessionResolved: boolean;
|
|
8
|
+
resolveCloudAuthUrl: (path: string) => string;
|
|
9
|
+
authOnlyCloudMode: boolean;
|
|
10
|
+
authRequiredError: string;
|
|
11
|
+
runtimeMode: 'local' | 'cloud';
|
|
12
|
+
workspaceSelectionScope: string;
|
|
13
|
+
markBootstrapPhase: (phase: Exclude<BootstrapPhase, 'stalled'>) => void;
|
|
14
|
+
markBootstrapStalled: (message: string) => void;
|
|
15
|
+
setRuntimeMode: (mode: 'local' | 'cloud') => void;
|
|
16
|
+
setAuthRequiredForApi: (value: boolean) => void;
|
|
17
|
+
setIsAuthenticated: (value: boolean) => void;
|
|
18
|
+
setAuthUserId: (value: string) => void;
|
|
19
|
+
setAuthWorkspaceId: (value: string) => void;
|
|
20
|
+
setAuthUserEmail: (value: string) => void;
|
|
21
|
+
setAuthUserDisplayName: (value: string) => void;
|
|
22
|
+
setAuthUserAvatarUrl: (value: string) => void;
|
|
23
|
+
setUserGlobalSyncStatus: (value: 'idle' | 'disconnected') => void;
|
|
24
|
+
setUserGlobalSyncError: (value: string | null) => void;
|
|
25
|
+
setHasBetaAccess: (value: boolean) => void;
|
|
26
|
+
setAvailableWorkspaces: (value: any[]) => void;
|
|
27
|
+
setHydratedWorkspaceRole: (value: any) => void;
|
|
28
|
+
setAssigneeOptions: (value: ReturnType<typeof createDefaultAssigneeOptions>) => void;
|
|
29
|
+
setAuthBlocked: (value: boolean) => void;
|
|
30
|
+
setAuthSessionResolved: (value: boolean) => void;
|
|
31
|
+
setError: (value: string | ((prev: string) => string)) => void;
|
|
32
|
+
applyResolvedWorkspaceId: (workspaceId: string, options?: {
|
|
33
|
+
preserveNonDefaultDefault?: boolean;
|
|
34
|
+
}) => void;
|
|
35
|
+
readPersistedWorkspaceId: (scope: string) => string;
|
|
36
|
+
authSessionRequestRef: MutableRefObject<Promise<boolean> | null>;
|
|
37
|
+
authSessionEpochRef: MutableRefObject<number>;
|
|
38
|
+
authSessionLastCheckedAtRef: MutableRefObject<number>;
|
|
39
|
+
authSessionLastResultRef: MutableRefObject<boolean>;
|
|
40
|
+
isLoopbackHost: boolean;
|
|
41
|
+
setAuthStateReadyRefs: (authenticated: boolean, authSessionResolved: boolean, authRequiredForApi: boolean) => void;
|
|
42
|
+
}
|
|
43
|
+
export declare function useTaskforceAuthBootstrap({ runtimeConfigReady, shouldProbeCloudAuth, authSessionResolved, resolveCloudAuthUrl, authOnlyCloudMode, authRequiredError, runtimeMode, workspaceSelectionScope, markBootstrapPhase, markBootstrapStalled, setRuntimeMode, setAuthRequiredForApi, setIsAuthenticated, setAuthUserId, setAuthWorkspaceId, setAuthUserEmail, setAuthUserDisplayName, setAuthUserAvatarUrl, setUserGlobalSyncStatus, setUserGlobalSyncError, setHasBetaAccess, setAvailableWorkspaces, setHydratedWorkspaceRole, setAssigneeOptions, setAuthBlocked, setAuthSessionResolved, setError, applyResolvedWorkspaceId, readPersistedWorkspaceId, authSessionRequestRef, authSessionEpochRef, authSessionLastCheckedAtRef, authSessionLastResultRef, isLoopbackHost, setAuthStateReadyRefs }: UseTaskforceAuthBootstrapArgs): {
|
|
44
|
+
checkAuthSession: (options?: {
|
|
45
|
+
force?: boolean;
|
|
46
|
+
}) => Promise<boolean>;
|
|
47
|
+
};
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { useCallback, useEffect } from 'react';
|
|
2
|
+
import { createDefaultAssigneeOptions } from '../../utils/assignees';
|
|
3
|
+
import { persistLocalMutationActorUserId, readLocalMutationActorUserId } from '../../utils/taskforceApiClient';
|
|
4
|
+
export function useTaskforceAuthBootstrap({ runtimeConfigReady, shouldProbeCloudAuth, authSessionResolved, resolveCloudAuthUrl, authOnlyCloudMode, authRequiredError, runtimeMode, workspaceSelectionScope, markBootstrapPhase, markBootstrapStalled, setRuntimeMode, setAuthRequiredForApi, setIsAuthenticated, setAuthUserId, setAuthWorkspaceId, setAuthUserEmail, setAuthUserDisplayName, setAuthUserAvatarUrl, setUserGlobalSyncStatus, setUserGlobalSyncError, setHasBetaAccess, setAvailableWorkspaces, setHydratedWorkspaceRole, setAssigneeOptions, setAuthBlocked, setAuthSessionResolved, setError, applyResolvedWorkspaceId, readPersistedWorkspaceId, authSessionRequestRef, authSessionEpochRef, authSessionLastCheckedAtRef, authSessionLastResultRef, isLoopbackHost, setAuthStateReadyRefs }) {
|
|
5
|
+
const checkAuthSession = useCallback(async (options) => {
|
|
6
|
+
const force = options?.force === true;
|
|
7
|
+
markBootstrapPhase('auth');
|
|
8
|
+
if (!runtimeConfigReady) {
|
|
9
|
+
return authSessionLastResultRef.current;
|
|
10
|
+
}
|
|
11
|
+
if (!shouldProbeCloudAuth) {
|
|
12
|
+
setRuntimeMode('local');
|
|
13
|
+
setAuthRequiredForApi(false);
|
|
14
|
+
setIsAuthenticated(false);
|
|
15
|
+
setAuthUserId('anonymous');
|
|
16
|
+
setAuthWorkspaceId('');
|
|
17
|
+
persistLocalMutationActorUserId(null);
|
|
18
|
+
setAuthUserEmail('');
|
|
19
|
+
setAuthUserDisplayName('');
|
|
20
|
+
setAuthUserAvatarUrl('');
|
|
21
|
+
setUserGlobalSyncStatus('disconnected');
|
|
22
|
+
setUserGlobalSyncError(null);
|
|
23
|
+
setHasBetaAccess(true);
|
|
24
|
+
setAvailableWorkspaces([]);
|
|
25
|
+
setHydratedWorkspaceRole(null);
|
|
26
|
+
setAssigneeOptions(createDefaultAssigneeOptions());
|
|
27
|
+
setAuthBlocked(false);
|
|
28
|
+
setAuthSessionResolved(true);
|
|
29
|
+
setAuthStateReadyRefs(false, true, false);
|
|
30
|
+
authSessionLastResultRef.current = false;
|
|
31
|
+
authSessionLastCheckedAtRef.current = Date.now();
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const sessionUrl = resolveCloudAuthUrl('/api/taskforce/auth/session');
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
if (!force && authSessionRequestRef.current)
|
|
37
|
+
return authSessionRequestRef.current;
|
|
38
|
+
if (!force && authSessionResolved && (now - authSessionLastCheckedAtRef.current) < 1500) {
|
|
39
|
+
return authSessionLastResultRef.current;
|
|
40
|
+
}
|
|
41
|
+
const requestEpoch = ++authSessionEpochRef.current;
|
|
42
|
+
let request = null;
|
|
43
|
+
request = (async () => {
|
|
44
|
+
const abortController = new AbortController();
|
|
45
|
+
const timeoutId = typeof window !== 'undefined'
|
|
46
|
+
? window.setTimeout(() => abortController.abort(), 8000)
|
|
47
|
+
: null;
|
|
48
|
+
const isStaleRequest = () => authSessionEpochRef.current !== requestEpoch;
|
|
49
|
+
try {
|
|
50
|
+
const res = await fetch(sessionUrl, {
|
|
51
|
+
method: 'GET',
|
|
52
|
+
credentials: 'include',
|
|
53
|
+
mode: 'cors',
|
|
54
|
+
signal: abortController.signal
|
|
55
|
+
});
|
|
56
|
+
if (isStaleRequest()) {
|
|
57
|
+
return authSessionLastResultRef.current;
|
|
58
|
+
}
|
|
59
|
+
if (res.status === 429) {
|
|
60
|
+
setAuthSessionResolved(true);
|
|
61
|
+
setAuthStateReadyRefs(authSessionLastResultRef.current, true, false);
|
|
62
|
+
return authSessionLastResultRef.current;
|
|
63
|
+
}
|
|
64
|
+
if (!res.ok) {
|
|
65
|
+
if (isStaleRequest()) {
|
|
66
|
+
return authSessionLastResultRef.current;
|
|
67
|
+
}
|
|
68
|
+
if (isLoopbackHost)
|
|
69
|
+
persistLocalMutationActorUserId(null);
|
|
70
|
+
setAuthSessionResolved(true);
|
|
71
|
+
setAuthStateReadyRefs(false, true, false);
|
|
72
|
+
authSessionLastResultRef.current = false;
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
const data = await res.json();
|
|
76
|
+
if (isStaleRequest()) {
|
|
77
|
+
return authSessionLastResultRef.current;
|
|
78
|
+
}
|
|
79
|
+
const required = Boolean(data.authRequiredForApi);
|
|
80
|
+
const authenticated = Boolean(data.authenticated);
|
|
81
|
+
const betaAccess = authenticated ? (data.betaAccess !== false) : true;
|
|
82
|
+
const workspaceId = typeof data.workspaceId === 'string' && data.workspaceId.trim().length > 0
|
|
83
|
+
? data.workspaceId.trim()
|
|
84
|
+
: '';
|
|
85
|
+
const userId = typeof data.userId === 'string' && data.userId.trim().length > 0
|
|
86
|
+
? data.userId.trim()
|
|
87
|
+
: 'anonymous';
|
|
88
|
+
const userEmail = typeof data.email === 'string' && data.email.trim().length > 0
|
|
89
|
+
? data.email.trim().toLowerCase()
|
|
90
|
+
: '';
|
|
91
|
+
const userDisplayName = typeof data.displayName === 'string' ? data.displayName.trim() : '';
|
|
92
|
+
const userAvatarUrl = typeof data.avatarUrl === 'string' ? data.avatarUrl.trim() : '';
|
|
93
|
+
const isLocalRuntime = runtimeMode === 'local';
|
|
94
|
+
const keepLocalWorkspaceContext = runtimeMode === 'local' || authOnlyCloudMode;
|
|
95
|
+
const nextAuthRequiredForApi = authOnlyCloudMode ? false : (isLocalRuntime ? false : required);
|
|
96
|
+
setAuthRequiredForApi(nextAuthRequiredForApi);
|
|
97
|
+
setIsAuthenticated(authenticated);
|
|
98
|
+
setAuthUserId(authenticated ? userId : 'anonymous');
|
|
99
|
+
setAuthWorkspaceId(authenticated ? workspaceId : '');
|
|
100
|
+
if (isLoopbackHost)
|
|
101
|
+
persistLocalMutationActorUserId(authenticated ? userId : null);
|
|
102
|
+
setAuthUserEmail(authenticated ? userEmail : '');
|
|
103
|
+
setAuthUserDisplayName(authenticated ? userDisplayName : '');
|
|
104
|
+
setAuthUserAvatarUrl(authenticated ? userAvatarUrl : '');
|
|
105
|
+
if (authenticated) {
|
|
106
|
+
setError((prev) => prev === authRequiredError ? '' : prev);
|
|
107
|
+
}
|
|
108
|
+
setUserGlobalSyncStatus(authenticated ? 'idle' : 'disconnected');
|
|
109
|
+
setUserGlobalSyncError(null);
|
|
110
|
+
setHasBetaAccess(betaAccess);
|
|
111
|
+
if (!keepLocalWorkspaceContext) {
|
|
112
|
+
applyResolvedWorkspaceId(workspaceId || readPersistedWorkspaceId(workspaceSelectionScope) || 'default', { preserveNonDefaultDefault: true });
|
|
113
|
+
}
|
|
114
|
+
setAuthBlocked(authOnlyCloudMode ? false : (isLocalRuntime ? false : (required && !authenticated)));
|
|
115
|
+
setAuthSessionResolved(true);
|
|
116
|
+
setAuthStateReadyRefs(authenticated, true, nextAuthRequiredForApi);
|
|
117
|
+
authSessionLastResultRef.current = authenticated;
|
|
118
|
+
if (authenticated)
|
|
119
|
+
markBootstrapPhase('ready');
|
|
120
|
+
return authenticated;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
if (isStaleRequest()) {
|
|
124
|
+
return authSessionLastResultRef.current;
|
|
125
|
+
}
|
|
126
|
+
if (isLoopbackHost)
|
|
127
|
+
persistLocalMutationActorUserId(null);
|
|
128
|
+
setAuthSessionResolved(true);
|
|
129
|
+
setAuthStateReadyRefs(false, true, false);
|
|
130
|
+
authSessionLastResultRef.current = false;
|
|
131
|
+
markBootstrapStalled('Unable to verify your session.');
|
|
132
|
+
return authSessionLastResultRef.current;
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
if (timeoutId !== null && typeof window !== 'undefined') {
|
|
136
|
+
window.clearTimeout(timeoutId);
|
|
137
|
+
}
|
|
138
|
+
if (!isStaleRequest()) {
|
|
139
|
+
authSessionLastCheckedAtRef.current = Date.now();
|
|
140
|
+
}
|
|
141
|
+
if (request && authSessionRequestRef.current === request) {
|
|
142
|
+
authSessionRequestRef.current = null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
})();
|
|
146
|
+
authSessionRequestRef.current = request;
|
|
147
|
+
return request;
|
|
148
|
+
}, [
|
|
149
|
+
runtimeConfigReady,
|
|
150
|
+
shouldProbeCloudAuth,
|
|
151
|
+
authSessionResolved,
|
|
152
|
+
resolveCloudAuthUrl,
|
|
153
|
+
authOnlyCloudMode,
|
|
154
|
+
authRequiredError,
|
|
155
|
+
runtimeMode,
|
|
156
|
+
workspaceSelectionScope,
|
|
157
|
+
markBootstrapPhase,
|
|
158
|
+
markBootstrapStalled,
|
|
159
|
+
setRuntimeMode,
|
|
160
|
+
setAuthRequiredForApi,
|
|
161
|
+
setIsAuthenticated,
|
|
162
|
+
setAuthUserId,
|
|
163
|
+
setAuthWorkspaceId,
|
|
164
|
+
setAuthUserEmail,
|
|
165
|
+
setAuthUserDisplayName,
|
|
166
|
+
setAuthUserAvatarUrl,
|
|
167
|
+
setUserGlobalSyncStatus,
|
|
168
|
+
setUserGlobalSyncError,
|
|
169
|
+
setHasBetaAccess,
|
|
170
|
+
setAvailableWorkspaces,
|
|
171
|
+
setHydratedWorkspaceRole,
|
|
172
|
+
setAssigneeOptions,
|
|
173
|
+
setAuthBlocked,
|
|
174
|
+
setAuthSessionResolved,
|
|
175
|
+
setError,
|
|
176
|
+
applyResolvedWorkspaceId,
|
|
177
|
+
readPersistedWorkspaceId,
|
|
178
|
+
authSessionRequestRef,
|
|
179
|
+
authSessionEpochRef,
|
|
180
|
+
authSessionLastCheckedAtRef,
|
|
181
|
+
authSessionLastResultRef,
|
|
182
|
+
isLoopbackHost,
|
|
183
|
+
setAuthStateReadyRefs
|
|
184
|
+
]);
|
|
185
|
+
const optimisticLocalAuthUserId = runtimeMode === 'local' && isLoopbackHost && shouldProbeCloudAuth
|
|
186
|
+
? readLocalMutationActorUserId()
|
|
187
|
+
: '';
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
if (optimisticLocalAuthUserId) {
|
|
190
|
+
setIsAuthenticated(true);
|
|
191
|
+
setAuthSessionResolved(true);
|
|
192
|
+
setAuthUserId(optimisticLocalAuthUserId);
|
|
193
|
+
setAuthStateReadyRefs(true, true, false);
|
|
194
|
+
authSessionLastResultRef.current = true;
|
|
195
|
+
}
|
|
196
|
+
}, [optimisticLocalAuthUserId, setAuthSessionResolved, setAuthStateReadyRefs, setAuthUserId, setIsAuthenticated, authSessionLastResultRef]);
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
if (runtimeMode !== 'local' || !shouldProbeCloudAuth)
|
|
199
|
+
return;
|
|
200
|
+
if (typeof window === 'undefined')
|
|
201
|
+
return;
|
|
202
|
+
const recheck = () => { void checkAuthSession(); };
|
|
203
|
+
const onVisibilityChange = () => {
|
|
204
|
+
if (document.visibilityState === 'visible')
|
|
205
|
+
recheck();
|
|
206
|
+
};
|
|
207
|
+
window.addEventListener('focus', recheck);
|
|
208
|
+
window.addEventListener('online', recheck);
|
|
209
|
+
document.addEventListener('visibilitychange', onVisibilityChange);
|
|
210
|
+
const intervalId = window.setInterval(recheck, 30_000);
|
|
211
|
+
return () => {
|
|
212
|
+
window.removeEventListener('focus', recheck);
|
|
213
|
+
window.removeEventListener('online', recheck);
|
|
214
|
+
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
215
|
+
window.clearInterval(intervalId);
|
|
216
|
+
};
|
|
217
|
+
}, [runtimeMode, shouldProbeCloudAuth, checkAuthSession]);
|
|
218
|
+
return { checkAuthSession };
|
|
219
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { WorkspaceSyncPhase } from '../../sync/workspaceSyncState';
|
|
2
|
+
interface CreateSyncAuthCoordinatorArgs {
|
|
3
|
+
currentWorkspaceId: string;
|
|
4
|
+
workspaceSyncPhase: WorkspaceSyncPhase;
|
|
5
|
+
clearWorkspaceRetry: () => void;
|
|
6
|
+
setWorkspaceSyncBusy: (value: boolean) => void;
|
|
7
|
+
setUserGlobalSyncStatus: (value: 'error') => void;
|
|
8
|
+
setUserGlobalSyncError: (value: string) => void;
|
|
9
|
+
setWorkspaceLastErrorMessage: (value: string) => void;
|
|
10
|
+
setWorkspaceLastErrorAt: (value: string) => void;
|
|
11
|
+
reportSyncEvent: (workspaceId: string, event: {
|
|
12
|
+
eventType: 'pull' | 'handshake' | 'bootstrap' | 'apply' | 'push';
|
|
13
|
+
status: 'success' | 'error';
|
|
14
|
+
statusCode?: number;
|
|
15
|
+
errorMessage?: string;
|
|
16
|
+
}) => void;
|
|
17
|
+
setAuthBlocked: (value: boolean) => void;
|
|
18
|
+
setIsAuthenticated: (value: boolean) => void;
|
|
19
|
+
checkAuthSession: () => Promise<boolean>;
|
|
20
|
+
ensureCloudWorkspaceReadyForSync: () => Promise<{
|
|
21
|
+
success: boolean;
|
|
22
|
+
statusCode?: number;
|
|
23
|
+
error?: string;
|
|
24
|
+
transient?: boolean;
|
|
25
|
+
retryAfterMs?: number;
|
|
26
|
+
}>;
|
|
27
|
+
scheduleWorkspaceSyncRetry: (minDelayMs?: number) => void;
|
|
28
|
+
persistWorkspaceSyncPatchSafely: (patch: {
|
|
29
|
+
phase: 'error';
|
|
30
|
+
lastErrorMessage: string;
|
|
31
|
+
}) => void;
|
|
32
|
+
}
|
|
33
|
+
export declare function createSyncAuthCoordinator({ currentWorkspaceId, workspaceSyncPhase, clearWorkspaceRetry, setWorkspaceSyncBusy, setUserGlobalSyncStatus, setUserGlobalSyncError, setWorkspaceLastErrorMessage, setWorkspaceLastErrorAt, reportSyncEvent, setAuthBlocked, setIsAuthenticated, checkAuthSession, ensureCloudWorkspaceReadyForSync, scheduleWorkspaceSyncRetry, persistWorkspaceSyncPatchSafely }: CreateSyncAuthCoordinatorArgs): {
|
|
34
|
+
handleSyncAuthFailure: (source: "push" | "pull" | "apply" | "handshake", statusCode: number) => Promise<void>;
|
|
35
|
+
ensureWorkspaceSyncReady: () => Promise<boolean>;
|
|
36
|
+
};
|
|
37
|
+
export {};
|