@opendatalabs/personal-server-ts-server 1.35.5 → 1.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -123780,14 +123780,14 @@ function parsePayload(value) {
123780
123780
  grantId: payload["grantId"]
123781
123781
  };
123782
123782
  }
123783
- function parseWeb3SignedHeader(headerValue) {
123784
- if (!headerValue) {
123783
+ function parseWeb3SignedHeader(headerValue2) {
123784
+ if (!headerValue2) {
123785
123785
  throw new MissingAuthError();
123786
123786
  }
123787
- if (!headerValue.startsWith(WEB3_SIGNED_PREFIX)) {
123787
+ if (!headerValue2.startsWith(WEB3_SIGNED_PREFIX)) {
123788
123788
  throw new InvalidSignatureError2({ reason: "Missing Web3Signed prefix" });
123789
123789
  }
123790
- const value = headerValue.slice(WEB3_SIGNED_PREFIX.length);
123790
+ const value = headerValue2.slice(WEB3_SIGNED_PREFIX.length);
123791
123791
  const dotIndex = value.indexOf(".");
123792
123792
  if (dotIndex === -1 || dotIndex === 0 || dotIndex === value.length - 1) {
123793
123793
  throw new InvalidSignatureError2({ reason: "Invalid header format" });
@@ -146023,8 +146023,8 @@ function canonicalJsonBytes(value) {
146023
146023
  // ../core/dist/auth/proof-id.js
146024
146024
  var MAX_PROOF_LIFETIME_SEC = 15 * 60;
146025
146025
  var PROOF_CLOCK_SKEW_SEC = 60;
146026
- async function web3SignedProofId(headerValue, signer) {
146027
- const { payloadBase64 } = parseWeb3SignedHeader(headerValue);
146026
+ async function web3SignedProofId(headerValue2, signer) {
146027
+ const { payloadBase64 } = parseWeb3SignedHeader(headerValue2);
146028
146028
  const bytes2 = new TextEncoder().encode(`${signer.toLowerCase()}
146029
146029
  ${payloadBase64}`);
146030
146030
  const digest6 = await crypto.subtle.digest("SHA-256", bytes2);
@@ -146113,8 +146113,8 @@ async function signedBytesFor(request2, bodyBytes) {
146113
146113
  });
146114
146114
  }
146115
146115
  async function verifyWriterAttribution(input) {
146116
- const headerValue = input.request.headers.get(WRITE_SIGNATURE_HEADER) ?? void 0;
146117
- if (!headerValue) {
146116
+ const headerValue2 = input.request.headers.get(WRITE_SIGNATURE_HEADER) ?? void 0;
146117
+ if (!headerValue2) {
146118
146118
  throw new ProtocolError(401, "WRITE_ATTRIBUTION_REQUIRED", `Session writes must carry a builder-signed payload proof in ${WRITE_SIGNATURE_HEADER}`);
146119
146119
  }
146120
146120
  const url2 = new URL(input.request.url);
@@ -146122,7 +146122,7 @@ async function verifyWriterAttribution(input) {
146122
146122
  const expectedUri = canonicalSignedUri(url2.pathname, url2.search);
146123
146123
  let expectedPath = expectedUri;
146124
146124
  try {
146125
- const claimedUri = parseWeb3SignedHeader(headerValue).payload.uri;
146125
+ const claimedUri = parseWeb3SignedHeader(headerValue2).payload.uri;
146126
146126
  if (canonicalizeSignedUri(claimedUri) === expectedUri) {
146127
146127
  expectedPath = claimedUri;
146128
146128
  }
@@ -146131,7 +146131,7 @@ async function verifyWriterAttribution(input) {
146131
146131
  let verified;
146132
146132
  try {
146133
146133
  verified = await verifyWeb3Signed({
146134
- headerValue,
146134
+ headerValue: headerValue2,
146135
146135
  expectedOrigin: resolveOrigin(input.serverOrigin),
146136
146136
  expectedMethod: input.request.method,
146137
146137
  expectedPath,
@@ -146147,7 +146147,7 @@ async function verifyWriterAttribution(input) {
146147
146147
  throw new ProtocolError(401, "WRITE_ATTRIBUTION_GRANT_MISMATCH", "Payload proof must carry the write session's grantId as a signed claim", { expected: input.grantId, actual: verified.payload.grantId ?? null });
146148
146148
  }
146149
146149
  const expiresAtMs = boundedProofExpiry(verified.payload, "WRITE_ATTRIBUTION_LIFETIME");
146150
- const { payloadBase64, signature } = parseWeb3SignedHeader(headerValue);
146150
+ const { payloadBase64, signature } = parseWeb3SignedHeader(headerValue2);
146151
146151
  const nonce = signedNonce(payloadBase64);
146152
146152
  if (!isBodylessMethod(input.request.method) && bodyBytes.byteLength > 0 && isJsonContentType(input.request)) {
146153
146153
  assertCanonicalJsonBody(bodyBytes);
@@ -146155,7 +146155,7 @@ async function verifyWriterAttribution(input) {
146155
146155
  let releaseProof;
146156
146156
  if (input.replayStore) {
146157
146157
  const store = input.replayStore;
146158
- const proofId = nonce === void 0 ? await web3SignedProofId(headerValue, verified.signer) : await hashConnectionToken(`nonce:${input.builderAddress.toLowerCase()}:${nonce}`);
146158
+ const proofId = nonce === void 0 ? await web3SignedProofId(headerValue2, verified.signer) : await hashConnectionToken(`nonce:${input.builderAddress.toLowerCase()}:${nonce}`);
146159
146159
  const replayed = await store.consume(proofId, expiresAtMs);
146160
146160
  if (replayed) {
146161
146161
  throw new ProtocolError(401, "WRITE_ATTRIBUTION_REPLAY", nonce === void 0 ? "Payload proof already used; sign a fresh proof for each call (or add a unique nonce claim so repeated reads stay distinct)" : "Payload proof nonce already used; each nonce is single use");
@@ -147395,10 +147395,10 @@ function decodeBase64(input) {
147395
147395
  }
147396
147396
  throw new Error("Base64 decoding is not available in this runtime");
147397
147397
  }
147398
- function parseOAuthBasicAuth(headerValue) {
147399
- if (!headerValue?.startsWith("Basic "))
147398
+ function parseOAuthBasicAuth(headerValue2) {
147399
+ if (!headerValue2?.startsWith("Basic "))
147400
147400
  return null;
147401
- const decoded = decodeBase64(headerValue.slice(6));
147401
+ const decoded = decodeBase64(headerValue2.slice(6));
147402
147402
  const separatorIndex = decoded.indexOf(":");
147403
147403
  if (separatorIndex === -1)
147404
147404
  return null;
@@ -149826,12 +149826,12 @@ function base64decode(input) {
149826
149826
  const bytes2 = Uint8Array.from(binString, (c10) => c10.codePointAt(0) ?? 0);
149827
149827
  return new TextDecoder().decode(bytes2);
149828
149828
  }
149829
- function parsePaymentHeader(headerValue) {
149830
- if (!headerValue)
149829
+ function parsePaymentHeader(headerValue2) {
149830
+ if (!headerValue2)
149831
149831
  return null;
149832
149832
  let decoded;
149833
149833
  try {
149834
- decoded = base64decode(headerValue.trim());
149834
+ decoded = base64decode(headerValue2.trim());
149835
149835
  } catch {
149836
149836
  return null;
149837
149837
  }
@@ -150274,8 +150274,8 @@ async function handleX402Cycle(input) {
150274
150274
  now: deps.now
150275
150275
  });
150276
150276
  }
150277
- const headerValue = input.request.headers.get("x-payment");
150278
- const parsed = parsePaymentHeader(headerValue);
150277
+ const headerValue2 = input.request.headers.get("x-payment");
150278
+ const parsed = parsePaymentHeader(headerValue2);
150279
150279
  if (!parsed) {
150280
150280
  return { kind: "challenge", body: await buildFreshChallenge() };
150281
150281
  }
@@ -150792,6 +150792,37 @@ async function handlePersonalServerScopeUploadAppend(request2, deps, batch) {
150792
150792
  return handleScopeUploadRequest(request2, deps, parts, batch);
150793
150793
  });
150794
150794
  }
150795
+ var READ_GRANTEE_OBSERVER = /* @__PURE__ */ Symbol.for("@opendatalabs/personal-server-ts-core/read-grantee-observer");
150796
+ function attachPersonalServerReadGranteeObserver(request2, observer) {
150797
+ Object.defineProperty(request2, READ_GRANTEE_OBSERVER, {
150798
+ value: observer,
150799
+ configurable: true
150800
+ });
150801
+ }
150802
+ function readGranteeObserverOf(request2) {
150803
+ const observer = request2[READ_GRANTEE_OBSERVER];
150804
+ return typeof observer === "function" ? observer : void 0;
150805
+ }
150806
+ async function granteeRefFor(authResult) {
150807
+ if (authResult?.grantId === "owner" || authResult?.grantId === "policy-bypass") {
150808
+ return "owner";
150809
+ }
150810
+ const builder = authResult?.builder;
150811
+ if (typeof builder !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(builder)) {
150812
+ return void 0;
150813
+ }
150814
+ const digest6 = await sha256Hex(new TextEncoder().encode(builder.toLowerCase()));
150815
+ return digest6.slice(2, 14);
150816
+ }
150817
+ function notifyReadGrantee(request2, authResult) {
150818
+ const observer = readGranteeObserverOf(request2);
150819
+ if (!observer)
150820
+ return;
150821
+ try {
150822
+ observer(granteeRefFor(authResult).catch(() => void 0));
150823
+ } catch {
150824
+ }
150825
+ }
150795
150826
  async function handlePersonalServerDataRequest(request2, deps, options = {}) {
150796
150827
  const logContext = {
150797
150828
  logger: deps.logger,
@@ -151029,6 +151060,7 @@ async function handlePersonalServerDataRequest(request2, deps, options = {}) {
151029
151060
  fileId: fileIdParam ?? selectedEntry?.fileId ?? void 0,
151030
151061
  at: atParam
151031
151062
  });
151063
+ notifyReadGrantee(request2, authResult);
151032
151064
  const latest = fileIdParam === void 0 && atParam === void 0 && view === null ? await deps.legacyScopeView?.readLatest(scopeResult.scope) : void 0;
151033
151065
  const canonicalOwned = latest !== void 0 && deletionHistoryOf(latest) === "canonical";
151034
151066
  await assertScopeNotDeleted(deps, scopeResult.scope, canonicalOwned ? void 0 : selectedEntry, canonicalOwned ? "canonical" : "file");
@@ -151808,10 +151840,10 @@ function safeCompare(a10, b10) {
151808
151840
  }
151809
151841
  return diff === 0;
151810
151842
  }
151811
- function getBearerToken(headerValue) {
151812
- if (!headerValue?.startsWith("Bearer "))
151843
+ function getBearerToken(headerValue2) {
151844
+ if (!headerValue2?.startsWith("Bearer "))
151813
151845
  return null;
151814
- return headerValue.slice(7);
151846
+ return headerValue2.slice(7);
151815
151847
  }
151816
151848
  async function cacheRequestBodyBytes(request2, maxBytes = DEFAULT_SIGNED_BODY_MAX_BYTES, consumeBody = false) {
151817
151849
  if (request2.method === "GET" || request2.method === "HEAD")
@@ -173915,6 +173947,9 @@ function buildRequest2(origin, bridgeRequest) {
173915
173947
  }
173916
173948
  async function handlePsLiteBridgeRequest(runtime, bridgeRequest, options = {}) {
173917
173949
  const request2 = buildRequest2(options.origin ?? "https://ps-lite.local", bridgeRequest);
173950
+ if (options.onReadGrantee) {
173951
+ attachPersonalServerReadGranteeObserver(request2, options.onReadGrantee);
173952
+ }
173918
173953
  const response = await runtime.fetch(request2);
173919
173954
  const bytes2 = new Uint8Array(await response.arrayBuffer());
173920
173955
  return {
@@ -174562,8 +174597,9 @@ var DEFAULT_PUBLIC_SUFFIX2 = "34.16.49.200.sslip.io";
174562
174597
  var DEFAULT_ORIGIN = "https://ps-lite.local";
174563
174598
  var RELAY_CLOSE_SESSION_REPLACED = 1012;
174564
174599
  var HEARTBEAT_LATE_FIRE_FACTOR = 2;
174565
- function psLiteRelayControlUrl(sessionId, controlUrl = DEFAULT_CONTROL_URL) {
174566
- return `${controlUrl.replace(/\/+$/, "")}/browser/${sessionId}`;
174600
+ function psLiteRelayControlUrl(sessionId, controlUrl = DEFAULT_CONTROL_URL, connTag) {
174601
+ const base2 = `${controlUrl.replace(/\/+$/, "")}/browser/${sessionId}`;
174602
+ return connTag ? `${base2}?tag=${encodeURIComponent(connTag)}` : base2;
174567
174603
  }
174568
174604
  function psLiteRelayPublicUrl(sessionId, publicSuffix = DEFAULT_PUBLIC_SUFFIX2) {
174569
174605
  return `https://${sessionId}.${publicSuffix.replace(/^\./, "")}`;
@@ -174603,7 +174639,146 @@ function startPsLiteRelayClient(options) {
174603
174639
  let unansweredPingSentAt;
174604
174640
  let lastHeartbeatCheckAt = 0;
174605
174641
  let heartbeatGraceUsed = false;
174642
+ let connTag = randomConnTag();
174606
174643
  const log = (line) => options.logger?.(line);
174644
+ const emitStatus = (status2, detail) => options.onStatus?.(status2, { ...detail, connTag });
174645
+ const logHookError = (error51) => log(`relay read event hook failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
174646
+ const readQueue = [];
174647
+ let readFlushTimer;
174648
+ let droppedReadEvents = 0;
174649
+ const flushReadEvents = () => {
174650
+ readFlushTimer = void 0;
174651
+ const batch = readQueue.splice(0);
174652
+ const hook = options.onReadEvent;
174653
+ if (closed || !hook)
174654
+ return;
174655
+ if (droppedReadEvents > 0) {
174656
+ log(`relay read events dropped (queue full): ${droppedReadEvents}`);
174657
+ droppedReadEvents = 0;
174658
+ }
174659
+ for (const produce of batch) {
174660
+ void Promise.resolve().then(produce).then((event) => closed ? void 0 : hook(event)).catch(logHookError);
174661
+ }
174662
+ };
174663
+ const dispatchReadEvent = (produce) => {
174664
+ if (closed || !options.onReadEvent)
174665
+ return;
174666
+ if (readQueue.length >= MAX_QUEUED_READ_EVENTS) {
174667
+ readQueue.shift();
174668
+ droppedReadEvents += 1;
174669
+ }
174670
+ readQueue.push(produce);
174671
+ readFlushTimer ??= setTimeout(flushReadEvents, 0);
174672
+ };
174673
+ const stopReadEvents = () => {
174674
+ readQueue.length = 0;
174675
+ if (readFlushTimer !== void 0) {
174676
+ clearTimeout(readFlushTimer);
174677
+ readFlushTimer = void 0;
174678
+ }
174679
+ };
174680
+ const appVisibility = () => {
174681
+ try {
174682
+ const state = options.appVisibility ? options.appVisibility() : documentVisibility();
174683
+ return state === "visible" || state === "hidden" ? state : "unknown";
174684
+ } catch {
174685
+ return "unknown";
174686
+ }
174687
+ };
174688
+ const sampleHidden = (tracker) => {
174689
+ const visibility = appVisibility();
174690
+ if (visibility === "hidden")
174691
+ tracker.hiddenDuring = true;
174692
+ else if (visibility === "visible")
174693
+ tracker.hiddenDuring ??= false;
174694
+ return visibility;
174695
+ };
174696
+ const beginRead = (stream, request2) => {
174697
+ const seq = stream.requestSeq;
174698
+ stream.requestSeq += 1;
174699
+ const scope = relayReadScope(request2);
174700
+ if (scope === void 0)
174701
+ return void 0;
174702
+ const tracker = {
174703
+ ids: {
174704
+ readRef: `${stream.streamRef ?? crypto.randomUUID()}:${seq}`,
174705
+ ...stream.streamRef ? { streamRef: stream.streamRef } : {},
174706
+ connTag: stream.connTag,
174707
+ scope,
174708
+ method: request2.method.toUpperCase()
174709
+ },
174710
+ startedAt: Date.now(),
174711
+ hiddenDuring: void 0,
174712
+ done: false
174713
+ };
174714
+ const visibility = sampleHidden(tracker);
174715
+ stream.activeRead = tracker;
174716
+ const received = {
174717
+ type: "ps_read_received",
174718
+ at: tracker.startedAt,
174719
+ ...tracker.ids,
174720
+ appVisibility: visibility
174721
+ };
174722
+ dispatchReadEvent(() => received);
174723
+ return tracker;
174724
+ };
174725
+ const finishRead = (tracker, result) => {
174726
+ if (!tracker || tracker.done)
174727
+ return;
174728
+ tracker.done = true;
174729
+ sampleHidden(tracker);
174730
+ const finishedAt = Date.now();
174731
+ const durationMs = Math.max(0, finishedAt - tracker.startedAt);
174732
+ const hiddenDuring = tracker.hiddenDuring;
174733
+ const emptyCheckBody = result.body && result.body.length <= EMPTY_CHECK_MAX_BYTES ? result.body : void 0;
174734
+ const { status: status2, contentType, granteeRef: granteeRefPromise } = result;
174735
+ const outcomeOrClassify = result.outcome;
174736
+ const bytesOut = result.bytesOut ?? 0;
174737
+ const gzip = result.gzip ?? false;
174738
+ dispatchReadEvent(async () => {
174739
+ const outcome = outcomeOrClassify === "classify" ? classifyReadOutcome(status2 ?? 0, emptyCheckBody, contentType) : outcomeOrClassify;
174740
+ const granteeRef = validGranteeRef(granteeRefPromise ? await withTimeout3(granteeRefPromise, GRANTEE_REF_WAIT_MS) : void 0);
174741
+ return {
174742
+ type: "ps_read_finished",
174743
+ at: finishedAt,
174744
+ ...tracker.ids,
174745
+ ...granteeRef ? { granteeRef } : {},
174746
+ ...status2 !== void 0 ? { status: status2 } : {},
174747
+ outcome,
174748
+ bytesOut,
174749
+ gzip,
174750
+ durationMs,
174751
+ ...hiddenDuring !== void 0 ? { hiddenDuring } : {}
174752
+ };
174753
+ });
174754
+ };
174755
+ const visibilityDocument = options.appVisibility ? void 0 : readVisibilityDocument();
174756
+ const onVisibilityChange = () => {
174757
+ if (visibilityDocument?.visibilityState !== "hidden")
174758
+ return;
174759
+ for (const stream of streams.values()) {
174760
+ if (stream.activeRead && !stream.activeRead.done) {
174761
+ stream.activeRead.hiddenDuring = true;
174762
+ }
174763
+ }
174764
+ };
174765
+ let visibilityListening = false;
174766
+ if (visibilityDocument) {
174767
+ try {
174768
+ visibilityDocument.addEventListener("visibilitychange", onVisibilityChange);
174769
+ visibilityListening = true;
174770
+ } catch {
174771
+ }
174772
+ }
174773
+ const stopVisibilityListener = () => {
174774
+ if (!visibilityListening || !visibilityDocument)
174775
+ return;
174776
+ visibilityListening = false;
174777
+ try {
174778
+ visibilityDocument.removeEventListener("visibilitychange", onVisibilityChange);
174779
+ } catch {
174780
+ }
174781
+ };
174607
174782
  const isLive = (stream) => !closed && !stream.closed && stream.socket === socket && socket.readyState === socket.OPEN && streams.get(stream.streamId) === stream;
174608
174783
  const sendText = (message, target = socket) => {
174609
174784
  if (!closed && target === socket && target.readyState === target.OPEN) {
@@ -174619,6 +174794,7 @@ function startPsLiteRelayClient(options) {
174619
174794
  if (stream.closed)
174620
174795
  return;
174621
174796
  stream.closed = true;
174797
+ finishRead(stream.activeRead, { outcome: "stream_closed" });
174622
174798
  if (streams.get(stream.streamId) === stream) {
174623
174799
  streams.delete(stream.streamId);
174624
174800
  pendingStreamData.delete(stream.streamId);
@@ -174640,17 +174816,17 @@ function startPsLiteRelayClient(options) {
174640
174816
  };
174641
174817
  const writePlaintextResponse = (stream, responseBytes) => {
174642
174818
  if (!isLive(stream))
174643
- return;
174819
+ return false;
174644
174820
  if (!stream.tls) {
174645
174821
  for (const chunk of chunks(responseBytes, RESPONSE_CHUNK_BYTES)) {
174646
174822
  sendData(stream, chunk);
174647
174823
  }
174648
- return;
174824
+ return isLive(stream);
174649
174825
  }
174650
174826
  const responseChunks = chunks(responseBytes, RESPONSE_CHUNK_BYTES);
174651
174827
  for (let index2 = 0; index2 < responseChunks.length; index2 += 1) {
174652
174828
  if (!isLive(stream))
174653
- return;
174829
+ return false;
174654
174830
  const step = stream.tls.writePlaintext(responseChunks[index2], index2 === responseChunks.length - 1);
174655
174831
  for (const chunk of chunks(step.tls, RESPONSE_CHUNK_BYTES)) {
174656
174832
  if (chunk.length > 0) {
@@ -174658,6 +174834,7 @@ function startPsLiteRelayClient(options) {
174658
174834
  }
174659
174835
  }
174660
174836
  }
174837
+ return isLive(stream);
174661
174838
  };
174662
174839
  const drainHttpRequests = async (stream) => {
174663
174840
  while (isLive(stream)) {
@@ -174666,10 +174843,38 @@ function startPsLiteRelayClient(options) {
174666
174843
  return;
174667
174844
  }
174668
174845
  stream.plaintext = stream.plaintext.slice(parsed.consumedBytes);
174669
- const response = await handleRelayHttpRequest(options.runtime, parsed.request, origin);
174670
- if (!isLive(stream))
174846
+ const read = beginRead(stream, parsed.request);
174847
+ let relayed;
174848
+ try {
174849
+ relayed = await handleRelayHttpRequest(options.runtime, parsed.request, origin, Boolean(read));
174850
+ } catch (error51) {
174851
+ finishRead(read, { outcome: "handler_error" });
174852
+ throw error51;
174853
+ }
174854
+ const { response } = relayed;
174855
+ const finish = (outcome) => ({
174856
+ outcome,
174857
+ status: relayed.status,
174858
+ granteeRef: relayed.granteeRef,
174859
+ bytesOut: response.length,
174860
+ gzip: relayed.gzip
174861
+ });
174862
+ if (!isLive(stream)) {
174863
+ finishRead(read, { ...finish("stream_closed"), bytesOut: 0 });
174671
174864
  return;
174672
- writePlaintextResponse(stream, binaryToBytes(response));
174865
+ }
174866
+ let written;
174867
+ try {
174868
+ written = writePlaintextResponse(stream, binaryToBytes(response));
174869
+ } catch (error51) {
174870
+ finishRead(read, { ...finish("tls_error"), bytesOut: 0 });
174871
+ throw error51;
174872
+ }
174873
+ finishRead(read, written ? {
174874
+ ...finish("classify"),
174875
+ body: relayed.plainBody,
174876
+ contentType: relayed.contentType
174877
+ } : finish("stream_closed"));
174673
174878
  closeStream(stream, "http_response_sent");
174674
174879
  return;
174675
174880
  }
@@ -174707,7 +174912,7 @@ function startPsLiteRelayClient(options) {
174707
174912
  closeStream(stream, "tls_error");
174708
174913
  }
174709
174914
  };
174710
- const openStream = async (streamId) => {
174915
+ const openStream = async (streamId, streamRef) => {
174711
174916
  const previous = streams.get(streamId);
174712
174917
  if (previous)
174713
174918
  disposeStream(previous);
@@ -174717,7 +174922,10 @@ function startPsLiteRelayClient(options) {
174717
174922
  socket,
174718
174923
  closed: false,
174719
174924
  ready: !tls,
174720
- handling: false
174925
+ handling: false,
174926
+ ...streamRef ? { streamRef } : {},
174927
+ connTag,
174928
+ requestSeq: 0
174721
174929
  };
174722
174930
  streams.set(streamId, stream);
174723
174931
  try {
@@ -174750,7 +174958,7 @@ function startPsLiteRelayClient(options) {
174750
174958
  const handleControl = (message) => {
174751
174959
  if (message.type === "session.ready") {
174752
174960
  issueToken = message.issueToken;
174753
- options.onStatus?.("connected");
174961
+ emitStatus("connected");
174754
174962
  if (tls && tls.prepare) {
174755
174963
  void tls.prepare({
174756
174964
  sessionId: options.sessionId,
@@ -174765,7 +174973,7 @@ function startPsLiteRelayClient(options) {
174765
174973
  return;
174766
174974
  }
174767
174975
  if (message.type === "stream.open" && typeof message.streamId === "number") {
174768
- void openStream(message.streamId);
174976
+ void openStream(message.streamId, parseStreamRef(message.streamRef));
174769
174977
  return;
174770
174978
  }
174771
174979
  if (message.type === "stream.close" && typeof message.streamId === "number") {
@@ -174847,18 +175055,19 @@ function startPsLiteRelayClient(options) {
174847
175055
  return;
174848
175056
  }
174849
175057
  issueToken = void 0;
174850
- socket = createSocket(options.webSocketFactory, controlUrl);
175058
+ connTag = randomConnTag();
175059
+ socket = createSocket(options.webSocketFactory, psLiteRelayControlUrl(options.sessionId, baseControlUrl, connTag));
174851
175060
  const currentSocket = socket;
174852
175061
  const isCurrent = () => !closed && socket === currentSocket;
174853
175062
  const acceptsFrames = () => isCurrent() && currentSocket.readyState === currentSocket.OPEN;
174854
175063
  socket.binaryType = "arraybuffer";
174855
- options.onStatus?.("connecting");
175064
+ emitStatus("connecting");
174856
175065
  socket.onopen = () => {
174857
175066
  if (!acceptsFrames())
174858
175067
  return;
174859
175068
  reconnectAttempts = 0;
174860
175069
  startHeartbeat();
174861
- options.onStatus?.("connected");
175070
+ emitStatus("connected");
174862
175071
  };
174863
175072
  socket.onmessage = (event) => {
174864
175073
  if (!acceptsFrames())
@@ -174878,27 +175087,31 @@ function startPsLiteRelayClient(options) {
174878
175087
  issueToken = void 0;
174879
175088
  const detail = { code: event?.code, reason: event?.reason };
174880
175089
  if (closed) {
174881
- options.onStatus?.("closed", detail);
175090
+ stopVisibilityListener();
175091
+ stopReadEvents();
175092
+ emitStatus("closed", detail);
174882
175093
  return;
174883
175094
  }
174884
175095
  if (event?.code === RELAY_CLOSE_SESSION_REPLACED) {
174885
175096
  closed = true;
175097
+ stopVisibilityListener();
175098
+ stopReadEvents();
174886
175099
  if (reconnectTimer !== void 0) {
174887
175100
  clearTimeout(reconnectTimer);
174888
175101
  reconnectTimer = void 0;
174889
175102
  }
174890
175103
  log("relay session replaced by another connection \u2014 not reconnecting");
174891
- options.onStatus?.("replaced", detail);
175104
+ emitStatus("replaced", detail);
174892
175105
  return;
174893
175106
  }
174894
- options.onStatus?.("disconnected", detail);
175107
+ emitStatus("disconnected", detail);
174895
175108
  scheduleReconnect();
174896
175109
  };
174897
175110
  socket.onerror = () => {
174898
175111
  if (!isCurrent() || currentSocket.readyState > currentSocket.OPEN) {
174899
175112
  return;
174900
175113
  }
174901
- options.onStatus?.("error");
175114
+ emitStatus("error");
174902
175115
  };
174903
175116
  }
174904
175117
  connect();
@@ -174925,6 +175138,8 @@ function startPsLiteRelayClient(options) {
174925
175138
  return;
174926
175139
  closed = true;
174927
175140
  stopHeartbeat();
175141
+ stopVisibilityListener();
175142
+ stopReadEvents();
174928
175143
  if (reconnectTimer !== void 0) {
174929
175144
  clearTimeout(reconnectTimer);
174930
175145
  reconnectTimer = void 0;
@@ -174934,7 +175149,7 @@ function startPsLiteRelayClient(options) {
174934
175149
  }
174935
175150
  };
174936
175151
  }
174937
- async function handleRelayHttpRequest(runtime, request2, origin) {
175152
+ async function handleRelayHttpRequest(runtime, request2, origin, isRead) {
174938
175153
  const bridgeRequest = {
174939
175154
  requestId: crypto.randomUUID(),
174940
175155
  method: request2.method,
@@ -174943,20 +175158,164 @@ async function handleRelayHttpRequest(runtime, request2, origin) {
174943
175158
  headers: request2.headers,
174944
175159
  body: encodeBase642(request2.body)
174945
175160
  };
175161
+ let granteeRef;
174946
175162
  const bridgeResponse = await handlePsLiteBridgeRequest(runtime, bridgeRequest, {
174947
- origin
175163
+ origin,
175164
+ ...isRead ? {
175165
+ onReadGrantee: (ref) => {
175166
+ granteeRef = ref;
175167
+ }
175168
+ } : {}
174948
175169
  });
175170
+ const contentType = headerValue(bridgeResponse.headers, "content-type");
174949
175171
  const bodyBytes = decodeBase643(bridgeResponse.body);
174950
175172
  const acceptsGzip = (request2.headers["accept-encoding"] ?? "").includes("gzip");
174951
175173
  const alreadyEncoded = Boolean(bridgeResponse.headers?.["content-encoding"]);
175174
+ const base2 = {
175175
+ status: bridgeResponse.status,
175176
+ plainBody: bodyBytes,
175177
+ ...contentType !== void 0 ? { contentType } : {},
175178
+ ...granteeRef ? { granteeRef } : {}
175179
+ };
174952
175180
  if (acceptsGzip && !alreadyEncoded && bodyBytes.length >= GZIP_MIN_BYTES && typeof CompressionStream !== "undefined") {
174953
175181
  const gzipped = await gzipBytes(bodyBytes);
174954
- return buildHttpResponse(bridgeResponse, {
174955
- bodyOverride: gzipped,
174956
- contentEncoding: "gzip"
174957
- });
175182
+ return {
175183
+ ...base2,
175184
+ gzip: true,
175185
+ response: buildHttpResponse(bridgeResponse, {
175186
+ bodyOverride: gzipped,
175187
+ contentEncoding: "gzip"
175188
+ })
175189
+ };
175190
+ }
175191
+ return {
175192
+ ...base2,
175193
+ gzip: false,
175194
+ response: buildHttpResponse(bridgeResponse, { bodyOverride: bodyBytes })
175195
+ };
175196
+ }
175197
+ function headerValue(headers, name) {
175198
+ for (const [key, value] of Object.entries(headers)) {
175199
+ if (key.toLowerCase() === name)
175200
+ return value;
175201
+ }
175202
+ return void 0;
175203
+ }
175204
+ var DATA_PATH_PREFIX = "/v1/data/";
175205
+ function relayReadScope(request2) {
175206
+ const method = request2.method.toUpperCase();
175207
+ if (method !== "GET")
175208
+ return void 0;
175209
+ if (!request2.path.startsWith(DATA_PATH_PREFIX))
175210
+ return void 0;
175211
+ const rest = request2.path.slice(DATA_PATH_PREFIX.length).split("?")[0];
175212
+ if (!rest || rest.includes("/"))
175213
+ return void 0;
175214
+ const segment = rest;
175215
+ let scope;
175216
+ try {
175217
+ scope = decodeURIComponent(segment);
175218
+ } catch {
175219
+ return INVALID_SCOPE;
174958
175220
  }
174959
- return buildHttpResponse(bridgeResponse, { bodyOverride: bodyBytes });
175221
+ if (scope.length > MAX_SCOPE_LENGTH)
175222
+ return INVALID_SCOPE;
175223
+ const parsed = parseDataScopeContract(scope);
175224
+ return parsed.ok ? parsed.scope : INVALID_SCOPE;
175225
+ }
175226
+ var INVALID_SCOPE = "invalid";
175227
+ var MAX_SCOPE_LENGTH = 128;
175228
+ var GRANTEE_REF_WAIT_MS = 1e3;
175229
+ var MAX_QUEUED_READ_EVENTS = 200;
175230
+ function validGranteeRef(value) {
175231
+ return typeof value === "string" && /^(owner|[0-9a-f]{12})$/.test(value) ? value : void 0;
175232
+ }
175233
+ async function withTimeout3(promise2, ms3) {
175234
+ let timer;
175235
+ try {
175236
+ return await Promise.race([
175237
+ promise2.catch(() => void 0),
175238
+ new Promise((resolve) => {
175239
+ timer = setTimeout(() => resolve(void 0), ms3);
175240
+ })
175241
+ ]);
175242
+ } finally {
175243
+ if (timer !== void 0)
175244
+ clearTimeout(timer);
175245
+ }
175246
+ }
175247
+ var EMPTY_CHECK_MAX_BYTES = 64 * 1024;
175248
+ function classifyReadOutcome(status2, body, contentType) {
175249
+ if (status2 === 401 || status2 === 403)
175250
+ return "denied";
175251
+ if (status2 === 402)
175252
+ return "payment_required";
175253
+ if (status2 === 404)
175254
+ return "not_found";
175255
+ if (status2 >= 400)
175256
+ return "error";
175257
+ if (status2 < 200 || status2 >= 300)
175258
+ return "ok";
175259
+ return body && isEmptyJsonBody(body, contentType) ? "empty" : "ok";
175260
+ }
175261
+ function isEmptyJsonBody(body, contentType) {
175262
+ if (body.length === 0 || body.length > EMPTY_CHECK_MAX_BYTES)
175263
+ return false;
175264
+ if (contentType !== void 0 && !/json/i.test(contentType))
175265
+ return false;
175266
+ let value;
175267
+ try {
175268
+ value = JSON.parse(new TextDecoder().decode(body));
175269
+ } catch {
175270
+ return false;
175271
+ }
175272
+ if (isEmptyValue(value))
175273
+ return true;
175274
+ if (value && typeof value === "object" && !Array.isArray(value)) {
175275
+ const record3 = value;
175276
+ if (!("data" in record3))
175277
+ return false;
175278
+ const data = record3.data;
175279
+ if (isEmptyValue(data))
175280
+ return true;
175281
+ if (data && typeof data === "object" && !Array.isArray(data)) {
175282
+ const values = Object.values(data);
175283
+ return values.length > 0 && values.every(isEmptyValue);
175284
+ }
175285
+ }
175286
+ return false;
175287
+ }
175288
+ function isEmptyValue(value) {
175289
+ if (value === null || value === void 0 || value === "")
175290
+ return true;
175291
+ if (Array.isArray(value))
175292
+ return value.length === 0;
175293
+ if (typeof value === "object") {
175294
+ return Object.keys(value).length === 0;
175295
+ }
175296
+ return false;
175297
+ }
175298
+ function parseStreamRef(value) {
175299
+ return typeof value === "string" && /^[A-Za-z0-9-]{1,64}$/.test(value) ? value : void 0;
175300
+ }
175301
+ function randomConnTag() {
175302
+ const bytes2 = new Uint8Array(8);
175303
+ crypto.getRandomValues(bytes2);
175304
+ let hex4 = "";
175305
+ for (const byte of bytes2)
175306
+ hex4 += byte.toString(16).padStart(2, "0");
175307
+ return hex4;
175308
+ }
175309
+ function readVisibilityDocument() {
175310
+ const candidate = globalThis.document;
175311
+ if (!candidate || typeof candidate.addEventListener !== "function" || typeof candidate.removeEventListener !== "function") {
175312
+ return void 0;
175313
+ }
175314
+ return candidate;
175315
+ }
175316
+ function documentVisibility() {
175317
+ const state = globalThis.document?.visibilityState;
175318
+ return state === "visible" || state === "hidden" ? state : "unknown";
174960
175319
  }
174961
175320
  var GZIP_MIN_BYTES = 1024;
174962
175321
  async function gzipBytes(input) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendatalabs/personal-server-ts-server",
3
- "version": "1.35.5",
3
+ "version": "1.36.0",
4
4
  "description": "Hono HTTP server for the Vana Personal Server — routes, middleware, composition root",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -55,8 +55,8 @@
55
55
  },
56
56
  "dependencies": {
57
57
  "@hono/node-server": "^1.19.17",
58
- "@opendatalabs/personal-server-ts-core": "1.35.5",
59
- "@opendatalabs/personal-server-ts-lite": "1.35.5",
58
+ "@opendatalabs/personal-server-ts-core": "1.36.0",
59
+ "@opendatalabs/personal-server-ts-lite": "1.36.0",
60
60
  "@opendatalabs/vana-sdk": "3.23.0-pr.211.36da1d8",
61
61
  "ajv": "^8.20.0",
62
62
  "better-sqlite3": "^12.11.1",