chore: planning document almsot ready
This commit is contained in:
@@ -61,7 +61,7 @@ User-Agent: TimeSafari-DailyNotificationPlugin/1.0.0
|
|||||||
"version": "1.0.0"
|
"version": "1.0.0"
|
||||||
},
|
},
|
||||||
"previousClaim": {
|
"previousClaim": {
|
||||||
"jwtId": "1703980800_xyz789_ghi01234",
|
"jwtId": "1703980800_xyz789_0badf00d",
|
||||||
"claimType": "project_update",
|
"claimType": "project_update",
|
||||||
"claimData": {
|
"claimData": {
|
||||||
"status": "in_progress",
|
"status": "in_progress",
|
||||||
@@ -78,7 +78,7 @@ User-Agent: TimeSafari-DailyNotificationPlugin/1.0.0
|
|||||||
"hitLimit": false,
|
"hitLimit": false,
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"hasMore": true,
|
"hasMore": true,
|
||||||
"nextAfterId": "1704153600_mno345_pqr67890"
|
"nextAfterId": "1704153600_mno345_0badf00d"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -219,7 +219,7 @@ function compareJwtIds(a: string, b: string): number {
|
|||||||
return a.localeCompare(b);
|
return a.localeCompare(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example: "1704067200_abc123_def45678" < "1704153600_xyz789_ghi01234"
|
// Example: "1704067200_abc123_def45678" < "1704153600_xyz789_0badf00d"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Eventual Consistency Bounds**:
|
**Eventual Consistency Bounds**:
|
||||||
@@ -247,7 +247,7 @@ X-Idempotency-Key: {uuid}
|
|||||||
**Request Body**:
|
**Request Body**:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"acknowledgedJwtIds": ["1704067200_abc123_def45678", "1704153600_mno345_pqr67890"],
|
"acknowledgedJwtIds": ["1704067200_abc123_def45678", "1704153600_mno345_0badf00d"],
|
||||||
"acknowledgedAt": "2025-01-01T12:00:00Z",
|
"acknowledgedAt": "2025-01-01T12:00:00Z",
|
||||||
"clientVersion": "TimeSafari-DailyNotificationPlugin/1.0.0"
|
"clientVersion": "TimeSafari-DailyNotificationPlugin/1.0.0"
|
||||||
}
|
}
|
||||||
@@ -822,7 +822,9 @@ document.addEventListener('visibilitychange', () => {
|
|||||||
const lastPoll = localStorage.getItem('lastPollTimestamp');
|
const lastPoll = localStorage.getItem('lastPollTimestamp');
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (now - parseInt(lastPoll) > 3600000) { // 1 hour
|
if (now - parseInt(lastPoll) > 3600000) { // 1 hour
|
||||||
pollStarredProjects();
|
pollStarredProjects().then(() => {
|
||||||
|
localStorage.setItem('lastPollTimestamp', now.toString());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -841,13 +843,15 @@ document.addEventListener('visibilitychange', () => {
|
|||||||
↓
|
↓
|
||||||
[Make API Call] ← [Valid Config] ← [Has Starred Projects] ← [Has Last Ack ID]
|
[Make API Call] ← [Valid Config] ← [Has Starred Projects] ← [Has Last Ack ID]
|
||||||
↓ ↓ ↓ ↓
|
↓ ↓ ↓ ↓
|
||||||
[Network Error] [Parse Response] [Process Results] [Update Watermark]
|
[Network Error] [Parse Response] [Process Results] [Generate Notifications]
|
||||||
↓ ↓ ↓ ↓
|
↓ ↓ ↓ ↓
|
||||||
[Retry Logic] [Generate Notifications] [Success] [Commit State]
|
[Retry Logic] [Schedule Delivery] [Success] [Acknowledge Delivery]
|
||||||
↓ ↓ ↓ ↓
|
↓ ↓ ↓ ↓
|
||||||
[Exponential Backoff] [Schedule Delivery] [End] [End]
|
[Exponential Backoff] [Acknowledge Delivery] [End] [Update Watermark]
|
||||||
↓ ↓
|
↓ ↓ ↓ ↓
|
||||||
[Max Retries] [End]
|
[Max Retries] [Update Watermark] [End] [Commit State]
|
||||||
|
↓ ↓ ↓ ↓
|
||||||
|
[End] [Commit State] [End] [End]
|
||||||
↓
|
↓
|
||||||
[End]
|
[End]
|
||||||
```
|
```
|
||||||
@@ -858,12 +862,13 @@ document.addEventListener('visibilitychange', () => {
|
|||||||
3. **Check Last Ack ID**: If `lastAckedStarredPlanChangesJwtId` is null, run Bootstrap Watermark; then continue
|
3. **Check Last Ack ID**: If `lastAckedStarredPlanChangesJwtId` is null, run Bootstrap Watermark; then continue
|
||||||
4. **Make API Call**: Execute authenticated POST request
|
4. **Make API Call**: Execute authenticated POST request
|
||||||
5. **Process Results**: Parse response and extract change count
|
5. **Process Results**: Parse response and extract change count
|
||||||
6. **Update Watermark**: Advance watermark only after successful delivery AND acknowledgment
|
6. **Generate Notifications**: Create user notifications for changes
|
||||||
7. **Generate Notifications**: Create user notifications for changes
|
7. **Update Watermark**: Advance watermark only after successful delivery AND acknowledgment
|
||||||
|
|
||||||
#### Watermark Bootstrap Path
|
#### Watermark Bootstrap Path
|
||||||
|
|
||||||
**Bootstrap Implementation**:
|
**Bootstrap Implementation**:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
async function bootstrapWatermark(activeDid: string, starredPlanHandleIds: string[]): Promise<string> {
|
async function bootstrapWatermark(activeDid: string, starredPlanHandleIds: string[]): Promise<string> {
|
||||||
try {
|
try {
|
||||||
@@ -930,10 +935,10 @@ CREATE INDEX idx_outbox_undelivered ON notification_outbox(delivered_at) WHERE d
|
|||||||
|
|
||||||
**Atomic Transaction Pattern**:
|
**Atomic Transaction Pattern**:
|
||||||
```sql
|
```sql
|
||||||
-- Phase 1: Atomic commit of watermark + outbox
|
-- Phase 1: Atomic commit of outbox only (watermark stays unchanged)
|
||||||
BEGIN TRANSACTION;
|
BEGIN TRANSACTION;
|
||||||
INSERT INTO notification_outbox (jwt_id, content) VALUES (?, ?);
|
INSERT INTO notification_outbox (jwt_id, content) VALUES (?, ?);
|
||||||
UPDATE settings SET lastAckedStarredPlanChangesJwtId = ? WHERE accountDid = ?;
|
-- Do NOT update watermark here - it advances only after delivery + acknowledgment
|
||||||
COMMIT;
|
COMMIT;
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1011,7 +1016,7 @@ async function processPollingResults(results: PollingResult[]): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. Dispatcher delivers notifications
|
// 3. Dispatcher delivers notifications
|
||||||
await notificationDispatcher.processOutbox();
|
const { deliveredJwtIds, latestJwtId } = await notificationDispatcher.processOutbox();
|
||||||
|
|
||||||
// 4. After successful delivery, call acknowledgment endpoint
|
// 4. After successful delivery, call acknowledgment endpoint
|
||||||
await acknowledgeDeliveredNotifications(deliveredJwtIds);
|
await acknowledgeDeliveredNotifications(deliveredJwtIds);
|
||||||
@@ -1047,7 +1052,7 @@ UPDATE notification_outbox SET delivered_at = datetime('now'), acknowledged_at =
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Recovery Rules**:
|
**Recovery Rules**:
|
||||||
- **Crash After Watermark Update**: On restart, check `notification_pending` table for uncommitted notifications
|
- **Crash After Watermark Update**: On restart, check `notification_outbox` table for uncommitted notifications
|
||||||
- **Crash Before Watermark Update**: Safe to retry - no state change occurred
|
- **Crash Before Watermark Update**: Safe to retry - no state change occurred
|
||||||
- **Partial Notification Failure**: Rollback watermark update, retry entire transaction
|
- **Partial Notification Failure**: Rollback watermark update, retry entire transaction
|
||||||
- **Acknowledgment Endpoint**: Call `POST /api/v2/plans/acknowledge` after successful notification delivery
|
- **Acknowledgment Endpoint**: Call `POST /api/v2/plans/acknowledge` after successful notification delivery
|
||||||
@@ -1055,13 +1060,13 @@ UPDATE notification_outbox SET delivered_at = datetime('now'), acknowledged_at =
|
|||||||
**Recovery Implementation**:
|
**Recovery Implementation**:
|
||||||
```typescript
|
```typescript
|
||||||
async function recoverPendingNotifications(): Promise<void> {
|
async function recoverPendingNotifications(): Promise<void> {
|
||||||
const pending = await db.query('SELECT * FROM notification_pending WHERE created_at < ?',
|
const pending = await db.query('SELECT * FROM notification_outbox WHERE created_at < ?',
|
||||||
[Date.now() - 300000]); // 5 minutes ago
|
[Date.now() - 300000]); // 5 minutes ago
|
||||||
|
|
||||||
for (const notification of pending) {
|
for (const notification of pending) {
|
||||||
try {
|
try {
|
||||||
await scheduleNotification(notification.content);
|
await scheduleNotification(notification.content);
|
||||||
await db.query('DELETE FROM notification_pending WHERE id = ?', [notification.id]);
|
await db.query('DELETE FROM notification_outbox WHERE id = ?', [notification.id]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Log error, will retry on next recovery cycle
|
// Log error, will retry on next recovery cycle
|
||||||
console.error('Failed to recover notification:', error);
|
console.error('Failed to recover notification:', error);
|
||||||
@@ -1118,7 +1123,7 @@ Body: "You have {count} new updates in your starred projects"
|
|||||||
|
|
||||||
**Deep Link Routes**:
|
**Deep Link Routes**:
|
||||||
```
|
```
|
||||||
timesafari://projects/updates?jwtIds=1704067200_abc123_def45678,1704153600_mno345_pqr67890
|
timesafari://projects/updates?jwtIds=1704067200_abc123_def45678,1704153600_mno345_0badf00d
|
||||||
timesafari://projects/{projectId}/details?jwtId=1704067200_abc123_def45678
|
timesafari://projects/{projectId}/details?jwtId=1704067200_abc123_def45678
|
||||||
timesafari://notifications/starred-projects
|
timesafari://notifications/starred-projects
|
||||||
timesafari://projects/updates?shortlink=abc123def456789
|
timesafari://projects/updates?shortlink=abc123def456789
|
||||||
@@ -1153,7 +1158,7 @@ function validateDeepLinkParams(params: any): DeepLinkValidation {
|
|||||||
```typescript
|
```typescript
|
||||||
// Server generates shortlink for large result sets
|
// Server generates shortlink for large result sets
|
||||||
const shortlink = await generateShortlink({
|
const shortlink = await generateShortlink({
|
||||||
jwtIds: ['1704067200_abc123_def45678', '1704153600_mno345_pqr67890', /* ... 50 more */],
|
jwtIds: ['1704067200_abc123_def45678', '1704153600_mno345_0badf00d', /* ... 50 more */],
|
||||||
expiresAt: Date.now() + 86400000 // 24 hours
|
expiresAt: Date.now() + 86400000 // 24 hours
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1384,125 +1389,6 @@ const PII_REDACTION_PATTERNS = [
|
|||||||
|
|
||||||
### Testing Artifacts
|
### Testing Artifacts
|
||||||
|
|
||||||
#### Mock Fixtures
|
|
||||||
|
|
||||||
**Empty Response**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"data": [],
|
|
||||||
"hitLimit": false,
|
|
||||||
"pagination": {
|
|
||||||
"hasMore": false,
|
|
||||||
"nextAfterId": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Small Response (3 items)**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"planSummary": {
|
|
||||||
"jwtId": "1704067200_abc123_def45678",
|
|
||||||
"handleId": "test_project_1",
|
|
||||||
"name": "Test Project 1",
|
|
||||||
"description": "First test project"
|
|
||||||
},
|
|
||||||
"previousClaim": {
|
|
||||||
"jwtId": "1703980800_xyz789_ghi01234",
|
|
||||||
"claimType": "project_update"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"planSummary": {
|
|
||||||
"jwtId": "1704153600_mno345_pqr67890",
|
|
||||||
"handleId": "test_project_2",
|
|
||||||
"name": "Test Project 2",
|
|
||||||
"description": "Second test project"
|
|
||||||
},
|
|
||||||
"previousClaim": {
|
|
||||||
"jwtId": "1704067200_stu901_vwx23456",
|
|
||||||
"claimType": "project_update"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"planSummary": {
|
|
||||||
"jwtId": "1704240000_new123_0badf00d",
|
|
||||||
"handleId": "test_project_3",
|
|
||||||
"name": "Test Project 3",
|
|
||||||
"description": "Third test project"
|
|
||||||
},
|
|
||||||
"previousClaim": {
|
|
||||||
"jwtId": "1704153600_old456_1cafebad",
|
|
||||||
"claimType": "project_update"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"hitLimit": false,
|
|
||||||
"pagination": {
|
|
||||||
"hasMore": false,
|
|
||||||
"nextAfterId": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Paginated Response**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"data": [...], // 100 items
|
|
||||||
"hitLimit": true,
|
|
||||||
"pagination": {
|
|
||||||
"hasMore": true,
|
|
||||||
"nextAfterId": "1704153600_mno345_pqr67890"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rate Limited Response (canonical format)**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": "Rate limit exceeded",
|
|
||||||
"code": "RATE_LIMIT_EXCEEDED",
|
|
||||||
"retryAfter": 60,
|
|
||||||
"details": {
|
|
||||||
"limit": 100,
|
|
||||||
"window": "1m",
|
|
||||||
"remaining": 0,
|
|
||||||
"resetAt": "2024-01-01T12:01:00Z"
|
|
||||||
},
|
|
||||||
"requestId": "req_jkl012"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Contract Tests**:
|
|
||||||
```typescript
|
|
||||||
// JWT ID comparison helper
|
|
||||||
function compareJwtIds(a: string, b: string): number {
|
|
||||||
return a.localeCompare(b); // Lexicographic comparison for fixed-width format
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('StarredProjectsPolling Contract Tests', () => {
|
|
||||||
test('should maintain JWT ID ordering', () => {
|
|
||||||
const response = mockPaginatedResponse();
|
|
||||||
const jwtIds = response.data.map(item => item.planSummary.jwtId);
|
|
||||||
const sortedIds = [...jwtIds].sort(compareJwtIds);
|
|
||||||
expect(jwtIds).toEqual(sortedIds);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should handle watermark movement correctly', () => {
|
|
||||||
const initialWatermark = '1704067200_abc123_def45678';
|
|
||||||
const response = mockSmallResponse();
|
|
||||||
const newWatermark = getNewWatermark(response);
|
|
||||||
expect(compareJwtIds(newWatermark, initialWatermark)).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should respect pagination limits', () => {
|
|
||||||
const response = mockPaginatedResponse();
|
|
||||||
expect(response.data.length).toBeLessThanOrEqual(100);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Testing Fixtures & SLAs
|
#### Testing Fixtures & SLAs
|
||||||
|
|
||||||
@@ -1533,16 +1419,16 @@ describe('StarredProjectsPolling Contract Tests', () => {
|
|||||||
"issuerDid": "did:key:test_issuer_1",
|
"issuerDid": "did:key:test_issuer_1",
|
||||||
"agentDid": "did:key:test_agent_1",
|
"agentDid": "did:key:test_agent_1",
|
||||||
"locLat": 40.7128,
|
"locLat": 40.7128,
|
||||||
"locLon": -74.0060,
|
"locLon": -74.0060
|
||||||
},
|
},
|
||||||
"previousClaim": {
|
"previousClaim": {
|
||||||
"jwtId": "1703980800_xyz789_ghi01234",
|
"jwtId": "1703980800_xyz789_0badf00d",
|
||||||
"claimType": "project_update"
|
"claimType": "project_update"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"planSummary": {
|
"planSummary": {
|
||||||
"jwtId": "1704153600_mno345_pqr67890",
|
"jwtId": "1704153600_mno345_0badf00d",
|
||||||
"handleId": "test_project_2",
|
"handleId": "test_project_2",
|
||||||
"name": "Test Project 2",
|
"name": "Test Project 2",
|
||||||
"description": "Second test project",
|
"description": "Second test project",
|
||||||
@@ -1552,7 +1438,7 @@ describe('StarredProjectsPolling Contract Tests', () => {
|
|||||||
"locLon": null
|
"locLon": null
|
||||||
},
|
},
|
||||||
"previousClaim": {
|
"previousClaim": {
|
||||||
"jwtId": "1704067200_stu901_vwx23456",
|
"jwtId": "1704067200_stu901_1cafebad",
|
||||||
"claimType": "project_update"
|
"claimType": "project_update"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1568,7 +1454,7 @@ describe('StarredProjectsPolling Contract Tests', () => {
|
|||||||
"locLon": -122.4194
|
"locLon": -122.4194
|
||||||
},
|
},
|
||||||
"previousClaim": {
|
"previousClaim": {
|
||||||
"jwtId": "1704153600_old456_1cafebad",
|
"jwtId": "1704153600_old456_0badf00d",
|
||||||
"claimType": "project_update"
|
"claimType": "project_update"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1585,12 +1471,12 @@ describe('StarredProjectsPolling Contract Tests', () => {
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"data": [
|
"data": [
|
||||||
// ... 100 items with jwtIds from "1704067200_abc123_def45678" to "1704153600_xyz789_ghi01234"
|
// ... 100 items with jwtIds from "1704067200_abc123_def45678" to "1704153600_xyz789_0badf00d"
|
||||||
],
|
],
|
||||||
"hitLimit": true,
|
"hitLimit": true,
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"hasMore": true,
|
"hasMore": true,
|
||||||
"nextAfterId": "1704240000_new123_0badf00d"
|
"nextAfterId": "1704240000_new123_1cafebad"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -1625,7 +1511,7 @@ describe('StarredProjectsPolling Contract Tests', () => {
|
|||||||
"locLon": null
|
"locLon": null
|
||||||
},
|
},
|
||||||
"previousClaim": {
|
"previousClaim": {
|
||||||
"jwtId": "1703980800_xyz789_ghi01234",
|
"jwtId": "1703980800_xyz789_0badf00d",
|
||||||
"claimType": "project_update"
|
"claimType": "project_update"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1644,7 +1530,7 @@ describe('StarredProjectsPolling Contract Tests', () => {
|
|||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
"planSummary": {
|
"planSummary": {
|
||||||
"jwtId": "1704153600_mno345_pqr67890",
|
"jwtId": "1704153600_mno345_0badf00d",
|
||||||
"handleId": "test_project_2",
|
"handleId": "test_project_2",
|
||||||
"locLat": null,
|
"locLat": null,
|
||||||
"locLon": null
|
"locLon": null
|
||||||
@@ -1655,7 +1541,7 @@ describe('StarredProjectsPolling Contract Tests', () => {
|
|||||||
"jwtId": "1704067200_abc123_def45678",
|
"jwtId": "1704067200_abc123_def45678",
|
||||||
"handleId": "test_project_1",
|
"handleId": "test_project_1",
|
||||||
"locLat": 40.7128,
|
"locLat": 40.7128,
|
||||||
"locLon": -74.0060,
|
"locLon": -74.0060
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -2166,11 +2052,24 @@ class StarredProjectsPollingManager {
|
|||||||
return StarredProjectsPollingResult(changeCount: 0, hitLimit: false, error: "No starred projects")
|
return StarredProjectsPollingResult(changeCount: 0, hitLimit: false, error: "No starred projects")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Check if we have last acknowledged ID
|
// 3. Check if we have last acknowledged ID, bootstrap if missing
|
||||||
guard let lastAckedId = config.lastAckedStarredPlanChangesJwtId,
|
guard let lastAckedId = config.lastAckedStarredPlanChangesJwtId,
|
||||||
!lastAckedId.isEmpty else {
|
!lastAckedId.isEmpty else {
|
||||||
print("\(TAG): No last acknowledged ID, skipping poll")
|
print("\(TAG): No last acknowledged ID, running bootstrap watermark")
|
||||||
return StarredProjectsPollingResult(changeCount: 0, hitLimit: false, error: "No last acknowledged ID")
|
let bootstrapWatermark = try await bootstrapWatermark(activeDid: config.activeDid, starredPlanHandleIds: starredPlanHandleIds)
|
||||||
|
if let bootstrapWatermark = bootstrapWatermark {
|
||||||
|
// Update config with bootstrap watermark and reload
|
||||||
|
try await updateLastAckedId(bootstrapWatermark, for: config.activeDid)
|
||||||
|
let updatedConfig = try await getUserConfiguration()
|
||||||
|
if let updatedConfig = updatedConfig {
|
||||||
|
print("\(TAG): Bootstrap watermark set: \(bootstrapWatermark)")
|
||||||
|
// Continue with updated config
|
||||||
|
return try await pollStarredProjectChangesWithConfig(updatedConfig)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("\(TAG): Bootstrap watermark failed, skipping poll")
|
||||||
|
return StarredProjectsPollingResult(changeCount: 0, hitLimit: false, error: "Bootstrap watermark failed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Make API call
|
// 4. Make API call
|
||||||
@@ -2331,10 +2230,23 @@ export class StarredProjectsPollingManager {
|
|||||||
return { changeCount: 0, hitLimit: false, error: 'No starred projects' };
|
return { changeCount: 0, hitLimit: false, error: 'No starred projects' };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Check if we have last acknowledged ID
|
// 3. Check if we have last acknowledged ID, bootstrap if missing
|
||||||
if (!config.lastAckedStarredPlanChangesJwtId) {
|
if (!config.lastAckedStarredPlanChangesJwtId) {
|
||||||
console.log('StarredProjectsPollingManager: No last acknowledged ID, skipping poll');
|
console.log('StarredProjectsPollingManager: No last acknowledged ID, running bootstrap watermark');
|
||||||
return { changeCount: 0, hitLimit: false, error: 'No last acknowledged ID' };
|
const bootstrapWatermark = await this.bootstrapWatermark(config.activeDid, config.starredPlanHandleIds);
|
||||||
|
if (bootstrapWatermark) {
|
||||||
|
// Update config with bootstrap watermark and reload
|
||||||
|
await this.updateLastAckedId(bootstrapWatermark, config.activeDid);
|
||||||
|
const updatedConfig = await this.getUserConfiguration();
|
||||||
|
if (updatedConfig) {
|
||||||
|
console.log(`StarredProjectsPollingManager: Bootstrap watermark set: ${bootstrapWatermark}`);
|
||||||
|
// Continue with updated config
|
||||||
|
return await this.pollStarredProjectChangesWithConfig(updatedConfig);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('StarredProjectsPollingManager: Bootstrap watermark failed, skipping poll');
|
||||||
|
return { changeCount: 0, hitLimit: false, error: 'Bootstrap watermark failed' };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Make API call
|
// 4. Make API call
|
||||||
|
|||||||
Reference in New Issue
Block a user