@inline-chat/hermes-agent-adapter 0.0.15 → 0.0.17
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/README.md +13 -3
- package/package.json +3 -3
- package/plugin/inline/adapter.py +155 -8
- package/plugin/inline/cli.py +1 -1
- package/plugin/inline/plugin.yaml +1 -1
- package/plugin/inline/sidecar/index.mjs +787 -432
|
@@ -5568,7 +5568,449 @@ var require_websocket_server = __commonJS((exports, module) => {
|
|
|
5568
5568
|
|
|
5569
5569
|
// src/sidecar/index.ts
|
|
5570
5570
|
import http from "node:http";
|
|
5571
|
-
|
|
5571
|
+
|
|
5572
|
+
// src/sidecar/inbound-stream.ts
|
|
5573
|
+
class InboundStream {
|
|
5574
|
+
consumer = null;
|
|
5575
|
+
stopped = false;
|
|
5576
|
+
changed = new Set;
|
|
5577
|
+
attach(consumer) {
|
|
5578
|
+
if (this.stopped) {
|
|
5579
|
+
consumer.end();
|
|
5580
|
+
return;
|
|
5581
|
+
}
|
|
5582
|
+
const previous = this.consumer;
|
|
5583
|
+
this.consumer = consumer;
|
|
5584
|
+
const retired = () => {
|
|
5585
|
+
if (this.consumer === consumer) {
|
|
5586
|
+
this.consumer = null;
|
|
5587
|
+
this.wake();
|
|
5588
|
+
}
|
|
5589
|
+
};
|
|
5590
|
+
consumer.once("close", retired);
|
|
5591
|
+
consumer.on("error", retired);
|
|
5592
|
+
this.wake();
|
|
5593
|
+
previous?.end();
|
|
5594
|
+
}
|
|
5595
|
+
close() {
|
|
5596
|
+
this.stopped = true;
|
|
5597
|
+
const previous = this.consumer;
|
|
5598
|
+
this.consumer = null;
|
|
5599
|
+
this.wake();
|
|
5600
|
+
previous?.end();
|
|
5601
|
+
}
|
|
5602
|
+
async deliver(event) {
|
|
5603
|
+
const line = JSON.stringify(event) + `
|
|
5604
|
+
`;
|
|
5605
|
+
while (!this.stopped) {
|
|
5606
|
+
const owner = this.consumer;
|
|
5607
|
+
if (!owner) {
|
|
5608
|
+
await new Promise((resolve) => this.changed.add(resolve));
|
|
5609
|
+
continue;
|
|
5610
|
+
}
|
|
5611
|
+
let cleanup = () => {};
|
|
5612
|
+
const drained = new Promise((resolve) => {
|
|
5613
|
+
const finish = (ok) => {
|
|
5614
|
+
cleanup();
|
|
5615
|
+
resolve(ok);
|
|
5616
|
+
};
|
|
5617
|
+
const onDrain = () => finish(true);
|
|
5618
|
+
const onChange = () => finish(false);
|
|
5619
|
+
cleanup = () => {
|
|
5620
|
+
owner.off("drain", onDrain);
|
|
5621
|
+
owner.off("close", onChange);
|
|
5622
|
+
owner.off("error", onChange);
|
|
5623
|
+
this.changed.delete(onChange);
|
|
5624
|
+
};
|
|
5625
|
+
owner.once("drain", onDrain);
|
|
5626
|
+
owner.once("close", onChange);
|
|
5627
|
+
owner.once("error", onChange);
|
|
5628
|
+
this.changed.add(onChange);
|
|
5629
|
+
});
|
|
5630
|
+
try {
|
|
5631
|
+
if (owner.destroyed || owner.writableEnded) {
|
|
5632
|
+
if (this.consumer === owner)
|
|
5633
|
+
this.consumer = null;
|
|
5634
|
+
continue;
|
|
5635
|
+
}
|
|
5636
|
+
const accepted = owner.write(line);
|
|
5637
|
+
if (this.consumer !== owner || this.stopped)
|
|
5638
|
+
continue;
|
|
5639
|
+
if (accepted)
|
|
5640
|
+
return;
|
|
5641
|
+
if (await drained && this.consumer === owner && !this.stopped)
|
|
5642
|
+
return;
|
|
5643
|
+
} catch {
|
|
5644
|
+
if (this.consumer === owner)
|
|
5645
|
+
this.consumer = null;
|
|
5646
|
+
} finally {
|
|
5647
|
+
cleanup();
|
|
5648
|
+
}
|
|
5649
|
+
}
|
|
5650
|
+
throw new Error("Inbound stream closed before delivery completed");
|
|
5651
|
+
}
|
|
5652
|
+
wake() {
|
|
5653
|
+
for (const resolve of this.changed)
|
|
5654
|
+
resolve();
|
|
5655
|
+
this.changed.clear();
|
|
5656
|
+
}
|
|
5657
|
+
}
|
|
5658
|
+
|
|
5659
|
+
// src/sidecar/inbound-delivery.ts
|
|
5660
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
5661
|
+
|
|
5662
|
+
// src/sidecar/contract.ts
|
|
5663
|
+
var MAX_INLINE_ID = 9223372036854775807n;
|
|
5664
|
+
var MAX_UINT64 = 18446744073709551615n;
|
|
5665
|
+
var sensitiveUrlParams = new Set([
|
|
5666
|
+
"access_token",
|
|
5667
|
+
"auth",
|
|
5668
|
+
"authorization",
|
|
5669
|
+
"key",
|
|
5670
|
+
"token"
|
|
5671
|
+
]);
|
|
5672
|
+
var botSettingsEventKinds = new Set([
|
|
5673
|
+
"bot.chatSettings.request",
|
|
5674
|
+
"bot.chatSettings.item.invoke"
|
|
5675
|
+
]);
|
|
5676
|
+
function inboundEventNeedsSenderResolution(event) {
|
|
5677
|
+
return !botSettingsEventKinds.has(event.kind ?? "");
|
|
5678
|
+
}
|
|
5679
|
+
|
|
5680
|
+
class SidecarError extends Error {
|
|
5681
|
+
errorKind;
|
|
5682
|
+
constructor(message, errorKind) {
|
|
5683
|
+
super(message);
|
|
5684
|
+
this.errorKind = errorKind;
|
|
5685
|
+
}
|
|
5686
|
+
}
|
|
5687
|
+
function parseTarget(record) {
|
|
5688
|
+
const targetRecord = asOptionalRecord(record.target) ?? record;
|
|
5689
|
+
const chatId = readOptionalInlineId(targetRecord, "chatId");
|
|
5690
|
+
const userId = readOptionalInlineId(targetRecord, "userId");
|
|
5691
|
+
if (chatId && userId)
|
|
5692
|
+
throw new SidecarError("target cannot include both chatId and userId", "bad_format");
|
|
5693
|
+
if (chatId)
|
|
5694
|
+
return { chatId };
|
|
5695
|
+
if (userId)
|
|
5696
|
+
return { userId };
|
|
5697
|
+
throw new SidecarError("target requires chatId or userId", "bad_format");
|
|
5698
|
+
}
|
|
5699
|
+
function normalizeUploadKind(raw, filePath) {
|
|
5700
|
+
if (raw === "photo" || raw === "image")
|
|
5701
|
+
return "photo";
|
|
5702
|
+
if (raw === "video")
|
|
5703
|
+
return "video";
|
|
5704
|
+
if (raw === "voice")
|
|
5705
|
+
return "voice";
|
|
5706
|
+
if (raw === "document" || raw === "file")
|
|
5707
|
+
return "document";
|
|
5708
|
+
const lower = filePath.toLowerCase();
|
|
5709
|
+
if (/\.(png|jpg|jpeg|gif|webp|heic|heif)$/.test(lower))
|
|
5710
|
+
return "photo";
|
|
5711
|
+
if (/\.(mp4|mov|webm)$/.test(lower))
|
|
5712
|
+
return "video";
|
|
5713
|
+
return "document";
|
|
5714
|
+
}
|
|
5715
|
+
function normalizeError(error, redact = defaultErrorText) {
|
|
5716
|
+
if (error instanceof SidecarError) {
|
|
5717
|
+
return {
|
|
5718
|
+
status: statusForErrorKind(error.errorKind),
|
|
5719
|
+
errorKind: error.errorKind,
|
|
5720
|
+
message: redact(error)
|
|
5721
|
+
};
|
|
5722
|
+
}
|
|
5723
|
+
const message = redact(error);
|
|
5724
|
+
const lower = message.toLowerCase();
|
|
5725
|
+
if (lower.includes("rate") && lower.includes("limit")) {
|
|
5726
|
+
return { status: 429, errorKind: "rate_limited", message };
|
|
5727
|
+
}
|
|
5728
|
+
if (lower.includes("forbidden") || lower.includes("unauthorized")) {
|
|
5729
|
+
return { status: 403, errorKind: "forbidden", message };
|
|
5730
|
+
}
|
|
5731
|
+
if (lower.includes("not found") || lower.includes("missing")) {
|
|
5732
|
+
return { status: 404, errorKind: "not_found", message };
|
|
5733
|
+
}
|
|
5734
|
+
if (lower.includes("timeout") || lower.includes("network") || lower.includes("closed")) {
|
|
5735
|
+
return { status: 503, errorKind: "transient", message };
|
|
5736
|
+
}
|
|
5737
|
+
return { status: 500, errorKind: "unknown", message };
|
|
5738
|
+
}
|
|
5739
|
+
function statusForErrorKind(errorKind) {
|
|
5740
|
+
switch (errorKind) {
|
|
5741
|
+
case "bad_format":
|
|
5742
|
+
return 400;
|
|
5743
|
+
case "forbidden":
|
|
5744
|
+
return 403;
|
|
5745
|
+
case "not_found":
|
|
5746
|
+
return 404;
|
|
5747
|
+
case "too_long":
|
|
5748
|
+
return 413;
|
|
5749
|
+
case "rate_limited":
|
|
5750
|
+
return 429;
|
|
5751
|
+
case "transient":
|
|
5752
|
+
return 503;
|
|
5753
|
+
case "unknown":
|
|
5754
|
+
return 500;
|
|
5755
|
+
}
|
|
5756
|
+
}
|
|
5757
|
+
function normalizeInboundEvent(event, meId, sender, meUsername) {
|
|
5758
|
+
if (event.kind === "message.new" || event.kind === "message.edit") {
|
|
5759
|
+
const message = asOptionalRecord(event.message);
|
|
5760
|
+
return safeJson({
|
|
5761
|
+
kind: event.kind,
|
|
5762
|
+
chatId: event.chatId,
|
|
5763
|
+
seq: event.seq,
|
|
5764
|
+
date: event.date,
|
|
5765
|
+
meId,
|
|
5766
|
+
meUsername,
|
|
5767
|
+
...sender ? { sender } : {},
|
|
5768
|
+
message: message ? normalizeMessage(message) : null
|
|
5769
|
+
});
|
|
5770
|
+
}
|
|
5771
|
+
if (event.kind === "message.action.invoke") {
|
|
5772
|
+
return safeJson({
|
|
5773
|
+
...event,
|
|
5774
|
+
meId,
|
|
5775
|
+
meUsername,
|
|
5776
|
+
...sender ? { sender } : {},
|
|
5777
|
+
dataBase64: event.data instanceof Uint8Array ? Buffer.from(event.data).toString("base64") : typeof event.data === "string" ? event.data : ""
|
|
5778
|
+
});
|
|
5779
|
+
}
|
|
5780
|
+
return safeJson({ ...event, meId, meUsername, ...sender ? { sender } : {} });
|
|
5781
|
+
}
|
|
5782
|
+
function normalizeMessage(message) {
|
|
5783
|
+
return {
|
|
5784
|
+
id: message.id,
|
|
5785
|
+
fromId: message.fromId,
|
|
5786
|
+
chatId: message.chatId,
|
|
5787
|
+
peerId: message.peerId,
|
|
5788
|
+
message: message.message ?? null,
|
|
5789
|
+
out: Boolean(message.out),
|
|
5790
|
+
date: message.date,
|
|
5791
|
+
mentioned: Boolean(message.mentioned),
|
|
5792
|
+
replyToMsgId: message.replyToMsgId ?? null,
|
|
5793
|
+
entities: message.entities ?? null,
|
|
5794
|
+
media: message.media ?? null,
|
|
5795
|
+
attachments: message.attachments ?? null,
|
|
5796
|
+
reactions: message.reactions ?? null,
|
|
5797
|
+
replies: message.replies ?? null,
|
|
5798
|
+
actions: message.actions ?? null,
|
|
5799
|
+
rev: message.rev ?? null,
|
|
5800
|
+
raw: message
|
|
5801
|
+
};
|
|
5802
|
+
}
|
|
5803
|
+
function redactText(value, secrets) {
|
|
5804
|
+
let text = value instanceof Error ? value.message : String(value);
|
|
5805
|
+
for (const secret of secrets) {
|
|
5806
|
+
const raw = typeof secret.value === "string" ? secret.value : "";
|
|
5807
|
+
if (!raw)
|
|
5808
|
+
continue;
|
|
5809
|
+
text = text.split(raw).join(secret.label);
|
|
5810
|
+
}
|
|
5811
|
+
return text;
|
|
5812
|
+
}
|
|
5813
|
+
function redactUrl(value) {
|
|
5814
|
+
let url;
|
|
5815
|
+
try {
|
|
5816
|
+
url = new URL(value);
|
|
5817
|
+
} catch {
|
|
5818
|
+
return value;
|
|
5819
|
+
}
|
|
5820
|
+
if (url.username)
|
|
5821
|
+
url.username = "redacted";
|
|
5822
|
+
if (url.password)
|
|
5823
|
+
url.password = "redacted";
|
|
5824
|
+
const keys = Array.from(url.searchParams.keys());
|
|
5825
|
+
for (const key of keys) {
|
|
5826
|
+
const normalized = key.toLowerCase();
|
|
5827
|
+
if (sensitiveUrlParams.has(normalized) || normalized.includes("token")) {
|
|
5828
|
+
url.searchParams.set(key, "redacted");
|
|
5829
|
+
}
|
|
5830
|
+
}
|
|
5831
|
+
return url.toString();
|
|
5832
|
+
}
|
|
5833
|
+
function safeJson(value) {
|
|
5834
|
+
if (value == null)
|
|
5835
|
+
return null;
|
|
5836
|
+
if (typeof value === "string" || typeof value === "boolean")
|
|
5837
|
+
return value;
|
|
5838
|
+
if (typeof value === "number")
|
|
5839
|
+
return Number.isFinite(value) ? value : null;
|
|
5840
|
+
if (typeof value === "bigint")
|
|
5841
|
+
return value.toString();
|
|
5842
|
+
if (value instanceof Uint8Array)
|
|
5843
|
+
return Buffer.from(value).toString("base64");
|
|
5844
|
+
if (Array.isArray(value))
|
|
5845
|
+
return value.map(safeJson);
|
|
5846
|
+
if (typeof value === "object") {
|
|
5847
|
+
const out = {};
|
|
5848
|
+
for (const [key, item] of Object.entries(value)) {
|
|
5849
|
+
if (item !== undefined)
|
|
5850
|
+
out[key] = safeJson(item);
|
|
5851
|
+
}
|
|
5852
|
+
return out;
|
|
5853
|
+
}
|
|
5854
|
+
return String(value);
|
|
5855
|
+
}
|
|
5856
|
+
function asRecord(value) {
|
|
5857
|
+
const record = asOptionalRecord(value);
|
|
5858
|
+
if (!record)
|
|
5859
|
+
throw new SidecarError("expected JSON object", "bad_format");
|
|
5860
|
+
return record;
|
|
5861
|
+
}
|
|
5862
|
+
function asOptionalRecord(value) {
|
|
5863
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
5864
|
+
return null;
|
|
5865
|
+
return value;
|
|
5866
|
+
}
|
|
5867
|
+
function readRequiredString(record, key) {
|
|
5868
|
+
const value = readOptionalString(record, key);
|
|
5869
|
+
if (!value)
|
|
5870
|
+
throw new SidecarError(`missing ${key}`, "bad_format");
|
|
5871
|
+
return value;
|
|
5872
|
+
}
|
|
5873
|
+
function readOptionalString(record, key) {
|
|
5874
|
+
const value = record[key];
|
|
5875
|
+
if (typeof value === "string")
|
|
5876
|
+
return value.trim() || undefined;
|
|
5877
|
+
if (typeof value === "bigint" || typeof value === "number")
|
|
5878
|
+
return String(value);
|
|
5879
|
+
return;
|
|
5880
|
+
}
|
|
5881
|
+
function readOptionalBoolean(record, key) {
|
|
5882
|
+
const value = record[key];
|
|
5883
|
+
if (typeof value === "boolean")
|
|
5884
|
+
return value;
|
|
5885
|
+
if (typeof value === "string") {
|
|
5886
|
+
if (/^(1|true|yes|on)$/i.test(value))
|
|
5887
|
+
return true;
|
|
5888
|
+
if (/^(0|false|no|off)$/i.test(value))
|
|
5889
|
+
return false;
|
|
5890
|
+
}
|
|
5891
|
+
return;
|
|
5892
|
+
}
|
|
5893
|
+
function readOptionalNumber(record, key) {
|
|
5894
|
+
const value = record[key];
|
|
5895
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
5896
|
+
return value;
|
|
5897
|
+
if (typeof value === "string" && value.trim()) {
|
|
5898
|
+
const parsed = Number(value);
|
|
5899
|
+
if (Number.isFinite(parsed))
|
|
5900
|
+
return parsed;
|
|
5901
|
+
}
|
|
5902
|
+
return;
|
|
5903
|
+
}
|
|
5904
|
+
function readRequiredInlineId(record, key) {
|
|
5905
|
+
const value = readOptionalInlineId(record, key);
|
|
5906
|
+
if (value == null)
|
|
5907
|
+
throw new SidecarError(`missing ${key}`, "bad_format");
|
|
5908
|
+
return value;
|
|
5909
|
+
}
|
|
5910
|
+
function readOptionalInlineId(record, key) {
|
|
5911
|
+
const value = record[key];
|
|
5912
|
+
if (value == null || value === "")
|
|
5913
|
+
return;
|
|
5914
|
+
return parseInlineId(value, key);
|
|
5915
|
+
}
|
|
5916
|
+
function readInlineIdArray(record, key, maxItems, required = false) {
|
|
5917
|
+
const value = record[key];
|
|
5918
|
+
if (value == null) {
|
|
5919
|
+
if (required)
|
|
5920
|
+
throw new SidecarError(`missing ${key}`, "bad_format");
|
|
5921
|
+
return [];
|
|
5922
|
+
}
|
|
5923
|
+
if (!Array.isArray(value))
|
|
5924
|
+
throw new SidecarError(`${key} must be an array`, "bad_format");
|
|
5925
|
+
if (value.length === 0 && required)
|
|
5926
|
+
throw new SidecarError(`${key} must not be empty`, "bad_format");
|
|
5927
|
+
if (value.length > maxItems)
|
|
5928
|
+
throw new SidecarError(`${key} supports at most ${maxItems} items`, "bad_format");
|
|
5929
|
+
const ids = [];
|
|
5930
|
+
const seen = new Set;
|
|
5931
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
5932
|
+
const id = parseInlineId(value[index], `${key}[${index}]`);
|
|
5933
|
+
const normalized = id.toString();
|
|
5934
|
+
if (seen.has(normalized))
|
|
5935
|
+
continue;
|
|
5936
|
+
seen.add(normalized);
|
|
5937
|
+
ids.push(id);
|
|
5938
|
+
}
|
|
5939
|
+
return ids;
|
|
5940
|
+
}
|
|
5941
|
+
function parseOptionalInt(value) {
|
|
5942
|
+
const raw = (value || "").trim();
|
|
5943
|
+
if (!raw || !/^\d+$/.test(raw))
|
|
5944
|
+
return;
|
|
5945
|
+
const parsed = Number(raw);
|
|
5946
|
+
return Number.isSafeInteger(parsed) ? parsed : undefined;
|
|
5947
|
+
}
|
|
5948
|
+
function parseInlineId(value, field) {
|
|
5949
|
+
try {
|
|
5950
|
+
if (typeof value === "number" && (!Number.isSafeInteger(value) || value <= 0)) {
|
|
5951
|
+
throw new Error("unsafe number");
|
|
5952
|
+
}
|
|
5953
|
+
const raw = typeof value === "string" ? value.trim() : value;
|
|
5954
|
+
if (typeof raw === "string" && !/^[1-9][0-9]*$/.test(raw))
|
|
5955
|
+
throw new Error("invalid digits");
|
|
5956
|
+
if (typeof raw !== "string" && typeof raw !== "bigint" && typeof raw !== "number") {
|
|
5957
|
+
throw new Error("invalid type");
|
|
5958
|
+
}
|
|
5959
|
+
const parsed = BigInt(raw);
|
|
5960
|
+
if (parsed <= 0n || parsed > MAX_INLINE_ID)
|
|
5961
|
+
throw new Error("out of range");
|
|
5962
|
+
return parsed;
|
|
5963
|
+
} catch {
|
|
5964
|
+
throw new SidecarError(`${field} must be a positive signed 64-bit integer`, "bad_format");
|
|
5965
|
+
}
|
|
5966
|
+
}
|
|
5967
|
+
function parseUnsigned64Id(value, field) {
|
|
5968
|
+
try {
|
|
5969
|
+
if (typeof value === "number" && (!Number.isSafeInteger(value) || value <= 0)) {
|
|
5970
|
+
throw new Error("unsafe number");
|
|
5971
|
+
}
|
|
5972
|
+
const raw = typeof value === "string" ? value.trim() : value;
|
|
5973
|
+
if (typeof raw === "string" && !/^[1-9][0-9]*$/.test(raw))
|
|
5974
|
+
throw new Error("invalid digits");
|
|
5975
|
+
if (typeof raw !== "string" && typeof raw !== "bigint" && typeof raw !== "number") {
|
|
5976
|
+
throw new Error("invalid type");
|
|
5977
|
+
}
|
|
5978
|
+
const parsed = BigInt(raw);
|
|
5979
|
+
if (parsed <= 0n || parsed > MAX_UINT64)
|
|
5980
|
+
throw new Error("out of range");
|
|
5981
|
+
return parsed;
|
|
5982
|
+
} catch {
|
|
5983
|
+
throw new SidecarError(`${field} must be a positive unsigned 64-bit integer`, "bad_format");
|
|
5984
|
+
}
|
|
5985
|
+
}
|
|
5986
|
+
function defaultErrorText(error) {
|
|
5987
|
+
return error instanceof Error ? error.message : String(error);
|
|
5988
|
+
}
|
|
5989
|
+
|
|
5990
|
+
// src/sidecar/inbound-delivery.ts
|
|
5991
|
+
function explicitlyMentionsSelf(event, meId) {
|
|
5992
|
+
if (!meId || event.kind !== "message.new" && event.kind !== "message.edit")
|
|
5993
|
+
return false;
|
|
5994
|
+
const message = event.message;
|
|
5995
|
+
return message?.entities?.entities?.some((e) => e.entity?.oneofKind === "mention" && e.entity.mention?.userId?.toString() === meId) ?? false;
|
|
5996
|
+
}
|
|
5997
|
+
async function deliverInboundEvent(event, owner) {
|
|
5998
|
+
const explicitMention = explicitlyMentionsSelf(event, owner.meId);
|
|
5999
|
+
while (!owner.signal.aborted) {
|
|
6000
|
+
const resolution = explicitMention ? { provenanceVerified: false } : inboundEventNeedsSenderResolution(event) ? await owner.resolveSender(event) : { provenanceVerified: true };
|
|
6001
|
+
owner.signal.throwIfAborted();
|
|
6002
|
+
if (!explicitMention && !resolution.provenanceVerified && (event.kind === "message.new" || event.kind === "message.edit")) {
|
|
6003
|
+
await delay(1000, undefined, { signal: owner.signal });
|
|
6004
|
+
continue;
|
|
6005
|
+
}
|
|
6006
|
+
const normalized = normalizeInboundEvent(event, owner.meId, resolution.profile, owner.meUsername);
|
|
6007
|
+
await owner.deliver(resolution.provenanceVerified ? normalized : { ...normalized, _inlineSenderProvenanceVerified: false });
|
|
6008
|
+
return;
|
|
6009
|
+
}
|
|
6010
|
+
owner.signal.throwIfAborted();
|
|
6011
|
+
}
|
|
6012
|
+
|
|
6013
|
+
// src/sidecar/index.ts
|
|
5572
6014
|
import { timingSafeEqual } from "node:crypto";
|
|
5573
6015
|
import { mkdir, readFile as readFile3, stat } from "node:fs/promises";
|
|
5574
6016
|
import path from "node:path";
|
|
@@ -38683,7 +39125,7 @@ var reportProgress = (job) => {
|
|
|
38683
39125
|
job.input.onProgress?.({ acceptedBytes: acceptedBytes(job), totalBytes: job.input.source.byteCount });
|
|
38684
39126
|
} catch {}
|
|
38685
39127
|
};
|
|
38686
|
-
var
|
|
39128
|
+
var delay2 = async (seconds, signal) => {
|
|
38687
39129
|
if (signal?.aborted)
|
|
38688
39130
|
throw new NativeUploadError("canceled", "Upload was canceled");
|
|
38689
39131
|
await new Promise((resolve, reject) => {
|
|
@@ -38966,7 +39408,7 @@ class NativeUploadClient {
|
|
|
38966
39408
|
}
|
|
38967
39409
|
}
|
|
38968
39410
|
#resumeFinishAfter(job, seconds) {
|
|
38969
|
-
|
|
39411
|
+
delay2(seconds, job.input.signal).then(() => {
|
|
38970
39412
|
if (job.settled)
|
|
38971
39413
|
return;
|
|
38972
39414
|
job.active = 0;
|
|
@@ -39074,6 +39516,13 @@ var asInlineId = (value, fieldName = "id") => {
|
|
|
39074
39516
|
};
|
|
39075
39517
|
|
|
39076
39518
|
// node_modules/@inline-chat/realtime-sdk/dist/utils/async-channel.js
|
|
39519
|
+
class ChannelConsumerError extends Error {
|
|
39520
|
+
constructor(message) {
|
|
39521
|
+
super(message);
|
|
39522
|
+
this.name = "ChannelConsumerError";
|
|
39523
|
+
}
|
|
39524
|
+
}
|
|
39525
|
+
|
|
39077
39526
|
class AsyncChannelOverflowError extends Error {
|
|
39078
39527
|
capacity;
|
|
39079
39528
|
constructor(capacity) {
|
|
@@ -39183,6 +39632,7 @@ class AcknowledgedAsyncChannel {
|
|
|
39183
39632
|
closed = false;
|
|
39184
39633
|
failure = null;
|
|
39185
39634
|
iteratorClaimed = false;
|
|
39635
|
+
consumption = null;
|
|
39186
39636
|
constructor(capacity, byteLimit) {
|
|
39187
39637
|
this.capacity = capacity;
|
|
39188
39638
|
this.byteLimit = byteLimit;
|
|
@@ -39196,7 +39646,7 @@ class AcknowledgedAsyncChannel {
|
|
|
39196
39646
|
send(value) {
|
|
39197
39647
|
if (this.closed)
|
|
39198
39648
|
return Promise.resolve(false);
|
|
39199
|
-
if (!this.waiter && this.queue.length >= this.capacity) {
|
|
39649
|
+
if (!this.waiter && this.queue.length + (this.consumption?.active.size ?? 0) >= this.capacity) {
|
|
39200
39650
|
throw new AsyncChannelOverflowError(this.capacity);
|
|
39201
39651
|
}
|
|
39202
39652
|
const bytes = this.byteLimit?.byteLength(value) ?? 0;
|
|
@@ -39216,6 +39666,7 @@ class AcknowledgedAsyncChannel {
|
|
|
39216
39666
|
waiter.resolve({ value, done: false });
|
|
39217
39667
|
} else {
|
|
39218
39668
|
this.queue.push(item);
|
|
39669
|
+
this.pumpConsumption();
|
|
39219
39670
|
}
|
|
39220
39671
|
});
|
|
39221
39672
|
}
|
|
@@ -39230,6 +39681,16 @@ class AcknowledgedAsyncChannel {
|
|
|
39230
39681
|
return;
|
|
39231
39682
|
this.closed = true;
|
|
39232
39683
|
this.failure = error;
|
|
39684
|
+
const consumption = this.consumption;
|
|
39685
|
+
this.consumption = null;
|
|
39686
|
+
if (consumption) {
|
|
39687
|
+
for (const item of consumption.active.keys())
|
|
39688
|
+
item.acknowledge(false);
|
|
39689
|
+
if (error)
|
|
39690
|
+
consumption.reject(error);
|
|
39691
|
+
else
|
|
39692
|
+
consumption.resolve();
|
|
39693
|
+
}
|
|
39233
39694
|
this.active?.acknowledge(false);
|
|
39234
39695
|
this.active = null;
|
|
39235
39696
|
for (const item of this.queue)
|
|
@@ -39245,6 +39706,65 @@ class AcknowledgedAsyncChannel {
|
|
|
39245
39706
|
else
|
|
39246
39707
|
waiter.resolve({ value: undefined, done: true });
|
|
39247
39708
|
}
|
|
39709
|
+
consume(handler, key, concurrency = 8) {
|
|
39710
|
+
if (this.iteratorClaimed)
|
|
39711
|
+
return Promise.reject(new ChannelConsumerError("AcknowledgedAsyncChannel supports one consumer"));
|
|
39712
|
+
if (!Number.isSafeInteger(concurrency) || concurrency < 1) {
|
|
39713
|
+
return Promise.reject(new ChannelConsumerError("Consumer concurrency must be a positive safe integer"));
|
|
39714
|
+
}
|
|
39715
|
+
if (this.closed)
|
|
39716
|
+
return this.failure ? Promise.reject(this.failure) : Promise.resolve();
|
|
39717
|
+
this.iteratorClaimed = true;
|
|
39718
|
+
return new Promise((resolve, reject) => {
|
|
39719
|
+
this.consumption = { handler, key, concurrency, active: new Map, resolve, reject };
|
|
39720
|
+
this.pumpConsumption();
|
|
39721
|
+
});
|
|
39722
|
+
}
|
|
39723
|
+
pumpConsumption() {
|
|
39724
|
+
const owner = this.consumption;
|
|
39725
|
+
if (!owner || this.closed)
|
|
39726
|
+
return;
|
|
39727
|
+
try {
|
|
39728
|
+
while (owner.active.size < owner.concurrency && this.queue.length > 0) {
|
|
39729
|
+
if ([...owner.active.values()].includes(null))
|
|
39730
|
+
return;
|
|
39731
|
+
const keys = new Set(owner.active.values());
|
|
39732
|
+
let index = -1;
|
|
39733
|
+
let key = null;
|
|
39734
|
+
for (let i = 0;i < this.queue.length; i++) {
|
|
39735
|
+
const candidate = this.queue[i];
|
|
39736
|
+
const candidateKey = owner.key(candidate.value);
|
|
39737
|
+
if (candidateKey === null) {
|
|
39738
|
+
if (i === 0 && owner.active.size === 0)
|
|
39739
|
+
index = i;
|
|
39740
|
+
break;
|
|
39741
|
+
}
|
|
39742
|
+
if (!keys.has(candidateKey)) {
|
|
39743
|
+
index = i;
|
|
39744
|
+
key = candidateKey;
|
|
39745
|
+
break;
|
|
39746
|
+
}
|
|
39747
|
+
}
|
|
39748
|
+
if (index < 0)
|
|
39749
|
+
return;
|
|
39750
|
+
const item = this.queue.splice(index, 1)[0];
|
|
39751
|
+
owner.active.set(item, key);
|
|
39752
|
+
Promise.resolve().then(() => owner.handler(item.value)).then(() => {
|
|
39753
|
+
if (this.consumption !== owner)
|
|
39754
|
+
return;
|
|
39755
|
+
owner.active.delete(item);
|
|
39756
|
+
this.bufferedBytes -= item.bytes;
|
|
39757
|
+
item.acknowledge(true);
|
|
39758
|
+
this.pumpConsumption();
|
|
39759
|
+
}, (error) => {
|
|
39760
|
+
if (this.consumption === owner)
|
|
39761
|
+
this.fail(error instanceof Error ? error : new Error("Inbound handler failed"));
|
|
39762
|
+
});
|
|
39763
|
+
}
|
|
39764
|
+
} catch (error) {
|
|
39765
|
+
this.fail(error instanceof Error ? error : new Error("Inbound routing failed"));
|
|
39766
|
+
}
|
|
39767
|
+
}
|
|
39248
39768
|
[Symbol.asyncIterator]() {
|
|
39249
39769
|
if (this.iteratorClaimed) {
|
|
39250
39770
|
return {
|
|
@@ -39252,8 +39772,11 @@ class AcknowledgedAsyncChannel {
|
|
|
39252
39772
|
};
|
|
39253
39773
|
}
|
|
39254
39774
|
this.iteratorClaimed = true;
|
|
39775
|
+
let returned = false;
|
|
39255
39776
|
return {
|
|
39256
39777
|
next: () => {
|
|
39778
|
+
if (returned)
|
|
39779
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
39257
39780
|
if (this.active) {
|
|
39258
39781
|
this.bufferedBytes -= this.active.bytes;
|
|
39259
39782
|
this.active.acknowledge(true);
|
|
@@ -39277,6 +39800,11 @@ class AcknowledgedAsyncChannel {
|
|
|
39277
39800
|
});
|
|
39278
39801
|
},
|
|
39279
39802
|
return: () => {
|
|
39803
|
+
if (returned)
|
|
39804
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
39805
|
+
returned = true;
|
|
39806
|
+
this.waiter?.resolve({ value: undefined, done: true });
|
|
39807
|
+
this.waiter = null;
|
|
39280
39808
|
if (this.active) {
|
|
39281
39809
|
this.bufferedBytes -= this.active.bytes;
|
|
39282
39810
|
this.active.acknowledge(false);
|
|
@@ -39955,10 +40483,12 @@ var normalizeRpcTimeoutMs = (timeoutMs, fallback) => {
|
|
|
39955
40483
|
|
|
39956
40484
|
class ProtocolClientError extends Error {
|
|
39957
40485
|
code;
|
|
40486
|
+
rpcCode;
|
|
39958
40487
|
constructor(code, details) {
|
|
39959
40488
|
super(details?.message ?? code);
|
|
39960
40489
|
this.code = code;
|
|
39961
40490
|
this.name = `ProtocolClientError:${code}`;
|
|
40491
|
+
this.rpcCode = details?.code;
|
|
39962
40492
|
}
|
|
39963
40493
|
}
|
|
39964
40494
|
var commitOutcomeUnknownError = (message) => new ProtocolClientError("commit-outcome-unknown", { message });
|
|
@@ -43583,15 +44113,15 @@ class InlineProtocolV3Connection {
|
|
|
43583
44113
|
if (!authorization?.temporary || authorization.expiresAt === undefined || this.#closing)
|
|
43584
44114
|
return;
|
|
43585
44115
|
const boundary = authorization.expiresAt * 1000 - TEMPORARY_KEY_ROTATION_MILLISECONDS;
|
|
43586
|
-
const
|
|
43587
|
-
if (!Number.isFinite(
|
|
44116
|
+
const delay3 = boundary - this.#clock.nowMilliseconds();
|
|
44117
|
+
if (!Number.isFinite(delay3) || delay3 <= 0) {
|
|
43588
44118
|
this.#markRotationDue();
|
|
43589
44119
|
return;
|
|
43590
44120
|
}
|
|
43591
44121
|
const timer = setTimeout(() => {
|
|
43592
44122
|
this.#rotationTimer = undefined;
|
|
43593
44123
|
this.#scheduleRotationTimer();
|
|
43594
|
-
}, Math.min(
|
|
44124
|
+
}, Math.min(delay3, 2147000000));
|
|
43595
44125
|
if (typeof timer !== "number" && typeof timer.unref === "function")
|
|
43596
44126
|
timer.unref();
|
|
43597
44127
|
this.#rotationTimer = timer;
|
|
@@ -44781,8 +45311,11 @@ class InlineSdkClient {
|
|
|
44781
45311
|
peerResolutionRequestedByChatId = new Map;
|
|
44782
45312
|
recoveryReconnectInFlight = null;
|
|
44783
45313
|
degradedUpdateBuckets = new Map;
|
|
45314
|
+
discoveryRetry = null;
|
|
45315
|
+
recoveryRetries = new Map;
|
|
44784
45316
|
liveCursorFences = new Set;
|
|
44785
45317
|
liveAdmittedSeqByBucket = new Map;
|
|
45318
|
+
liveDeliveriesByBucket = new Map;
|
|
44786
45319
|
discoveryRound = null;
|
|
44787
45320
|
discoveryInFlight = null;
|
|
44788
45321
|
discoveryCommitInFlight = null;
|
|
@@ -44875,12 +45408,14 @@ class InlineSdkClient {
|
|
|
44875
45408
|
}
|
|
44876
45409
|
}
|
|
44877
45410
|
async close() {
|
|
44878
|
-
|
|
44879
|
-
return;
|
|
45411
|
+
const wasStarted = this.started;
|
|
44880
45412
|
this.closed = true;
|
|
44881
45413
|
this.started = false;
|
|
45414
|
+
this.cancelRecoveryRetries();
|
|
44882
45415
|
this.rejectOpen(new Error("closed"));
|
|
44883
45416
|
this.eventStream.close();
|
|
45417
|
+
if (!wasStarted)
|
|
45418
|
+
return;
|
|
44884
45419
|
await settleWithin(this.protocol.stopTransport(), closeJoinTimeoutMs, () => this.log.warn?.("Timed out stopping SDK transport during close"));
|
|
44885
45420
|
await settleWithin(Promise.allSettled([
|
|
44886
45421
|
...this.catchUpInFlightByChatId.values(),
|
|
@@ -44942,6 +45477,20 @@ class InlineSdkClient {
|
|
|
44942
45477
|
events() {
|
|
44943
45478
|
return this.eventStream;
|
|
44944
45479
|
}
|
|
45480
|
+
consumeEvents(handler, options) {
|
|
45481
|
+
return this.eventStream.consume(handler, (event) => {
|
|
45482
|
+
const bucket = this.bucketForEvent(event);
|
|
45483
|
+
if (bucket?.kind === "user")
|
|
45484
|
+
return null;
|
|
45485
|
+
if (bucket)
|
|
45486
|
+
return this.updateBucketKey(bucket);
|
|
45487
|
+
return "chatId" in event && typeof event.chatId === "bigint" ? `chat:${event.chatId}` : null;
|
|
45488
|
+
}, options?.concurrency).catch(async (error) => {
|
|
45489
|
+
if (!(error instanceof ChannelConsumerError))
|
|
45490
|
+
await this.close();
|
|
45491
|
+
throw error;
|
|
45492
|
+
});
|
|
45493
|
+
}
|
|
44945
45494
|
exportState() {
|
|
44946
45495
|
return {
|
|
44947
45496
|
version: 1,
|
|
@@ -45351,6 +45900,7 @@ class InlineSdkClient {
|
|
|
45351
45900
|
this.log.error?.("SDK listener crashed", failure);
|
|
45352
45901
|
this.started = false;
|
|
45353
45902
|
this.closed = true;
|
|
45903
|
+
this.cancelRecoveryRetries();
|
|
45354
45904
|
this.rejectOpen(failure);
|
|
45355
45905
|
this.eventStream.fail(failure);
|
|
45356
45906
|
this.protocol.stopTransport().catch((stopError) => {
|
|
@@ -45363,6 +45913,7 @@ class InlineSdkClient {
|
|
|
45363
45913
|
return;
|
|
45364
45914
|
this.authenticationError = error;
|
|
45365
45915
|
this.started = false;
|
|
45916
|
+
this.cancelRecoveryRetries();
|
|
45366
45917
|
this.rejectOpen(error);
|
|
45367
45918
|
this.eventStream.close();
|
|
45368
45919
|
try {
|
|
@@ -45372,6 +45923,7 @@ class InlineSdkClient {
|
|
|
45372
45923
|
}
|
|
45373
45924
|
}
|
|
45374
45925
|
async onOpen() {
|
|
45926
|
+
this.cancelRecoveryRetries();
|
|
45375
45927
|
this.requestCatchUpUser();
|
|
45376
45928
|
for (const bucket of this.degradedUpdateBuckets.values()) {
|
|
45377
45929
|
this.requestCatchUpForDegradedBucket(bucket);
|
|
@@ -45422,6 +45974,8 @@ class InlineSdkClient {
|
|
|
45422
45974
|
return this.discoveryInFlight;
|
|
45423
45975
|
if (this.discoveryCommitInFlight)
|
|
45424
45976
|
return this.discoveryCommitInFlight;
|
|
45977
|
+
if (this.discoveryRetry?.timer)
|
|
45978
|
+
return;
|
|
45425
45979
|
const round = {
|
|
45426
45980
|
resultReceived: false,
|
|
45427
45981
|
collectingHints: true,
|
|
@@ -45454,6 +46008,11 @@ class InlineSdkClient {
|
|
|
45454
46008
|
this.log.warn?.("GET_UPDATES_STATE failed (continuing without date cursor)", error);
|
|
45455
46009
|
if (this.discoveryRound === round)
|
|
45456
46010
|
this.discoveryRound = null;
|
|
46011
|
+
if (error instanceof ProtocolClientError && (error.code === "timeout" || error.code === "not-connected" || error.code === "capacity-exceeded" || error.rpcCode === RpcError_Code.INTERNAL_ERROR || error.rpcCode === 500 || error.rpcCode === RpcError_Code.RATE_LIMIT || error.rpcCode === 429)) {
|
|
46012
|
+
this.scheduleDiscoveryRetry(error.rpcCode === RpcError_Code.RATE_LIMIT || error.rpcCode === 429 ? "rate_limited" : "discovery_failed");
|
|
46013
|
+
} else {
|
|
46014
|
+
this.log.error?.("Sync discovery unavailable", { reason: "request_rejected" });
|
|
46015
|
+
}
|
|
45457
46016
|
}
|
|
45458
46017
|
}
|
|
45459
46018
|
tryCommitDiscoveryRound(round) {
|
|
@@ -45478,12 +46037,15 @@ class InlineSdkClient {
|
|
|
45478
46037
|
return;
|
|
45479
46038
|
if (saved) {
|
|
45480
46039
|
this.discoveryRound = null;
|
|
46040
|
+
clearTimeout(this.discoveryRetry?.timer);
|
|
46041
|
+
this.discoveryRetry = null;
|
|
45481
46042
|
return;
|
|
45482
46043
|
}
|
|
45483
46044
|
this.state.dateCursor = previousDateCursor;
|
|
45484
46045
|
this.scheduleStateSave();
|
|
45485
46046
|
this.discoveryRound = null;
|
|
45486
46047
|
this.log.warn?.("Failed to persist discovery checkpoint; preserving previous date cursor");
|
|
46048
|
+
this.scheduleDiscoveryRetry("checkpoint_write_failed");
|
|
45487
46049
|
}).finally(() => {
|
|
45488
46050
|
if (this.discoveryCommitInFlight === commit)
|
|
45489
46051
|
this.discoveryCommitInFlight = null;
|
|
@@ -45818,20 +46380,23 @@ class InlineSdkClient {
|
|
|
45818
46380
|
try {
|
|
45819
46381
|
acknowledged = this.eventStream.send(event);
|
|
45820
46382
|
} catch (error) {
|
|
45821
|
-
const
|
|
45822
|
-
if (
|
|
45823
|
-
this.fenceLiveCursor(
|
|
45824
|
-
this.markUpdateBucketDegraded(
|
|
45825
|
-
this.requestCatchUpAfterEventOverflow(
|
|
46383
|
+
const bucket2 = this.bucketForEvent(event);
|
|
46384
|
+
if (bucket2) {
|
|
46385
|
+
this.fenceLiveCursor(bucket2);
|
|
46386
|
+
this.markUpdateBucketDegraded(bucket2);
|
|
46387
|
+
this.requestCatchUpAfterEventOverflow(bucket2, event);
|
|
45826
46388
|
}
|
|
45827
46389
|
this.log.warn?.("Inbound event buffer overflow; durable cursor remains unchanged and transport will recover", {
|
|
45828
|
-
bucket:
|
|
46390
|
+
bucket: bucket2 ? this.updateBucketKey(bucket2) : "none",
|
|
45829
46391
|
error: extractErrorMessage(error)
|
|
45830
46392
|
});
|
|
45831
46393
|
this.requestRecoveryReconnect("inbound-event-buffer-overflow");
|
|
45832
46394
|
return Promise.resolve(false);
|
|
45833
46395
|
}
|
|
45834
|
-
|
|
46396
|
+
const bucket = source === "live" ? this.sequencedBucketForEvent(event) : undefined;
|
|
46397
|
+
const key = bucket ? this.updateBucketKey(bucket) : undefined;
|
|
46398
|
+
const deliveries = key ? this.liveDeliveriesByBucket.get(key) ?? new Set : undefined;
|
|
46399
|
+
const delivery = acknowledged.then((applied) => {
|
|
45835
46400
|
if (applied) {
|
|
45836
46401
|
onApplied?.();
|
|
45837
46402
|
if (source === "live")
|
|
@@ -45839,8 +46404,16 @@ class InlineSdkClient {
|
|
|
45839
46404
|
} else if (source === "live" && this.started) {
|
|
45840
46405
|
this.recoverLiveEvent(event, "consumer-did-not-apply");
|
|
45841
46406
|
}
|
|
46407
|
+
deliveries?.delete(delivery);
|
|
46408
|
+
if (key && deliveries?.size === 0)
|
|
46409
|
+
this.liveDeliveriesByBucket.delete(key);
|
|
45842
46410
|
return applied;
|
|
45843
46411
|
});
|
|
46412
|
+
if (key && deliveries) {
|
|
46413
|
+
deliveries.add(delivery);
|
|
46414
|
+
this.liveDeliveriesByBucket.set(key, deliveries);
|
|
46415
|
+
}
|
|
46416
|
+
return delivery;
|
|
45844
46417
|
}
|
|
45845
46418
|
bucketForEvent(event) {
|
|
45846
46419
|
switch (event.kind) {
|
|
@@ -46018,20 +46591,25 @@ class InlineSdkClient {
|
|
|
46018
46591
|
}
|
|
46019
46592
|
requestCatchUpForDegradedBucket(bucket) {
|
|
46020
46593
|
switch (bucket.kind) {
|
|
46021
|
-
case "chat":
|
|
46022
|
-
|
|
46023
|
-
|
|
46594
|
+
case "chat": {
|
|
46595
|
+
const request = this.catchUpRequestedByChatId.get(bucket.chatId) ?? this.peerResolutionRequestedByChatId.get(bucket.chatId);
|
|
46596
|
+
const updateSeq = request && !request.toLatest ? request.endSeq : undefined;
|
|
46597
|
+
const peer = bucket.peer ?? this.persistedChatPeer(bucket.chatId);
|
|
46598
|
+
if (peer && this.isReliableChatPeer(peer)) {
|
|
46599
|
+
this.requestCatchUpChat({ chatId: bucket.chatId, peer, updateSeq });
|
|
46024
46600
|
} else {
|
|
46025
|
-
|
|
46026
|
-
if (persistedPeer)
|
|
46027
|
-
this.requestCatchUpChat({ chatId: bucket.chatId, peer: persistedPeer });
|
|
46028
|
-
else
|
|
46029
|
-
this.resolvePersistedChatPeer(bucket.chatId);
|
|
46601
|
+
this.resolvePersistedChatPeer(bucket.chatId, updateSeq);
|
|
46030
46602
|
}
|
|
46031
46603
|
return;
|
|
46032
|
-
|
|
46033
|
-
|
|
46604
|
+
}
|
|
46605
|
+
case "space": {
|
|
46606
|
+
const request = this.catchUpRequestedBySpaceId.get(bucket.spaceId);
|
|
46607
|
+
this.requestCatchUpSpace({
|
|
46608
|
+
spaceId: bucket.spaceId,
|
|
46609
|
+
updateSeq: request && !request.toLatest ? request.endSeq : undefined
|
|
46610
|
+
});
|
|
46034
46611
|
return;
|
|
46612
|
+
}
|
|
46035
46613
|
case "user":
|
|
46036
46614
|
this.requestCatchUpUser(true);
|
|
46037
46615
|
return;
|
|
@@ -46216,7 +46794,7 @@ class InlineSdkClient {
|
|
|
46216
46794
|
bumpChatSeq(chatId, seq, source) {
|
|
46217
46795
|
if (source === "user" || source === "space" || source === "chat")
|
|
46218
46796
|
return;
|
|
46219
|
-
if (source === "live" && this.liveCursorFences.has(`chat:${chatId}`))
|
|
46797
|
+
if (source === "live" && this.liveCursorFences.has(`chat:${chatId}`) && seq !== (this.state.lastSeqByChatId?.[chatId.toString()] ?? 0) + 1)
|
|
46220
46798
|
return;
|
|
46221
46799
|
if (!Number.isFinite(seq))
|
|
46222
46800
|
return;
|
|
@@ -46225,6 +46803,7 @@ class InlineSdkClient {
|
|
|
46225
46803
|
const key = chatId.toString();
|
|
46226
46804
|
const prev = this.state.lastSeqByChatId[key] ?? 0;
|
|
46227
46805
|
if (seq > prev) {
|
|
46806
|
+
this.resetRecoveryBackoff({ kind: "chat", chatId });
|
|
46228
46807
|
this.state.lastSeqByChatId[key] = seq;
|
|
46229
46808
|
this.scheduleStateSave();
|
|
46230
46809
|
}
|
|
@@ -46233,7 +46812,7 @@ class InlineSdkClient {
|
|
|
46233
46812
|
bumpSpaceSeq(spaceId, seq, source) {
|
|
46234
46813
|
if (source === "user" || source === "space" || source === "chat")
|
|
46235
46814
|
return;
|
|
46236
|
-
if (source === "live" && this.liveCursorFences.has(`space:${spaceId}`))
|
|
46815
|
+
if (source === "live" && this.liveCursorFences.has(`space:${spaceId}`) && seq !== (this.state.lastSeqBySpaceId?.[spaceId.toString()] ?? 0) + 1)
|
|
46237
46816
|
return;
|
|
46238
46817
|
if (!Number.isFinite(seq))
|
|
46239
46818
|
return;
|
|
@@ -46242,6 +46821,7 @@ class InlineSdkClient {
|
|
|
46242
46821
|
const key = spaceId.toString();
|
|
46243
46822
|
const prev = this.state.lastSeqBySpaceId[key] ?? 0;
|
|
46244
46823
|
if (seq > prev) {
|
|
46824
|
+
this.resetRecoveryBackoff({ kind: "space", spaceId });
|
|
46245
46825
|
this.state.lastSeqBySpaceId[key] = seq;
|
|
46246
46826
|
this.scheduleStateSave();
|
|
46247
46827
|
}
|
|
@@ -46256,12 +46836,13 @@ class InlineSdkClient {
|
|
|
46256
46836
|
bumpUserSeq(seq, source) {
|
|
46257
46837
|
if (source === "user" || source === "space" || source === "chat")
|
|
46258
46838
|
return;
|
|
46259
|
-
if (source === "live" && this.liveCursorFences.has("user"))
|
|
46839
|
+
if (source === "live" && this.liveCursorFences.has("user") && seq !== (this.state.lastUserSeq ?? 0) + 1)
|
|
46260
46840
|
return;
|
|
46261
46841
|
if (!Number.isFinite(seq) || seq <= 0)
|
|
46262
46842
|
return;
|
|
46263
46843
|
const prev = this.state.lastUserSeq ?? 0;
|
|
46264
46844
|
if (seq > prev) {
|
|
46845
|
+
this.resetRecoveryBackoff({ kind: "user" });
|
|
46265
46846
|
this.state.lastUserSeq = seq;
|
|
46266
46847
|
this.scheduleStateSave();
|
|
46267
46848
|
}
|
|
@@ -46275,14 +46856,17 @@ class InlineSdkClient {
|
|
|
46275
46856
|
if (this.userCatchUpInFlight) {
|
|
46276
46857
|
return this.userCatchUpInFlight;
|
|
46277
46858
|
}
|
|
46859
|
+
if (this.recoveryRetries.get("user")?.timer)
|
|
46860
|
+
return null;
|
|
46278
46861
|
this.fenceLiveCursor({ kind: "user" });
|
|
46279
46862
|
this.userCatchUpInFlight = this.doCatchUpUser(lastUserSeq ?? 0).catch((error) => {
|
|
46280
|
-
this.
|
|
46863
|
+
this.recordCatchUpFailure({ kind: "user" }, error);
|
|
46281
46864
|
this.log.warn?.("GET_UPDATES user catch-up failed; bucket remains degraded", {
|
|
46282
46865
|
error: extractErrorMessage(error)
|
|
46283
46866
|
});
|
|
46284
46867
|
}).finally(() => {
|
|
46285
46868
|
this.userCatchUpInFlight = null;
|
|
46869
|
+
this.scheduleRecoveryRetry({ kind: "user" });
|
|
46286
46870
|
});
|
|
46287
46871
|
return this.userCatchUpInFlight;
|
|
46288
46872
|
}
|
|
@@ -46335,7 +46919,7 @@ class InlineSdkClient {
|
|
|
46335
46919
|
});
|
|
46336
46920
|
}
|
|
46337
46921
|
if (deliveredSeq <= cursor && !payload.final) {
|
|
46338
|
-
this.markUpdateBucketDegraded({ kind: "user" });
|
|
46922
|
+
this.markUpdateBucketDegraded({ kind: "user" }, "non_progress");
|
|
46339
46923
|
this.log.warn?.("GET_UPDATES user catch-up made no progress; bucket remains degraded", {
|
|
46340
46924
|
cursor,
|
|
46341
46925
|
deliveredSeq
|
|
@@ -46371,15 +46955,18 @@ class InlineSdkClient {
|
|
|
46371
46955
|
const existing = this.catchUpInFlightByChatId.get(params.chatId);
|
|
46372
46956
|
if (existing)
|
|
46373
46957
|
return existing;
|
|
46958
|
+
if (this.recoveryRetries.get(`chat:${params.chatId}`)?.timer)
|
|
46959
|
+
return Promise.resolve();
|
|
46960
|
+
const initialDemand = this.catchUpRequestedByChatId.get(params.chatId);
|
|
46374
46961
|
const task = this.drainCatchUpChat(params.chatId).catch((error) => {
|
|
46375
|
-
this.catchUpRequestedByChatId.
|
|
46376
|
-
this.markUpdateBucketDegraded({ kind: "chat", chatId: params.chatId, peer: params.peer });
|
|
46962
|
+
this.recordCatchUpFailure({ kind: "chat", chatId: params.chatId, peer: params.peer }, error, this.catchUpRequestedByChatId.get(params.chatId) !== initialDemand);
|
|
46377
46963
|
this.log.warn?.("GET_UPDATES chat catch-up failed; bucket remains degraded", {
|
|
46378
46964
|
chatId: params.chatId.toString(),
|
|
46379
46965
|
error: extractErrorMessage(error)
|
|
46380
46966
|
});
|
|
46381
46967
|
}).finally(() => {
|
|
46382
46968
|
this.catchUpInFlightByChatId.delete(params.chatId);
|
|
46969
|
+
this.scheduleRecoveryRetry({ kind: "chat", chatId: params.chatId });
|
|
46383
46970
|
});
|
|
46384
46971
|
this.catchUpInFlightByChatId.set(params.chatId, task);
|
|
46385
46972
|
return task;
|
|
@@ -46403,12 +46990,8 @@ class InlineSdkClient {
|
|
|
46403
46990
|
if (stop) {
|
|
46404
46991
|
if (this.catchUpRequestedByChatId.get(chatId) !== request)
|
|
46405
46992
|
continue;
|
|
46406
|
-
this.
|
|
46407
|
-
|
|
46408
|
-
}
|
|
46409
|
-
const latest = this.catchUpRequestedByChatId.get(chatId);
|
|
46410
|
-
const syncedSeq = this.state.lastSeqByChatId?.[key] ?? 0;
|
|
46411
|
-
if (!latest || latest.endSeq != null && latest.endSeq <= syncedSeq) {
|
|
46993
|
+
if (this.degradedUpdateBuckets.has(`chat:${chatId}`))
|
|
46994
|
+
return;
|
|
46412
46995
|
this.catchUpRequestedByChatId.delete(chatId);
|
|
46413
46996
|
return;
|
|
46414
46997
|
}
|
|
@@ -46475,7 +47058,7 @@ class InlineSdkClient {
|
|
|
46475
47058
|
});
|
|
46476
47059
|
}
|
|
46477
47060
|
if (deliveredSeq <= cursor && !payload.final) {
|
|
46478
|
-
this.markUpdateBucketDegraded({ kind: "chat", chatId, peer });
|
|
47061
|
+
this.markUpdateBucketDegraded({ kind: "chat", chatId, peer }, "non_progress");
|
|
46479
47062
|
this.log.warn?.("GET_UPDATES made no progress; bucket remains degraded", {
|
|
46480
47063
|
chatId: chatId.toString(),
|
|
46481
47064
|
cursor,
|
|
@@ -46493,7 +47076,7 @@ class InlineSdkClient {
|
|
|
46493
47076
|
continue;
|
|
46494
47077
|
}
|
|
46495
47078
|
if (endSeq != null && deliveredSeq < endSeq) {
|
|
46496
|
-
this.markUpdateBucketDegraded({ kind: "chat", chatId, peer });
|
|
47079
|
+
this.markUpdateBucketDegraded({ kind: "chat", chatId, peer }, "target_not_reached");
|
|
46497
47080
|
this.log.warn?.("GET_UPDATES final chat page remained behind the requested target", {
|
|
46498
47081
|
chatId: chatId.toString(),
|
|
46499
47082
|
requestedEndSeq: endSeq,
|
|
@@ -46519,15 +47102,18 @@ class InlineSdkClient {
|
|
|
46519
47102
|
const existing = this.catchUpInFlightBySpaceId.get(params.spaceId);
|
|
46520
47103
|
if (existing)
|
|
46521
47104
|
return existing;
|
|
47105
|
+
if (this.recoveryRetries.get(`space:${params.spaceId}`)?.timer)
|
|
47106
|
+
return Promise.resolve();
|
|
47107
|
+
const initialDemand = this.catchUpRequestedBySpaceId.get(params.spaceId);
|
|
46522
47108
|
const task = this.drainCatchUpSpace(params.spaceId).catch((error) => {
|
|
46523
|
-
this.catchUpRequestedBySpaceId.
|
|
46524
|
-
this.markUpdateBucketDegraded({ kind: "space", spaceId: params.spaceId });
|
|
47109
|
+
this.recordCatchUpFailure({ kind: "space", spaceId: params.spaceId }, error, this.catchUpRequestedBySpaceId.get(params.spaceId) !== initialDemand);
|
|
46525
47110
|
this.log.warn?.("GET_UPDATES space catch-up failed; bucket remains degraded", {
|
|
46526
47111
|
spaceId: params.spaceId.toString(),
|
|
46527
47112
|
error: extractErrorMessage(error)
|
|
46528
47113
|
});
|
|
46529
47114
|
}).finally(() => {
|
|
46530
47115
|
this.catchUpInFlightBySpaceId.delete(params.spaceId);
|
|
47116
|
+
this.scheduleRecoveryRetry({ kind: "space", spaceId: params.spaceId });
|
|
46531
47117
|
});
|
|
46532
47118
|
this.catchUpInFlightBySpaceId.set(params.spaceId, task);
|
|
46533
47119
|
return task;
|
|
@@ -46551,12 +47137,8 @@ class InlineSdkClient {
|
|
|
46551
47137
|
if (stop) {
|
|
46552
47138
|
if (this.catchUpRequestedBySpaceId.get(spaceId) !== request)
|
|
46553
47139
|
continue;
|
|
46554
|
-
this.
|
|
46555
|
-
|
|
46556
|
-
}
|
|
46557
|
-
const latest = this.catchUpRequestedBySpaceId.get(spaceId);
|
|
46558
|
-
const syncedSeq = this.state.lastSeqBySpaceId?.[key] ?? 0;
|
|
46559
|
-
if (!latest || latest.endSeq != null && latest.endSeq <= syncedSeq) {
|
|
47140
|
+
if (this.degradedUpdateBuckets.has(`space:${spaceId}`))
|
|
47141
|
+
return;
|
|
46560
47142
|
this.catchUpRequestedBySpaceId.delete(spaceId);
|
|
46561
47143
|
return;
|
|
46562
47144
|
}
|
|
@@ -46625,7 +47207,7 @@ class InlineSdkClient {
|
|
|
46625
47207
|
});
|
|
46626
47208
|
}
|
|
46627
47209
|
if (deliveredSeq <= cursor && !payload.final) {
|
|
46628
|
-
this.markUpdateBucketDegraded({ kind: "space", spaceId });
|
|
47210
|
+
this.markUpdateBucketDegraded({ kind: "space", spaceId }, "non_progress");
|
|
46629
47211
|
this.log.warn?.("GET_UPDATES space made no progress; bucket remains degraded", {
|
|
46630
47212
|
spaceId: spaceId.toString(),
|
|
46631
47213
|
cursor,
|
|
@@ -46643,7 +47225,7 @@ class InlineSdkClient {
|
|
|
46643
47225
|
continue;
|
|
46644
47226
|
}
|
|
46645
47227
|
if (endSeq != null && deliveredSeq < endSeq) {
|
|
46646
|
-
this.markUpdateBucketDegraded({ kind: "space", spaceId });
|
|
47228
|
+
this.markUpdateBucketDegraded({ kind: "space", spaceId }, "target_not_reached");
|
|
46647
47229
|
this.log.warn?.("GET_UPDATES final space page remained behind the requested target", {
|
|
46648
47230
|
spaceId: spaceId.toString(),
|
|
46649
47231
|
requestedEndSeq: endSeq,
|
|
@@ -46668,6 +47250,9 @@ class InlineSdkClient {
|
|
|
46668
47250
|
const existing = this.peerResolutionInFlightByChatId.get(chatId);
|
|
46669
47251
|
if (existing)
|
|
46670
47252
|
return existing;
|
|
47253
|
+
if (this.recoveryRetries.get(`chat:${chatId}`)?.timer)
|
|
47254
|
+
return Promise.resolve();
|
|
47255
|
+
const initialDemand = this.peerResolutionRequestedByChatId.get(chatId);
|
|
46671
47256
|
const task = this.getChat({ chatId }).then(async (chat) => {
|
|
46672
47257
|
const peer = chat.peer;
|
|
46673
47258
|
if (!peer || !this.isReliableChatPeer(peer)) {
|
|
@@ -46681,14 +47266,16 @@ class InlineSdkClient {
|
|
|
46681
47266
|
...requested && !requested.toLatest && requested.endSeq != null ? { updateSeq: requested.endSeq } : {}
|
|
46682
47267
|
});
|
|
46683
47268
|
}).catch((error) => {
|
|
46684
|
-
this.
|
|
47269
|
+
this.recordCatchUpFailure({ kind: "chat", chatId }, error, this.peerResolutionRequestedByChatId.get(chatId) !== initialDemand);
|
|
46685
47270
|
this.log.warn?.("Unable to resolve legacy chat peer; bucket remains degraded", {
|
|
46686
47271
|
chatId: chatId.toString(),
|
|
46687
47272
|
error: extractErrorMessage(error)
|
|
46688
47273
|
});
|
|
46689
47274
|
}).finally(() => {
|
|
46690
47275
|
this.peerResolutionInFlightByChatId.delete(chatId);
|
|
46691
|
-
this.
|
|
47276
|
+
if (!this.degradedUpdateBuckets.has(`chat:${chatId}`))
|
|
47277
|
+
this.peerResolutionRequestedByChatId.delete(chatId);
|
|
47278
|
+
this.scheduleRecoveryRetry({ kind: "chat", chatId });
|
|
46692
47279
|
});
|
|
46693
47280
|
this.peerResolutionInFlightByChatId.set(chatId, task);
|
|
46694
47281
|
return task;
|
|
@@ -46741,7 +47328,7 @@ class InlineSdkClient {
|
|
|
46741
47328
|
}
|
|
46742
47329
|
validateCatchUpPage(payload, startSeq, bucket) {
|
|
46743
47330
|
if (payload.resultType !== GetUpdatesResult_ResultType.SLICE && payload.resultType !== GetUpdatesResult_ResultType.EMPTY) {
|
|
46744
|
-
this.markUpdateBucketDegraded(bucket);
|
|
47331
|
+
this.markUpdateBucketDegraded(bucket, "invalid_page_type");
|
|
46745
47332
|
this.log.warn?.("GET_UPDATES returned an invalid page result type; bucket remains degraded", {
|
|
46746
47333
|
bucket: this.updateBucketKey(bucket),
|
|
46747
47334
|
resultType: payload.resultType
|
|
@@ -46750,7 +47337,7 @@ class InlineSdkClient {
|
|
|
46750
47337
|
}
|
|
46751
47338
|
const deliveredSeq = Number(payload.seq);
|
|
46752
47339
|
if (!Number.isSafeInteger(deliveredSeq) || deliveredSeq < startSeq) {
|
|
46753
|
-
this.markUpdateBucketDegraded(bucket);
|
|
47340
|
+
this.markUpdateBucketDegraded(bucket, "invalid_page_cursor");
|
|
46754
47341
|
this.log.warn?.("GET_UPDATES page moved backwards or returned an invalid seq; bucket remains degraded", {
|
|
46755
47342
|
bucket: this.updateBucketKey(bucket),
|
|
46756
47343
|
startSeq,
|
|
@@ -46762,7 +47349,7 @@ class InlineSdkClient {
|
|
|
46762
47349
|
for (const update of payload.updates) {
|
|
46763
47350
|
const seq = update.seq;
|
|
46764
47351
|
if (!Number.isSafeInteger(seq) || seq == null || seq <= startSeq || seq > deliveredSeq || accounted.has(seq)) {
|
|
46765
|
-
this.markUpdateBucketDegraded(bucket);
|
|
47352
|
+
this.markUpdateBucketDegraded(bucket, "invalid_update_sequence");
|
|
46766
47353
|
this.log.warn?.("GET_UPDATES page included an invalid or duplicate update seq; bucket remains degraded", {
|
|
46767
47354
|
bucket: this.updateBucketKey(bucket),
|
|
46768
47355
|
seq
|
|
@@ -46775,7 +47362,7 @@ class InlineSdkClient {
|
|
|
46775
47362
|
for (const skipped of payload.skippedSequences) {
|
|
46776
47363
|
const seq = Number(skipped.seq);
|
|
46777
47364
|
if (!Number.isSafeInteger(seq) || seq <= startSeq || seq > deliveredSeq || accounted.has(seq)) {
|
|
46778
|
-
this.markUpdateBucketDegraded(bucket);
|
|
47365
|
+
this.markUpdateBucketDegraded(bucket, "invalid_skipped_sequence");
|
|
46779
47366
|
this.log.warn?.("GET_UPDATES page included an invalid or duplicate skipped seq; bucket remains degraded", {
|
|
46780
47367
|
bucket: this.updateBucketKey(bucket),
|
|
46781
47368
|
seq
|
|
@@ -46791,7 +47378,7 @@ class InlineSdkClient {
|
|
|
46791
47378
|
break;
|
|
46792
47379
|
case SyncSkippedSequence_Reason.REASON_UNSPECIFIED:
|
|
46793
47380
|
default:
|
|
46794
|
-
this.markUpdateBucketDegraded(bucket);
|
|
47381
|
+
this.markUpdateBucketDegraded(bucket, "unknown_skip_reason");
|
|
46795
47382
|
this.log.warn?.("GET_UPDATES page included an unsupported skipped-sequence reason; bucket remains degraded", {
|
|
46796
47383
|
bucket: this.updateBucketKey(bucket),
|
|
46797
47384
|
reason: skipped.reason
|
|
@@ -46800,7 +47387,7 @@ class InlineSdkClient {
|
|
|
46800
47387
|
}
|
|
46801
47388
|
}
|
|
46802
47389
|
if (accounted.size !== deliveredSeq - startSeq) {
|
|
46803
|
-
this.markUpdateBucketDegraded(bucket);
|
|
47390
|
+
this.markUpdateBucketDegraded(bucket, "unaccounted_gap");
|
|
46804
47391
|
this.log.warn?.("GET_UPDATES page did not account for every advanced sequence; bucket remains degraded", {
|
|
46805
47392
|
bucket: this.updateBucketKey(bucket),
|
|
46806
47393
|
startSeq,
|
|
@@ -46882,7 +47469,7 @@ class InlineSdkClient {
|
|
|
46882
47469
|
this.clearUpdateBucketDegraded(bucket);
|
|
46883
47470
|
return true;
|
|
46884
47471
|
} catch (error) {
|
|
46885
|
-
this.markUpdateBucketDegraded(bucket);
|
|
47472
|
+
this.markUpdateBucketDegraded(bucket, "repair_failed");
|
|
46886
47473
|
this.log.warn?.("Authoritative update repair failed; bucket remains degraded", {
|
|
46887
47474
|
bucket: this.updateBucketKey(bucket),
|
|
46888
47475
|
error: extractErrorMessage(error)
|
|
@@ -46891,7 +47478,17 @@ class InlineSdkClient {
|
|
|
46891
47478
|
}
|
|
46892
47479
|
}
|
|
46893
47480
|
async acceptCatchUpUpdates(updates, source, bucket) {
|
|
47481
|
+
const liveDeliveries = this.liveDeliveriesByBucket.get(this.updateBucketKey(bucket));
|
|
47482
|
+
if (liveDeliveries && (await Promise.all(liveDeliveries)).some((applied) => !applied)) {
|
|
47483
|
+
this.markUpdateBucketDegraded(bucket, "host_unacknowledged");
|
|
47484
|
+
return false;
|
|
47485
|
+
}
|
|
47486
|
+
if (this.closed)
|
|
47487
|
+
return false;
|
|
47488
|
+
const appliedThrough = this.bucketCursor(bucket);
|
|
46894
47489
|
for (const update of updates) {
|
|
47490
|
+
if (update.seq != null && update.seq > 0 && update.seq <= appliedThrough)
|
|
47491
|
+
continue;
|
|
46895
47492
|
if (update.update.oneofKind === "chatSkipPts" && source === "chat") {
|
|
46896
47493
|
continue;
|
|
46897
47494
|
}
|
|
@@ -46902,7 +47499,7 @@ class InlineSdkClient {
|
|
|
46902
47499
|
if (accepted === true)
|
|
46903
47500
|
continue;
|
|
46904
47501
|
if (accepted === false) {
|
|
46905
|
-
this.markUpdateBucketDegraded(bucket);
|
|
47502
|
+
this.markUpdateBucketDegraded(bucket, "host_unacknowledged");
|
|
46906
47503
|
this.log.warn?.("GET_UPDATES event was not acknowledged by the SDK host; cursor remains unchanged", {
|
|
46907
47504
|
bucket: this.updateBucketKey(bucket),
|
|
46908
47505
|
updateKind: update.update.oneofKind
|
|
@@ -46923,8 +47520,95 @@ class InlineSdkClient {
|
|
|
46923
47520
|
}
|
|
46924
47521
|
return true;
|
|
46925
47522
|
}
|
|
46926
|
-
|
|
46927
|
-
|
|
47523
|
+
recordCatchUpFailure(bucket, error, hasNewerDemand = false) {
|
|
47524
|
+
const rpcCode = error instanceof ProtocolClientError ? error.rpcCode : undefined;
|
|
47525
|
+
if (!hasNewerDemand && bucket.kind !== "user" && (rpcCode === RpcError_Code.PEER_ID_INVALID || rpcCode === RpcError_Code.CHAT_ID_INVALID || rpcCode === RpcError_Code.SPACE_ID_INVALID)) {
|
|
47526
|
+
this.clearUpdateBucketDegraded(bucket);
|
|
47527
|
+
if (bucket.kind === "chat") {
|
|
47528
|
+
this.catchUpRequestedByChatId.delete(bucket.chatId);
|
|
47529
|
+
this.peerResolutionRequestedByChatId.delete(bucket.chatId);
|
|
47530
|
+
} else
|
|
47531
|
+
this.catchUpRequestedBySpaceId.delete(bucket.spaceId);
|
|
47532
|
+
const round = this.discoveryRound;
|
|
47533
|
+
if (round) {
|
|
47534
|
+
round.targets.delete(this.updateBucketKey(bucket));
|
|
47535
|
+
this.tryCommitDiscoveryRound(round);
|
|
47536
|
+
}
|
|
47537
|
+
this.log.error?.("Sync bucket unavailable", { bucketKind: bucket.kind, reason: "access_rejected" });
|
|
47538
|
+
return;
|
|
47539
|
+
}
|
|
47540
|
+
this.markUpdateBucketDegraded(bucket, rpcCode === RpcError_Code.RATE_LIMIT || rpcCode === 429 ? "rate_limited" : error instanceof ProtocolClientError ? error.code : "request_failed");
|
|
47541
|
+
}
|
|
47542
|
+
markUpdateBucketDegraded(bucket, failure = "incomplete") {
|
|
47543
|
+
const key = this.updateBucketKey(bucket);
|
|
47544
|
+
this.degradedUpdateBuckets.set(key, bucket);
|
|
47545
|
+
const retry = this.recoveryRetries.get(key) ?? { attempt: 0, failure, reported: new Set };
|
|
47546
|
+
retry.failure = failure;
|
|
47547
|
+
this.recoveryRetries.set(key, retry);
|
|
47548
|
+
queueMicrotask(() => this.scheduleRecoveryRetry(bucket));
|
|
47549
|
+
}
|
|
47550
|
+
resetRecoveryBackoff(bucket) {
|
|
47551
|
+
const retry = this.recoveryRetries.get(this.updateBucketKey(bucket));
|
|
47552
|
+
if (retry)
|
|
47553
|
+
retry.attempt = 0;
|
|
47554
|
+
}
|
|
47555
|
+
cancelRecoveryRetries() {
|
|
47556
|
+
clearTimeout(this.discoveryRetry?.timer);
|
|
47557
|
+
this.discoveryRetry = null;
|
|
47558
|
+
for (const retry of this.recoveryRetries.values())
|
|
47559
|
+
clearTimeout(retry.timer);
|
|
47560
|
+
this.recoveryRetries.clear();
|
|
47561
|
+
}
|
|
47562
|
+
scheduleDiscoveryRetry(reason) {
|
|
47563
|
+
if (!this.started || this.closed || this.protocol.getDiagnostics().state !== "open")
|
|
47564
|
+
return;
|
|
47565
|
+
const retry = this.discoveryRetry ?? { attempt: 0, reported: new Set };
|
|
47566
|
+
if (retry.timer)
|
|
47567
|
+
return;
|
|
47568
|
+
if (!retry.reported.has(reason)) {
|
|
47569
|
+
retry.reported.add(reason);
|
|
47570
|
+
this.log.error?.("Sync recovery scheduled", { bucketKind: "discovery", reason });
|
|
47571
|
+
}
|
|
47572
|
+
const delayMs = reason === "rate_limited" ? 60000 : Math.min(30000, 1000 * 2 ** Math.min(retry.attempt, 5));
|
|
47573
|
+
retry.attempt++;
|
|
47574
|
+
retry.timer = setTimeout(() => {
|
|
47575
|
+
retry.timer = undefined;
|
|
47576
|
+
if (this.started && !this.closed && this.protocol.getDiagnostics().state === "open") {
|
|
47577
|
+
this.initializeDateCursor();
|
|
47578
|
+
}
|
|
47579
|
+
}, delayMs);
|
|
47580
|
+
retry.timer.unref?.();
|
|
47581
|
+
this.discoveryRetry = retry;
|
|
47582
|
+
}
|
|
47583
|
+
scheduleRecoveryRetry(bucket) {
|
|
47584
|
+
const key = this.updateBucketKey(bucket);
|
|
47585
|
+
if (!this.started || this.closed || !this.degradedUpdateBuckets.has(key))
|
|
47586
|
+
return;
|
|
47587
|
+
if (bucket.kind === "chat" && (this.catchUpInFlightByChatId.has(bucket.chatId) || this.peerResolutionInFlightByChatId.has(bucket.chatId)))
|
|
47588
|
+
return;
|
|
47589
|
+
if (bucket.kind === "space" && this.catchUpInFlightBySpaceId.has(bucket.spaceId))
|
|
47590
|
+
return;
|
|
47591
|
+
if (bucket.kind === "user" && this.userCatchUpInFlight)
|
|
47592
|
+
return;
|
|
47593
|
+
const retry = this.recoveryRetries.get(key) ?? { attempt: 0, failure: "incomplete", reported: new Set };
|
|
47594
|
+
if (retry.timer || this.protocol.getDiagnostics().state !== "open")
|
|
47595
|
+
return;
|
|
47596
|
+
const delayMs = retry.failure === "rate_limited" ? 60000 : Math.min(5000, 1000 * 2 ** Math.min(retry.attempt, 3));
|
|
47597
|
+
if (!retry.reported.has(retry.failure)) {
|
|
47598
|
+
retry.reported.add(retry.failure);
|
|
47599
|
+
this.log.error?.("Sync recovery scheduled", { bucketKind: bucket.kind, reason: retry.failure });
|
|
47600
|
+
}
|
|
47601
|
+
retry.attempt++;
|
|
47602
|
+
retry.timer = setTimeout(() => {
|
|
47603
|
+
retry.timer = undefined;
|
|
47604
|
+
if (!this.started || this.closed || this.protocol.getDiagnostics().state !== "open")
|
|
47605
|
+
return;
|
|
47606
|
+
const pending = this.degradedUpdateBuckets.get(key);
|
|
47607
|
+
if (pending)
|
|
47608
|
+
this.requestCatchUpForDegradedBucket(pending);
|
|
47609
|
+
}, delayMs);
|
|
47610
|
+
retry.timer.unref?.();
|
|
47611
|
+
this.recoveryRetries.set(key, retry);
|
|
46928
47612
|
}
|
|
46929
47613
|
fenceLiveCursor(bucket) {
|
|
46930
47614
|
this.liveCursorFences.add(this.updateBucketKey(bucket));
|
|
@@ -46932,6 +47616,8 @@ class InlineSdkClient {
|
|
|
46932
47616
|
clearUpdateBucketDegraded(bucket) {
|
|
46933
47617
|
const key = this.updateBucketKey(bucket);
|
|
46934
47618
|
this.degradedUpdateBuckets.delete(key);
|
|
47619
|
+
clearTimeout(this.recoveryRetries.get(key)?.timer);
|
|
47620
|
+
this.recoveryRetries.delete(key);
|
|
46935
47621
|
this.liveCursorFences.delete(key);
|
|
46936
47622
|
this.liveAdmittedSeqByBucket.delete(key);
|
|
46937
47623
|
}
|
|
@@ -47538,334 +48224,6 @@ var portableCoreV1Vector = {
|
|
|
47538
48224
|
aesIvHex: "a32285aa1fc31b5b3a6c5e75843de0d48393f2350d23d7ccbbc3cb7b34ce7e6c",
|
|
47539
48225
|
recordHex: "32d1586ea457dfc80b016bab73824ee1e75f00f0fa824908302fa5dab375c8029b169848525548f61add2955845b9810fe817fcc7581efd11aaac110560a2cc78ae6a20cc6216a0b86fa0d061a57f84bacbf84af84ec31b4"
|
|
47540
48226
|
};
|
|
47541
|
-
// src/sidecar/contract.ts
|
|
47542
|
-
var MAX_INLINE_ID = 9223372036854775807n;
|
|
47543
|
-
var MAX_UINT64 = 18446744073709551615n;
|
|
47544
|
-
var sensitiveUrlParams = new Set([
|
|
47545
|
-
"access_token",
|
|
47546
|
-
"auth",
|
|
47547
|
-
"authorization",
|
|
47548
|
-
"key",
|
|
47549
|
-
"token"
|
|
47550
|
-
]);
|
|
47551
|
-
var botSettingsEventKinds = new Set([
|
|
47552
|
-
"bot.chatSettings.request",
|
|
47553
|
-
"bot.chatSettings.item.invoke"
|
|
47554
|
-
]);
|
|
47555
|
-
function inboundEventNeedsSenderResolution(event) {
|
|
47556
|
-
return !botSettingsEventKinds.has(event.kind ?? "");
|
|
47557
|
-
}
|
|
47558
|
-
|
|
47559
|
-
class SidecarError extends Error {
|
|
47560
|
-
errorKind;
|
|
47561
|
-
constructor(message, errorKind) {
|
|
47562
|
-
super(message);
|
|
47563
|
-
this.errorKind = errorKind;
|
|
47564
|
-
}
|
|
47565
|
-
}
|
|
47566
|
-
function parseTarget(record2) {
|
|
47567
|
-
const targetRecord = asOptionalRecord(record2.target) ?? record2;
|
|
47568
|
-
const chatId = readOptionalInlineId(targetRecord, "chatId");
|
|
47569
|
-
const userId = readOptionalInlineId(targetRecord, "userId");
|
|
47570
|
-
if (chatId && userId)
|
|
47571
|
-
throw new SidecarError("target cannot include both chatId and userId", "bad_format");
|
|
47572
|
-
if (chatId)
|
|
47573
|
-
return { chatId };
|
|
47574
|
-
if (userId)
|
|
47575
|
-
return { userId };
|
|
47576
|
-
throw new SidecarError("target requires chatId or userId", "bad_format");
|
|
47577
|
-
}
|
|
47578
|
-
function normalizeUploadKind(raw, filePath) {
|
|
47579
|
-
if (raw === "photo" || raw === "image")
|
|
47580
|
-
return "photo";
|
|
47581
|
-
if (raw === "video")
|
|
47582
|
-
return "video";
|
|
47583
|
-
if (raw === "voice")
|
|
47584
|
-
return "voice";
|
|
47585
|
-
if (raw === "document" || raw === "file")
|
|
47586
|
-
return "document";
|
|
47587
|
-
const lower = filePath.toLowerCase();
|
|
47588
|
-
if (/\.(png|jpg|jpeg|gif|webp|heic|heif)$/.test(lower))
|
|
47589
|
-
return "photo";
|
|
47590
|
-
if (/\.(mp4|mov|webm)$/.test(lower))
|
|
47591
|
-
return "video";
|
|
47592
|
-
return "document";
|
|
47593
|
-
}
|
|
47594
|
-
function normalizeError(error, redact = defaultErrorText) {
|
|
47595
|
-
if (error instanceof SidecarError) {
|
|
47596
|
-
return {
|
|
47597
|
-
status: statusForErrorKind(error.errorKind),
|
|
47598
|
-
errorKind: error.errorKind,
|
|
47599
|
-
message: redact(error)
|
|
47600
|
-
};
|
|
47601
|
-
}
|
|
47602
|
-
const message = redact(error);
|
|
47603
|
-
const lower = message.toLowerCase();
|
|
47604
|
-
if (lower.includes("rate") && lower.includes("limit")) {
|
|
47605
|
-
return { status: 429, errorKind: "rate_limited", message };
|
|
47606
|
-
}
|
|
47607
|
-
if (lower.includes("forbidden") || lower.includes("unauthorized")) {
|
|
47608
|
-
return { status: 403, errorKind: "forbidden", message };
|
|
47609
|
-
}
|
|
47610
|
-
if (lower.includes("not found") || lower.includes("missing")) {
|
|
47611
|
-
return { status: 404, errorKind: "not_found", message };
|
|
47612
|
-
}
|
|
47613
|
-
if (lower.includes("timeout") || lower.includes("network") || lower.includes("closed")) {
|
|
47614
|
-
return { status: 503, errorKind: "transient", message };
|
|
47615
|
-
}
|
|
47616
|
-
return { status: 500, errorKind: "unknown", message };
|
|
47617
|
-
}
|
|
47618
|
-
function statusForErrorKind(errorKind) {
|
|
47619
|
-
switch (errorKind) {
|
|
47620
|
-
case "bad_format":
|
|
47621
|
-
return 400;
|
|
47622
|
-
case "forbidden":
|
|
47623
|
-
return 403;
|
|
47624
|
-
case "not_found":
|
|
47625
|
-
return 404;
|
|
47626
|
-
case "too_long":
|
|
47627
|
-
return 413;
|
|
47628
|
-
case "rate_limited":
|
|
47629
|
-
return 429;
|
|
47630
|
-
case "transient":
|
|
47631
|
-
return 503;
|
|
47632
|
-
case "unknown":
|
|
47633
|
-
return 500;
|
|
47634
|
-
}
|
|
47635
|
-
}
|
|
47636
|
-
function normalizeInboundEvent(event, meId, sender, meUsername) {
|
|
47637
|
-
if (event.kind === "message.new" || event.kind === "message.edit") {
|
|
47638
|
-
const message = asOptionalRecord(event.message);
|
|
47639
|
-
return safeJson({
|
|
47640
|
-
kind: event.kind,
|
|
47641
|
-
chatId: event.chatId,
|
|
47642
|
-
seq: event.seq,
|
|
47643
|
-
date: event.date,
|
|
47644
|
-
meId,
|
|
47645
|
-
meUsername,
|
|
47646
|
-
...sender ? { sender } : {},
|
|
47647
|
-
message: message ? normalizeMessage(message) : null
|
|
47648
|
-
});
|
|
47649
|
-
}
|
|
47650
|
-
if (event.kind === "message.action.invoke") {
|
|
47651
|
-
return safeJson({
|
|
47652
|
-
...event,
|
|
47653
|
-
meId,
|
|
47654
|
-
meUsername,
|
|
47655
|
-
...sender ? { sender } : {},
|
|
47656
|
-
dataBase64: event.data instanceof Uint8Array ? Buffer.from(event.data).toString("base64") : typeof event.data === "string" ? event.data : ""
|
|
47657
|
-
});
|
|
47658
|
-
}
|
|
47659
|
-
return safeJson({ ...event, meId, meUsername, ...sender ? { sender } : {} });
|
|
47660
|
-
}
|
|
47661
|
-
function normalizeMessage(message) {
|
|
47662
|
-
return {
|
|
47663
|
-
id: message.id,
|
|
47664
|
-
fromId: message.fromId,
|
|
47665
|
-
chatId: message.chatId,
|
|
47666
|
-
peerId: message.peerId,
|
|
47667
|
-
message: message.message ?? null,
|
|
47668
|
-
out: Boolean(message.out),
|
|
47669
|
-
date: message.date,
|
|
47670
|
-
mentioned: Boolean(message.mentioned),
|
|
47671
|
-
replyToMsgId: message.replyToMsgId ?? null,
|
|
47672
|
-
entities: message.entities ?? null,
|
|
47673
|
-
media: message.media ?? null,
|
|
47674
|
-
attachments: message.attachments ?? null,
|
|
47675
|
-
reactions: message.reactions ?? null,
|
|
47676
|
-
replies: message.replies ?? null,
|
|
47677
|
-
actions: message.actions ?? null,
|
|
47678
|
-
rev: message.rev ?? null,
|
|
47679
|
-
raw: message
|
|
47680
|
-
};
|
|
47681
|
-
}
|
|
47682
|
-
function redactText(value, secrets) {
|
|
47683
|
-
let text = value instanceof Error ? value.message : String(value);
|
|
47684
|
-
for (const secret of secrets) {
|
|
47685
|
-
const raw = typeof secret.value === "string" ? secret.value : "";
|
|
47686
|
-
if (!raw)
|
|
47687
|
-
continue;
|
|
47688
|
-
text = text.split(raw).join(secret.label);
|
|
47689
|
-
}
|
|
47690
|
-
return text;
|
|
47691
|
-
}
|
|
47692
|
-
function redactUrl(value) {
|
|
47693
|
-
let url;
|
|
47694
|
-
try {
|
|
47695
|
-
url = new URL(value);
|
|
47696
|
-
} catch {
|
|
47697
|
-
return value;
|
|
47698
|
-
}
|
|
47699
|
-
if (url.username)
|
|
47700
|
-
url.username = "redacted";
|
|
47701
|
-
if (url.password)
|
|
47702
|
-
url.password = "redacted";
|
|
47703
|
-
const keys = Array.from(url.searchParams.keys());
|
|
47704
|
-
for (const key of keys) {
|
|
47705
|
-
const normalized = key.toLowerCase();
|
|
47706
|
-
if (sensitiveUrlParams.has(normalized) || normalized.includes("token")) {
|
|
47707
|
-
url.searchParams.set(key, "redacted");
|
|
47708
|
-
}
|
|
47709
|
-
}
|
|
47710
|
-
return url.toString();
|
|
47711
|
-
}
|
|
47712
|
-
function safeJson(value) {
|
|
47713
|
-
if (value == null)
|
|
47714
|
-
return null;
|
|
47715
|
-
if (typeof value === "string" || typeof value === "boolean")
|
|
47716
|
-
return value;
|
|
47717
|
-
if (typeof value === "number")
|
|
47718
|
-
return Number.isFinite(value) ? value : null;
|
|
47719
|
-
if (typeof value === "bigint")
|
|
47720
|
-
return value.toString();
|
|
47721
|
-
if (value instanceof Uint8Array)
|
|
47722
|
-
return Buffer.from(value).toString("base64");
|
|
47723
|
-
if (Array.isArray(value))
|
|
47724
|
-
return value.map(safeJson);
|
|
47725
|
-
if (typeof value === "object") {
|
|
47726
|
-
const out = {};
|
|
47727
|
-
for (const [key, item] of Object.entries(value)) {
|
|
47728
|
-
if (item !== undefined)
|
|
47729
|
-
out[key] = safeJson(item);
|
|
47730
|
-
}
|
|
47731
|
-
return out;
|
|
47732
|
-
}
|
|
47733
|
-
return String(value);
|
|
47734
|
-
}
|
|
47735
|
-
function asRecord(value) {
|
|
47736
|
-
const record2 = asOptionalRecord(value);
|
|
47737
|
-
if (!record2)
|
|
47738
|
-
throw new SidecarError("expected JSON object", "bad_format");
|
|
47739
|
-
return record2;
|
|
47740
|
-
}
|
|
47741
|
-
function asOptionalRecord(value) {
|
|
47742
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
47743
|
-
return null;
|
|
47744
|
-
return value;
|
|
47745
|
-
}
|
|
47746
|
-
function readRequiredString(record2, key) {
|
|
47747
|
-
const value = readOptionalString(record2, key);
|
|
47748
|
-
if (!value)
|
|
47749
|
-
throw new SidecarError(`missing ${key}`, "bad_format");
|
|
47750
|
-
return value;
|
|
47751
|
-
}
|
|
47752
|
-
function readOptionalString(record2, key) {
|
|
47753
|
-
const value = record2[key];
|
|
47754
|
-
if (typeof value === "string")
|
|
47755
|
-
return value.trim() || undefined;
|
|
47756
|
-
if (typeof value === "bigint" || typeof value === "number")
|
|
47757
|
-
return String(value);
|
|
47758
|
-
return;
|
|
47759
|
-
}
|
|
47760
|
-
function readOptionalBoolean(record2, key) {
|
|
47761
|
-
const value = record2[key];
|
|
47762
|
-
if (typeof value === "boolean")
|
|
47763
|
-
return value;
|
|
47764
|
-
if (typeof value === "string") {
|
|
47765
|
-
if (/^(1|true|yes|on)$/i.test(value))
|
|
47766
|
-
return true;
|
|
47767
|
-
if (/^(0|false|no|off)$/i.test(value))
|
|
47768
|
-
return false;
|
|
47769
|
-
}
|
|
47770
|
-
return;
|
|
47771
|
-
}
|
|
47772
|
-
function readOptionalNumber(record2, key) {
|
|
47773
|
-
const value = record2[key];
|
|
47774
|
-
if (typeof value === "number" && Number.isFinite(value))
|
|
47775
|
-
return value;
|
|
47776
|
-
if (typeof value === "string" && value.trim()) {
|
|
47777
|
-
const parsed = Number(value);
|
|
47778
|
-
if (Number.isFinite(parsed))
|
|
47779
|
-
return parsed;
|
|
47780
|
-
}
|
|
47781
|
-
return;
|
|
47782
|
-
}
|
|
47783
|
-
function readRequiredInlineId(record2, key) {
|
|
47784
|
-
const value = readOptionalInlineId(record2, key);
|
|
47785
|
-
if (value == null)
|
|
47786
|
-
throw new SidecarError(`missing ${key}`, "bad_format");
|
|
47787
|
-
return value;
|
|
47788
|
-
}
|
|
47789
|
-
function readOptionalInlineId(record2, key) {
|
|
47790
|
-
const value = record2[key];
|
|
47791
|
-
if (value == null || value === "")
|
|
47792
|
-
return;
|
|
47793
|
-
return parseInlineId(value, key);
|
|
47794
|
-
}
|
|
47795
|
-
function readInlineIdArray(record2, key, maxItems, required = false) {
|
|
47796
|
-
const value = record2[key];
|
|
47797
|
-
if (value == null) {
|
|
47798
|
-
if (required)
|
|
47799
|
-
throw new SidecarError(`missing ${key}`, "bad_format");
|
|
47800
|
-
return [];
|
|
47801
|
-
}
|
|
47802
|
-
if (!Array.isArray(value))
|
|
47803
|
-
throw new SidecarError(`${key} must be an array`, "bad_format");
|
|
47804
|
-
if (value.length === 0 && required)
|
|
47805
|
-
throw new SidecarError(`${key} must not be empty`, "bad_format");
|
|
47806
|
-
if (value.length > maxItems)
|
|
47807
|
-
throw new SidecarError(`${key} supports at most ${maxItems} items`, "bad_format");
|
|
47808
|
-
const ids = [];
|
|
47809
|
-
const seen = new Set;
|
|
47810
|
-
for (let index = 0;index < value.length; index += 1) {
|
|
47811
|
-
const id = parseInlineId(value[index], `${key}[${index}]`);
|
|
47812
|
-
const normalized = id.toString();
|
|
47813
|
-
if (seen.has(normalized))
|
|
47814
|
-
continue;
|
|
47815
|
-
seen.add(normalized);
|
|
47816
|
-
ids.push(id);
|
|
47817
|
-
}
|
|
47818
|
-
return ids;
|
|
47819
|
-
}
|
|
47820
|
-
function parseOptionalInt(value) {
|
|
47821
|
-
const raw = (value || "").trim();
|
|
47822
|
-
if (!raw || !/^\d+$/.test(raw))
|
|
47823
|
-
return;
|
|
47824
|
-
const parsed = Number(raw);
|
|
47825
|
-
return Number.isSafeInteger(parsed) ? parsed : undefined;
|
|
47826
|
-
}
|
|
47827
|
-
function parseInlineId(value, field) {
|
|
47828
|
-
try {
|
|
47829
|
-
if (typeof value === "number" && (!Number.isSafeInteger(value) || value <= 0)) {
|
|
47830
|
-
throw new Error("unsafe number");
|
|
47831
|
-
}
|
|
47832
|
-
const raw = typeof value === "string" ? value.trim() : value;
|
|
47833
|
-
if (typeof raw === "string" && !/^[1-9][0-9]*$/.test(raw))
|
|
47834
|
-
throw new Error("invalid digits");
|
|
47835
|
-
if (typeof raw !== "string" && typeof raw !== "bigint" && typeof raw !== "number") {
|
|
47836
|
-
throw new Error("invalid type");
|
|
47837
|
-
}
|
|
47838
|
-
const parsed = BigInt(raw);
|
|
47839
|
-
if (parsed <= 0n || parsed > MAX_INLINE_ID)
|
|
47840
|
-
throw new Error("out of range");
|
|
47841
|
-
return parsed;
|
|
47842
|
-
} catch {
|
|
47843
|
-
throw new SidecarError(`${field} must be a positive signed 64-bit integer`, "bad_format");
|
|
47844
|
-
}
|
|
47845
|
-
}
|
|
47846
|
-
function parseUnsigned64Id(value, field) {
|
|
47847
|
-
try {
|
|
47848
|
-
if (typeof value === "number" && (!Number.isSafeInteger(value) || value <= 0)) {
|
|
47849
|
-
throw new Error("unsafe number");
|
|
47850
|
-
}
|
|
47851
|
-
const raw = typeof value === "string" ? value.trim() : value;
|
|
47852
|
-
if (typeof raw === "string" && !/^[1-9][0-9]*$/.test(raw))
|
|
47853
|
-
throw new Error("invalid digits");
|
|
47854
|
-
if (typeof raw !== "string" && typeof raw !== "bigint" && typeof raw !== "number") {
|
|
47855
|
-
throw new Error("invalid type");
|
|
47856
|
-
}
|
|
47857
|
-
const parsed = BigInt(raw);
|
|
47858
|
-
if (parsed <= 0n || parsed > MAX_UINT64)
|
|
47859
|
-
throw new Error("out of range");
|
|
47860
|
-
return parsed;
|
|
47861
|
-
} catch {
|
|
47862
|
-
throw new SidecarError(`${field} must be a positive unsigned 64-bit integer`, "bad_format");
|
|
47863
|
-
}
|
|
47864
|
-
}
|
|
47865
|
-
function defaultErrorText(error) {
|
|
47866
|
-
return error instanceof Error ? error.message : String(error);
|
|
47867
|
-
}
|
|
47868
|
-
|
|
47869
48227
|
// src/sidecar/user-directory.ts
|
|
47870
48228
|
var DEFAULT_PROFILE_TTL_MS = 10 * 60000;
|
|
47871
48229
|
var DEFAULT_MAX_PROFILES = 5000;
|
|
@@ -47873,6 +48231,7 @@ var DEFAULT_MAX_PROFILES = 5000;
|
|
|
47873
48231
|
class InlineUserDirectory {
|
|
47874
48232
|
client;
|
|
47875
48233
|
profiles = new Map;
|
|
48234
|
+
lookupTimeouts = new Map;
|
|
47876
48235
|
hydratedChats = new Map;
|
|
47877
48236
|
chatFetches = new Map;
|
|
47878
48237
|
directoryExpiresAt = 0;
|
|
@@ -47897,7 +48256,7 @@ class InlineUserDirectory {
|
|
|
47897
48256
|
async resolveWithProvenance(params) {
|
|
47898
48257
|
const userId = params.userId.toString();
|
|
47899
48258
|
const cached2 = this.getFresh(userId);
|
|
47900
|
-
if (
|
|
48259
|
+
if (cached2?.bot != null) {
|
|
47901
48260
|
return { profile: cached2, provenanceVerified: true };
|
|
47902
48261
|
}
|
|
47903
48262
|
if (params.direct) {
|
|
@@ -47910,7 +48269,7 @@ class InlineUserDirectory {
|
|
|
47910
48269
|
} else {
|
|
47911
48270
|
const chatHydrated = await this.hydrateChat(params.chatId);
|
|
47912
48271
|
const participant = this.getFresh(userId);
|
|
47913
|
-
if (hasDisplayIdentity(participant)) {
|
|
48272
|
+
if (participant?.bot != null || hasDisplayIdentity(participant)) {
|
|
47914
48273
|
return {
|
|
47915
48274
|
profile: participant,
|
|
47916
48275
|
provenanceVerified: chatHydrated || participant.bot != null
|
|
@@ -47979,7 +48338,7 @@ class InlineUserDirectory {
|
|
|
47979
48338
|
if (existing)
|
|
47980
48339
|
return existing;
|
|
47981
48340
|
const fetch2 = (async () => {
|
|
47982
|
-
const result = await this.
|
|
48341
|
+
const result = await this.invokeLookup(Method.GET_CHAT_PARTICIPANTS, {
|
|
47983
48342
|
oneofKind: "getChatParticipants",
|
|
47984
48343
|
getChatParticipants: { chatId }
|
|
47985
48344
|
});
|
|
@@ -48000,13 +48359,24 @@ class InlineUserDirectory {
|
|
|
48000
48359
|
this.chatFetches.set(key, fetch2);
|
|
48001
48360
|
return await fetch2;
|
|
48002
48361
|
}
|
|
48362
|
+
async invokeLookup(method, input) {
|
|
48363
|
+
const timeoutMs = this.lookupTimeouts.get(method) ?? 1500;
|
|
48364
|
+
try {
|
|
48365
|
+
return await this.client.invokeUncheckedRaw(method, input, { timeoutMs });
|
|
48366
|
+
} catch (error) {
|
|
48367
|
+
if (error instanceof ProtocolClientError && error.code === "timeout") {
|
|
48368
|
+
this.lookupTimeouts.set(method, Math.max(this.lookupTimeouts.get(method) ?? 0, Math.min(30000, timeoutMs * 2)));
|
|
48369
|
+
}
|
|
48370
|
+
throw error;
|
|
48371
|
+
}
|
|
48372
|
+
}
|
|
48003
48373
|
async hydrateDirectory() {
|
|
48004
48374
|
if (this.directoryExpiresAt > this.now())
|
|
48005
48375
|
return true;
|
|
48006
48376
|
if (this.directoryFetch)
|
|
48007
48377
|
return this.directoryFetch;
|
|
48008
48378
|
const fetch2 = (async () => {
|
|
48009
|
-
const result = await this.
|
|
48379
|
+
const result = await this.invokeLookup(Method.GET_CHATS, {
|
|
48010
48380
|
oneofKind: "getChats",
|
|
48011
48381
|
getChats: {}
|
|
48012
48382
|
});
|
|
@@ -48244,8 +48614,8 @@ var meUsername = null;
|
|
|
48244
48614
|
var connectError = null;
|
|
48245
48615
|
var connectAttempts = 0;
|
|
48246
48616
|
var nextConnectRetryAt = null;
|
|
48247
|
-
var
|
|
48248
|
-
var
|
|
48617
|
+
var inboundStream = new InboundStream;
|
|
48618
|
+
var inboundAbort = new AbortController;
|
|
48249
48619
|
var clientOptions = {
|
|
48250
48620
|
token,
|
|
48251
48621
|
baseUrl,
|
|
@@ -48321,11 +48691,15 @@ async function connectClientLoop() {
|
|
|
48321
48691
|
}
|
|
48322
48692
|
async function consumeEvents() {
|
|
48323
48693
|
try {
|
|
48324
|
-
|
|
48325
|
-
|
|
48326
|
-
|
|
48327
|
-
|
|
48328
|
-
|
|
48694
|
+
await client.consumeEvents(async (event) => {
|
|
48695
|
+
await deliverInboundEvent(event, {
|
|
48696
|
+
meId,
|
|
48697
|
+
meUsername,
|
|
48698
|
+
signal: inboundAbort.signal,
|
|
48699
|
+
resolveSender: resolveInboundSender,
|
|
48700
|
+
deliver
|
|
48701
|
+
});
|
|
48702
|
+
});
|
|
48329
48703
|
} catch (error) {
|
|
48330
48704
|
if (!stopping) {
|
|
48331
48705
|
reportHermesPluginError("inbound.loop", error, { handled: false });
|
|
@@ -49166,6 +49540,10 @@ class MockInlineClient {
|
|
|
49166
49540
|
calls: this.calls
|
|
49167
49541
|
};
|
|
49168
49542
|
}
|
|
49543
|
+
async consumeEvents(handler) {
|
|
49544
|
+
for await (const event of this.events())
|
|
49545
|
+
await handler(event);
|
|
49546
|
+
}
|
|
49169
49547
|
events() {
|
|
49170
49548
|
return {
|
|
49171
49549
|
[Symbol.asyncIterator]: () => ({
|
|
@@ -49458,41 +49836,15 @@ server.listen(port, bind, () => {
|
|
|
49458
49836
|
});
|
|
49459
49837
|
setupShutdown();
|
|
49460
49838
|
function attachConsumer(res) {
|
|
49461
|
-
if (consumer) {
|
|
49462
|
-
consumer.end();
|
|
49463
|
-
consumer = null;
|
|
49464
|
-
}
|
|
49465
49839
|
res.writeHead(200, {
|
|
49466
49840
|
"content-type": "application/x-ndjson; charset=utf-8",
|
|
49467
49841
|
"cache-control": "no-cache",
|
|
49468
49842
|
connection: "keep-alive"
|
|
49469
49843
|
});
|
|
49470
|
-
|
|
49471
|
-
const waiters = consumerWaiters;
|
|
49472
|
-
consumerWaiters = [];
|
|
49473
|
-
for (const resolve of waiters)
|
|
49474
|
-
resolve();
|
|
49475
|
-
res.on("close", () => {
|
|
49476
|
-
if (consumer === res)
|
|
49477
|
-
consumer = null;
|
|
49478
|
-
});
|
|
49844
|
+
inboundStream.attach(res);
|
|
49479
49845
|
}
|
|
49480
49846
|
async function deliver(event) {
|
|
49481
|
-
|
|
49482
|
-
if (!consumer) {
|
|
49483
|
-
await new Promise((resolve) => consumerWaiters.push(resolve));
|
|
49484
|
-
continue;
|
|
49485
|
-
}
|
|
49486
|
-
try {
|
|
49487
|
-
const ok = consumer.write(JSON.stringify(event) + `
|
|
49488
|
-
`);
|
|
49489
|
-
if (!ok)
|
|
49490
|
-
await once(consumer, "drain");
|
|
49491
|
-
return;
|
|
49492
|
-
} catch {
|
|
49493
|
-
consumer = null;
|
|
49494
|
-
}
|
|
49495
|
-
}
|
|
49847
|
+
await inboundStream.deliver(event);
|
|
49496
49848
|
}
|
|
49497
49849
|
function inputPeerFromTarget(target) {
|
|
49498
49850
|
if ("chatId" in target) {
|
|
@@ -49593,6 +49945,9 @@ function redactError(error) {
|
|
|
49593
49945
|
}
|
|
49594
49946
|
function log(level, args) {
|
|
49595
49947
|
const message = args.map((arg) => typeof arg === "string" ? arg : JSON.stringify(safeJson(arg))).join(" ");
|
|
49948
|
+
if (level === "error" && (args[0] === "Sync recovery scheduled" || args[0] === "Sync bucket unavailable" || args[0] === "Sync discovery unavailable")) {
|
|
49949
|
+
reportHermesPluginError("sdk.sync_recovery", new Error(redactError(message)));
|
|
49950
|
+
}
|
|
49596
49951
|
console.error(`inline-sidecar:${level}: ${redactError(message)}`);
|
|
49597
49952
|
}
|
|
49598
49953
|
function clampRetryMs(value, fallback) {
|
|
@@ -49643,8 +49998,8 @@ async function shutdown(code) {
|
|
|
49643
49998
|
return;
|
|
49644
49999
|
stopping = true;
|
|
49645
50000
|
try {
|
|
49646
|
-
|
|
49647
|
-
|
|
50001
|
+
inboundAbort.abort();
|
|
50002
|
+
inboundStream.close();
|
|
49648
50003
|
server.close();
|
|
49649
50004
|
await client.close();
|
|
49650
50005
|
} catch (error) {
|