@openclaw/imessage 2026.9.1-beta.1 → 2026.9.2
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/{accounts-DIpGOIiN.js → accounts-CCirBEIm.js} +2 -3
- package/dist/{actions.runtime-BhggncbG.js → actions.runtime-D5MAgeSs.js} +5 -5
- package/dist/api.js +5 -5
- package/dist/{approval-handler.runtime-BC-YsgAf.js → approval-handler.runtime-47rs6THN.js} +14 -9
- package/dist/{approval-reactions-Cvwmdlev.js → approval-reactions-sVpDno7d.js} +8 -16
- package/dist/{channel-CnKQ2MVB.js → channel-BxraMrmK.js} +21 -14
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.runtime-BQLCh7bE.js → channel.runtime-2jwkMPGR.js} +59 -14
- package/dist/{config-schema-DV0KP5nC.js → config-schema-DuGbA8Kp.js} +8 -7
- package/dist/contract-api.js +1 -1
- package/dist/doctor-contract-api.js +8 -281
- package/dist/{group-policy-BipELT2x.js → group-policy-Cp8SDG4G.js} +18 -12
- package/dist/media-contract-api.js +1 -1
- package/dist/{media-contract-BVXuQHG2.js → media-contract-pns3TSS5.js} +3 -2
- package/dist/{message-tool-api-BwIxJDoz.js → message-tool-api-CUoFESHj.js} +11 -6
- package/dist/message-tool-api.js +1 -1
- package/dist/{monitor-DRD_Dnp9.js → monitor-CESwxMQ4.js} +319 -48
- package/dist/{question-reactions-viP7ZcyG.js → question-reactions-9tBYB1_F.js} +4 -7
- package/dist/{reaction-context-BAYI7pz0.js → reaction-context-Br-vAJFp.js} +9 -3
- package/dist/{remote-file-CyTTuRAt.js → remote-file-Biz5PjXi.js} +5 -53
- package/dist/runtime-Cza4CY5T.js +8 -0
- package/dist/runtime-api.js +7 -7
- package/dist/{sanitize-outbound-g651fIhi.js → sanitize-outbound-Bvo4g1Mp.js} +470 -82
- package/dist/{send-DVQY6M5M.js → send-DC981dqK.js} +146 -23
- package/dist/state-contract-DvahEY4X.js +56 -0
- package/dist/state-migrations-D-fyHVcb.js +277 -0
- package/node_modules/typebox/build/format/email.mjs +1 -1
- package/node_modules/typebox/build/format/idna/idn-hostname.mjs +19 -1
- package/node_modules/typebox/build/format/json_pointer.d.mts +2 -2
- package/node_modules/typebox/build/format/json_pointer.mjs +2 -2
- package/node_modules/typebox/build/format/json_pointer_uri_fragment.d.mts +2 -2
- package/node_modules/typebox/build/format/json_pointer_uri_fragment.mjs +2 -2
- package/node_modules/typebox/build/format/relative_json_pointer.d.mts +2 -2
- package/node_modules/typebox/build/format/relative_json_pointer.mjs +2 -2
- package/node_modules/typebox/build/system/memory/assign.mjs +2 -1
- package/node_modules/typebox/build/system/memory/create.mjs +3 -4
- package/node_modules/typebox/build/system/memory/discard.mjs +2 -2
- package/node_modules/typebox/build/system/memory/freeze.d.mts +4 -0
- package/node_modules/typebox/build/system/memory/freeze.mjs +6 -0
- package/node_modules/typebox/build/system/memory/update.mjs +3 -2
- package/node_modules/typebox/package.json +1 -1
- package/openclaw.plugin.json +2 -7
- package/package.json +5 -5
- package/dist/catchup-Dh-Rv4Ur.js +0 -294
- package/dist/monitor-reply-cache-BdeUQaHO.js +0 -353
- package/dist/persisted-echo-cache-DGxy-J4t.js +0 -143
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// deno-lint-ignore-file ban-types no-explicit-any
|
|
2
2
|
// deno-fmt-ignore-file
|
|
3
3
|
import { Metrics } from './metrics.mjs';
|
|
4
|
+
import { Freeze } from './freeze.mjs';
|
|
4
5
|
/**
|
|
5
6
|
* Performs an Object assign using the Left and Right object types. We track this operation as it
|
|
6
7
|
* creates a new GC handle per assignment.
|
|
7
8
|
*/
|
|
8
9
|
export function Assign(left, right) {
|
|
9
10
|
Metrics.assign += 1;
|
|
10
|
-
return { ...left, ...right };
|
|
11
|
+
return Freeze({ ...left, ...right });
|
|
11
12
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// deno-lint-ignore-file no-explicit-any
|
|
2
|
-
// deno-fmt-ignore-file
|
|
3
2
|
import { Settings } from '../settings/index.mjs';
|
|
4
3
|
import { Metrics } from './metrics.mjs';
|
|
4
|
+
import { Freeze } from './freeze.mjs';
|
|
5
5
|
function MergeHidden(left, right) {
|
|
6
6
|
for (const key of Object.keys(right)) {
|
|
7
7
|
Object.defineProperty(left, key, {
|
|
@@ -23,8 +23,7 @@ function Merge(left, right) {
|
|
|
23
23
|
*/
|
|
24
24
|
export function Create(hidden, enumerable, options = {}) {
|
|
25
25
|
Metrics.create += 1;
|
|
26
|
-
const settings = Settings.Get();
|
|
27
26
|
const withOptions = Merge(enumerable, options);
|
|
28
|
-
const withHidden =
|
|
29
|
-
return
|
|
27
|
+
const withHidden = Settings.Get().enumerableKind ? Merge(withOptions, hidden) : MergeHidden(withOptions, hidden);
|
|
28
|
+
return Freeze(withHidden);
|
|
30
29
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// deno-lint-ignore-file no-explicit-any
|
|
2
|
-
// deno-fmt-ignore-file
|
|
3
2
|
import { Guard } from '../../guard/index.mjs';
|
|
4
3
|
import { Metrics } from './metrics.mjs';
|
|
4
|
+
import { Freeze } from './freeze.mjs';
|
|
5
5
|
import { Clone } from './clone.mjs';
|
|
6
6
|
/** Discards multiple property keys from the given object value */
|
|
7
7
|
export function Discard(value, propertyKeys) {
|
|
@@ -14,5 +14,5 @@ export function Discard(value, propertyKeys) {
|
|
|
14
14
|
descriptor.value = Clone(descriptor.value);
|
|
15
15
|
Object.defineProperty(result, key, descriptor);
|
|
16
16
|
}
|
|
17
|
-
return result;
|
|
17
|
+
return Freeze(result);
|
|
18
18
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// deno-lint-ignore-file no-explicit-any
|
|
2
|
+
import { Settings } from '../settings/index.mjs';
|
|
3
|
+
/** Conditionally freezes the value if `immutableTypes` is true, otherwise no action. */
|
|
4
|
+
export function Freeze(value) {
|
|
5
|
+
return Settings.Get().immutableTypes ? Object.freeze(value) : value;
|
|
6
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
// deno-
|
|
1
|
+
// deno-lint-ignore-file no-explicit-any
|
|
2
2
|
import { Settings } from '../settings/index.mjs';
|
|
3
3
|
import { Metrics } from './metrics.mjs';
|
|
4
|
+
import { Freeze } from './freeze.mjs';
|
|
4
5
|
import { Clone } from './clone.mjs';
|
|
5
6
|
/**
|
|
6
7
|
* Updates a value with new properties while preserving property enumerability. Use this function to modify
|
|
@@ -28,5 +29,5 @@ export function Update(current, hidden, enumerable) {
|
|
|
28
29
|
value: enumerable[key]
|
|
29
30
|
});
|
|
30
31
|
}
|
|
31
|
-
return result;
|
|
32
|
+
return Freeze(result);
|
|
32
33
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "imessage",
|
|
3
|
+
"name": "iMessage",
|
|
4
|
+
"description": "OpenClaw iMessage channel plugin using imsg on a signed-in Mac.",
|
|
3
5
|
"doctorContract": {
|
|
4
6
|
"configRepair": true,
|
|
5
7
|
"stateMigrations": true
|
|
6
8
|
},
|
|
7
|
-
"icon": "https://cdn.simpleicons.org/imessage",
|
|
8
9
|
"activation": {
|
|
9
10
|
"onStartup": false
|
|
10
11
|
},
|
|
@@ -467,7 +468,6 @@
|
|
|
467
468
|
"type": "boolean"
|
|
468
469
|
},
|
|
469
470
|
"dmPolicy": {
|
|
470
|
-
"default": "pairing",
|
|
471
471
|
"type": "string",
|
|
472
472
|
"enum": [
|
|
473
473
|
"pairing",
|
|
@@ -506,7 +506,6 @@
|
|
|
506
506
|
}
|
|
507
507
|
},
|
|
508
508
|
"groupPolicy": {
|
|
509
|
-
"default": "allowlist",
|
|
510
509
|
"type": "string",
|
|
511
510
|
"enum": [
|
|
512
511
|
"open",
|
|
@@ -840,10 +839,6 @@
|
|
|
840
839
|
}
|
|
841
840
|
}
|
|
842
841
|
},
|
|
843
|
-
"required": [
|
|
844
|
-
"dmPolicy",
|
|
845
|
-
"groupPolicy"
|
|
846
|
-
],
|
|
847
842
|
"additionalProperties": false
|
|
848
843
|
}
|
|
849
844
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/imessage",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.2",
|
|
4
4
|
"description": "OpenClaw iMessage channel plugin using imsg on a signed-in Mac",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"typebox": "1.3.
|
|
11
|
+
"typebox": "1.3.18",
|
|
12
12
|
"zod": "4.4.3"
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
|
-
"openclaw": ">=2026.9.
|
|
15
|
+
"openclaw": ">=2026.9.2"
|
|
16
16
|
},
|
|
17
17
|
"peerDependenciesMeta": {
|
|
18
18
|
"openclaw": {
|
|
@@ -92,10 +92,10 @@
|
|
|
92
92
|
"allowInvalidConfigRecovery": true
|
|
93
93
|
},
|
|
94
94
|
"compat": {
|
|
95
|
-
"pluginApi": ">=2026.9.
|
|
95
|
+
"pluginApi": ">=2026.9.2"
|
|
96
96
|
},
|
|
97
97
|
"build": {
|
|
98
|
-
"openclawVersion": "2026.9.
|
|
98
|
+
"openclawVersion": "2026.9.2",
|
|
99
99
|
"bundledDist": false
|
|
100
100
|
},
|
|
101
101
|
"release": {
|
package/dist/catchup-Dh-Rv4Ur.js
DELETED
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
import { f as getIMessageRuntime } from "./monitor-reply-cache-BdeUQaHO.js";
|
|
2
|
-
import { resolveIntegerOption } from "openclaw/plugin-sdk/number-runtime";
|
|
3
|
-
import { createHash } from "node:crypto";
|
|
4
|
-
import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
|
|
5
|
-
//#region extensions/imessage/src/monitor/catchup.ts
|
|
6
|
-
const DEFAULT_MAX_AGE_MINUTES = 120;
|
|
7
|
-
const MAX_MAX_AGE_MINUTES = 720;
|
|
8
|
-
const DEFAULT_PER_RUN_LIMIT = 50;
|
|
9
|
-
const MAX_PER_RUN_LIMIT = 500;
|
|
10
|
-
const DEFAULT_FIRST_RUN_LOOKBACK_MINUTES = 30;
|
|
11
|
-
const DEFAULT_MAX_FAILURE_RETRIES = 10;
|
|
12
|
-
const MAX_MAX_FAILURE_RETRIES = 1e3;
|
|
13
|
-
const MAX_FAILURE_RETRY_MAP_SIZE = 512;
|
|
14
|
-
const MAX_FAILURE_RETRY_MAP_JSON_BYTES = 48e3;
|
|
15
|
-
const textEncoder = new TextEncoder();
|
|
16
|
-
const IMESSAGE_CATCHUP_CURSOR_NAMESPACE = "imessage.catchup-cursors";
|
|
17
|
-
const cursorWriteQueue = new KeyedAsyncQueue();
|
|
18
|
-
function resolveIMessageCatchupCursorKey(accountId) {
|
|
19
|
-
return createHash("sha256").update(accountId, "utf8").digest("hex").slice(0, 32);
|
|
20
|
-
}
|
|
21
|
-
function openCatchupCursorStore() {
|
|
22
|
-
return getIMessageRuntime().state.openSyncKeyedStore({
|
|
23
|
-
namespace: IMESSAGE_CATCHUP_CURSOR_NAMESPACE,
|
|
24
|
-
maxEntries: 256
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
function updateCatchupCursorStore(key, updateValue) {
|
|
28
|
-
const store = openCatchupCursorStore();
|
|
29
|
-
if (!store.update) throw new Error("iMessage catchup cursor persistence requires atomic plugin-state update.");
|
|
30
|
-
return store.update(key, updateValue);
|
|
31
|
-
}
|
|
32
|
-
function enqueueCursorWrite(accountId, fn) {
|
|
33
|
-
const key = resolveIMessageCatchupCursorKey(accountId);
|
|
34
|
-
return cursorWriteQueue.enqueue(key, fn);
|
|
35
|
-
}
|
|
36
|
-
function sanitizeFailureRetriesInput(raw) {
|
|
37
|
-
if (!raw || typeof raw !== "object") return {};
|
|
38
|
-
const out = {};
|
|
39
|
-
for (const [guid, count] of Object.entries(raw)) {
|
|
40
|
-
if (!guid || typeof guid !== "string") continue;
|
|
41
|
-
if (typeof count !== "number" || !Number.isFinite(count) || count <= 0) continue;
|
|
42
|
-
out[guid] = Math.floor(count);
|
|
43
|
-
}
|
|
44
|
-
return out;
|
|
45
|
-
}
|
|
46
|
-
function normalizeIMessageCatchupCursor(value) {
|
|
47
|
-
if (!value || typeof value !== "object") return null;
|
|
48
|
-
const raw = value;
|
|
49
|
-
if (typeof raw.lastSeenMs !== "number" || !Number.isFinite(raw.lastSeenMs)) return null;
|
|
50
|
-
if (typeof raw.lastSeenRowid !== "number" || !Number.isFinite(raw.lastSeenRowid)) return null;
|
|
51
|
-
const failureRetries = sanitizeFailureRetriesInput(raw.failureRetries);
|
|
52
|
-
const hasRetries = Object.keys(failureRetries).length > 0;
|
|
53
|
-
return {
|
|
54
|
-
lastSeenMs: raw.lastSeenMs,
|
|
55
|
-
lastSeenRowid: raw.lastSeenRowid,
|
|
56
|
-
updatedAt: typeof raw.updatedAt === "number" ? raw.updatedAt : 0,
|
|
57
|
-
...hasRetries ? { failureRetries } : {}
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
function readIMessageCatchupCursor(accountId) {
|
|
61
|
-
return normalizeIMessageCatchupCursor(openCatchupCursorStore().lookup(resolveIMessageCatchupCursorKey(accountId)));
|
|
62
|
-
}
|
|
63
|
-
async function loadIMessageCatchupCursor(accountId) {
|
|
64
|
-
return readIMessageCatchupCursor(accountId);
|
|
65
|
-
}
|
|
66
|
-
function buildIMessageCatchupCursor(next) {
|
|
67
|
-
const sanitized = sanitizeFailureRetriesInput(next.failureRetries);
|
|
68
|
-
const hasRetries = Object.keys(sanitized).length > 0;
|
|
69
|
-
return {
|
|
70
|
-
lastSeenMs: next.lastSeenMs,
|
|
71
|
-
lastSeenRowid: next.lastSeenRowid,
|
|
72
|
-
updatedAt: Date.now(),
|
|
73
|
-
...hasRetries ? { failureRetries: sanitized } : {}
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
async function saveIMessageCatchupCursor(accountId, next, options = {}) {
|
|
77
|
-
const cursor = buildIMessageCatchupCursor(next);
|
|
78
|
-
updateCatchupCursorStore(resolveIMessageCatchupCursorKey(accountId), (existingValue) => {
|
|
79
|
-
const existing = normalizeIMessageCatchupCursor(existingValue);
|
|
80
|
-
if (existing && cursor.lastSeenRowid < existing.lastSeenRowid) {
|
|
81
|
-
if (!options.allowCursorRewindForRetries) return;
|
|
82
|
-
return buildIMessageCatchupCursor({
|
|
83
|
-
lastSeenMs: cursor.lastSeenMs,
|
|
84
|
-
lastSeenRowid: cursor.lastSeenRowid,
|
|
85
|
-
failureRetries: {
|
|
86
|
-
...existing.failureRetries,
|
|
87
|
-
...cursor.failureRetries
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
return cursor;
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Bound the retry map so a pathological storm of unique failing GUIDs
|
|
96
|
-
* cannot grow the cursor file without limit. Keeps the `maxSize` entries
|
|
97
|
-
* with the highest counts (closest to give-up) when over the bound.
|
|
98
|
-
*/
|
|
99
|
-
function capFailureRetriesMap(map, maxSize = MAX_FAILURE_RETRY_MAP_SIZE, maxBytes = MAX_FAILURE_RETRY_MAP_JSON_BYTES) {
|
|
100
|
-
const entries = Object.entries(map);
|
|
101
|
-
if (entries.length <= maxSize && textEncoder.encode(JSON.stringify(map)).byteLength <= maxBytes) return map;
|
|
102
|
-
entries.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
103
|
-
const capped = {};
|
|
104
|
-
for (const [guid, count] of entries.slice(0, maxSize)) {
|
|
105
|
-
capped[guid] = count;
|
|
106
|
-
if (textEncoder.encode(JSON.stringify(capped)).byteLength > maxBytes) {
|
|
107
|
-
delete capped[guid];
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return capped;
|
|
112
|
-
}
|
|
113
|
-
function clampInt(value, min, max, fallback) {
|
|
114
|
-
return resolveIntegerOption(value, fallback, {
|
|
115
|
-
min,
|
|
116
|
-
max
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
function resolveCatchupConfig(raw) {
|
|
120
|
-
return {
|
|
121
|
-
enabled: Boolean(raw?.enabled),
|
|
122
|
-
maxAgeMinutes: clampInt(raw?.maxAgeMinutes, 1, MAX_MAX_AGE_MINUTES, DEFAULT_MAX_AGE_MINUTES),
|
|
123
|
-
perRunLimit: clampInt(raw?.perRunLimit, 1, MAX_PER_RUN_LIMIT, DEFAULT_PER_RUN_LIMIT),
|
|
124
|
-
firstRunLookbackMinutes: clampInt(raw?.firstRunLookbackMinutes, 1, MAX_MAX_AGE_MINUTES, DEFAULT_FIRST_RUN_LOOKBACK_MINUTES),
|
|
125
|
-
maxFailureRetries: clampInt(raw?.maxFailureRetries, 1, MAX_MAX_FAILURE_RETRIES, DEFAULT_MAX_FAILURE_RETRIES)
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
async function advanceIMessageCatchupCursor(accountId, next, config) {
|
|
129
|
-
if (!Number.isFinite(next.lastSeenMs) || !Number.isFinite(next.lastSeenRowid)) return false;
|
|
130
|
-
return await enqueueCursorWrite(accountId, async () => {
|
|
131
|
-
let advanced = false;
|
|
132
|
-
updateCatchupCursorStore(resolveIMessageCatchupCursorKey(accountId), (existingValue) => {
|
|
133
|
-
const cursor = normalizeIMessageCatchupCursor(existingValue);
|
|
134
|
-
if (cursor && next.lastSeenRowid <= cursor.lastSeenRowid) return;
|
|
135
|
-
if (Object.values(cursor?.failureRetries ?? {}).some((count) => count < config.maxFailureRetries)) return;
|
|
136
|
-
advanced = true;
|
|
137
|
-
return buildIMessageCatchupCursor({
|
|
138
|
-
lastSeenMs: Math.max(cursor?.lastSeenMs ?? next.lastSeenMs, next.lastSeenMs),
|
|
139
|
-
lastSeenRowid: next.lastSeenRowid,
|
|
140
|
-
failureRetries: cursor?.failureRetries
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
return advanced;
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* One catchup pass. Loads the cursor, fetches `messages.history`, replays
|
|
148
|
-
* each row through `dispatch`, advances the cursor on success / give-up,
|
|
149
|
-
* persists the cursor, returns a summary.
|
|
150
|
-
*
|
|
151
|
-
* The fetch and dispatch functions are injected so this loop is unit-testable
|
|
152
|
-
* without standing up an `imsg` daemon. The wiring in `monitor-provider.ts`
|
|
153
|
-
* passes the live `client.request("messages.history", ...)` adapter as
|
|
154
|
-
* `fetch` and the `evaluateIMessageInbound` + `runChannelInboundEvent`
|
|
155
|
-
* pipeline as `dispatch`.
|
|
156
|
-
*/
|
|
157
|
-
async function performIMessageCatchup(params) {
|
|
158
|
-
const now = params.now ?? Date.now();
|
|
159
|
-
const cfg = params.config;
|
|
160
|
-
const cursor = await loadIMessageCatchupCursor(params.accountId);
|
|
161
|
-
const lookbackMs = cursor === null ? cfg.firstRunLookbackMinutes * 6e4 : cfg.maxAgeMinutes * 6e4;
|
|
162
|
-
const ageBoundMs = now - cfg.maxAgeMinutes * 6e4;
|
|
163
|
-
const windowStartMs = Math.max(cursor?.lastSeenMs ?? now - lookbackMs, ageBoundMs);
|
|
164
|
-
const windowEndMs = now;
|
|
165
|
-
const sinceRowid = cursor?.lastSeenRowid ?? 0;
|
|
166
|
-
const summary = {
|
|
167
|
-
querySucceeded: false,
|
|
168
|
-
fullyCaughtUp: false,
|
|
169
|
-
fetchedCount: 0,
|
|
170
|
-
replayed: 0,
|
|
171
|
-
skippedFromMe: 0,
|
|
172
|
-
skippedPreCursor: 0,
|
|
173
|
-
skippedGivenUp: 0,
|
|
174
|
-
failed: 0,
|
|
175
|
-
givenUp: 0,
|
|
176
|
-
cursorBefore: cursor ? {
|
|
177
|
-
lastSeenMs: cursor.lastSeenMs,
|
|
178
|
-
lastSeenRowid: cursor.lastSeenRowid
|
|
179
|
-
} : null,
|
|
180
|
-
cursorAfter: {
|
|
181
|
-
lastSeenMs: cursor?.lastSeenMs ?? windowStartMs,
|
|
182
|
-
lastSeenRowid: cursor?.lastSeenRowid ?? 0
|
|
183
|
-
},
|
|
184
|
-
windowStartMs,
|
|
185
|
-
windowEndMs
|
|
186
|
-
};
|
|
187
|
-
let fetchResult;
|
|
188
|
-
try {
|
|
189
|
-
fetchResult = await params.fetch({
|
|
190
|
-
sinceMs: windowStartMs,
|
|
191
|
-
sinceRowid,
|
|
192
|
-
limit: cfg.perRunLimit
|
|
193
|
-
});
|
|
194
|
-
} catch (err) {
|
|
195
|
-
params.warn?.(`imessage catchup: fetch failed: ${String(err)}`);
|
|
196
|
-
return summary;
|
|
197
|
-
}
|
|
198
|
-
if (!fetchResult.resolved) {
|
|
199
|
-
params.warn?.(`imessage catchup: fetch returned unresolved result`);
|
|
200
|
-
return summary;
|
|
201
|
-
}
|
|
202
|
-
summary.querySucceeded = true;
|
|
203
|
-
summary.fullyCaughtUp = fetchResult.fullyCaughtUp !== false;
|
|
204
|
-
summary.fetchedCount = fetchResult.rows.length;
|
|
205
|
-
const rows = fetchResult.rows.toSorted((a, b) => a.rowid - b.rowid);
|
|
206
|
-
const failureRetries = { ...cursor?.failureRetries };
|
|
207
|
-
const cursorBeforeMs = cursor?.lastSeenMs ?? windowStartMs;
|
|
208
|
-
const cursorBeforeRowid = cursor?.lastSeenRowid ?? 0;
|
|
209
|
-
let highWatermarkMs = cursorBeforeMs;
|
|
210
|
-
let highWatermarkRowid = cursorBeforeRowid;
|
|
211
|
-
let earliestHeldFailureRow = null;
|
|
212
|
-
for (const row of rows) {
|
|
213
|
-
if (row.rowid <= sinceRowid) {
|
|
214
|
-
summary.skippedPreCursor += 1;
|
|
215
|
-
continue;
|
|
216
|
-
}
|
|
217
|
-
if (row.date < ageBoundMs) {
|
|
218
|
-
summary.skippedPreCursor += 1;
|
|
219
|
-
highWatermarkMs = Math.max(highWatermarkMs, row.date);
|
|
220
|
-
highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
if (row.isFromMe) {
|
|
224
|
-
try {
|
|
225
|
-
await params.observeSkippedFromMe?.(row);
|
|
226
|
-
} catch (err) {
|
|
227
|
-
params.warn?.(`imessage catchup: from-me observer failed for guid=${row.guid}: ${String(err)}`);
|
|
228
|
-
}
|
|
229
|
-
summary.skippedFromMe += 1;
|
|
230
|
-
highWatermarkMs = Math.max(highWatermarkMs, row.date);
|
|
231
|
-
highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
|
|
232
|
-
continue;
|
|
233
|
-
}
|
|
234
|
-
const priorCount = failureRetries[row.guid] ?? 0;
|
|
235
|
-
if (priorCount >= cfg.maxFailureRetries) {
|
|
236
|
-
summary.skippedGivenUp += 1;
|
|
237
|
-
highWatermarkMs = Math.max(highWatermarkMs, row.date);
|
|
238
|
-
highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
|
|
239
|
-
continue;
|
|
240
|
-
}
|
|
241
|
-
let dispatched;
|
|
242
|
-
try {
|
|
243
|
-
dispatched = await params.dispatch(row);
|
|
244
|
-
} catch (err) {
|
|
245
|
-
params.warn?.(`imessage catchup: dispatch threw for guid=${row.guid}: ${String(err)}`);
|
|
246
|
-
dispatched = { ok: false };
|
|
247
|
-
}
|
|
248
|
-
if (dispatched.ok) {
|
|
249
|
-
summary.replayed += 1;
|
|
250
|
-
delete failureRetries[row.guid];
|
|
251
|
-
highWatermarkMs = Math.max(highWatermarkMs, row.date);
|
|
252
|
-
highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
const nextCount = priorCount + 1;
|
|
256
|
-
failureRetries[row.guid] = nextCount;
|
|
257
|
-
summary.failed += 1;
|
|
258
|
-
if (nextCount >= cfg.maxFailureRetries) {
|
|
259
|
-
summary.givenUp += 1;
|
|
260
|
-
params.warn?.(`imessage catchup: giving up on guid=${row.guid} after ${nextCount} failures; advancing cursor past it`);
|
|
261
|
-
highWatermarkMs = Math.max(highWatermarkMs, row.date);
|
|
262
|
-
highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
if (earliestHeldFailureRow === null || row.rowid < earliestHeldFailureRow.rowid) earliestHeldFailureRow = row;
|
|
266
|
-
}
|
|
267
|
-
if (earliestHeldFailureRow === null) {
|
|
268
|
-
if (typeof fetchResult.highWatermarkMs === "number") highWatermarkMs = Math.max(highWatermarkMs, fetchResult.highWatermarkMs);
|
|
269
|
-
if (typeof fetchResult.highWatermarkRowid === "number") highWatermarkRowid = Math.max(highWatermarkRowid, fetchResult.highWatermarkRowid);
|
|
270
|
-
}
|
|
271
|
-
let lastSeenMs;
|
|
272
|
-
let lastSeenRowid;
|
|
273
|
-
if (earliestHeldFailureRow !== null) {
|
|
274
|
-
lastSeenMs = Math.max(cursorBeforeMs, earliestHeldFailureRow.date - 1);
|
|
275
|
-
lastSeenRowid = Math.max(cursorBeforeRowid, earliestHeldFailureRow.rowid - 1);
|
|
276
|
-
} else {
|
|
277
|
-
lastSeenMs = highWatermarkMs;
|
|
278
|
-
lastSeenRowid = highWatermarkRowid;
|
|
279
|
-
}
|
|
280
|
-
const capped = capFailureRetriesMap(failureRetries);
|
|
281
|
-
summary.cursorAfter = {
|
|
282
|
-
lastSeenMs,
|
|
283
|
-
lastSeenRowid
|
|
284
|
-
};
|
|
285
|
-
await saveIMessageCatchupCursor(params.accountId, {
|
|
286
|
-
lastSeenMs,
|
|
287
|
-
lastSeenRowid,
|
|
288
|
-
failureRetries: capped
|
|
289
|
-
}, { allowCursorRewindForRetries: earliestHeldFailureRow !== null });
|
|
290
|
-
if (summary.replayed > 0 || summary.failed > 0 || summary.givenUp > 0) params.log?.(`imessage catchup: replayed=${summary.replayed} skippedFromMe=${summary.skippedFromMe} skippedGivenUp=${summary.skippedGivenUp} failed=${summary.failed} givenUp=${summary.givenUp} fetchedCount=${summary.fetchedCount}`);
|
|
291
|
-
return summary;
|
|
292
|
-
}
|
|
293
|
-
//#endregion
|
|
294
|
-
export { resolveCatchupConfig as a, performIMessageCatchup as i, advanceIMessageCatchupCursor as n, resolveIMessageCatchupCursorKey as o, capFailureRetriesMap as r, IMESSAGE_CATCHUP_CURSOR_NAMESPACE as t };
|