sitepong 0.2.10 → 0.2.12

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.
@@ -732,6 +732,12 @@ interface WatchtowerConfig {
732
732
  * you give the SDK's identify() so users line up across pipelines.
733
733
  */
734
734
  distinctId?: string;
735
+ /**
736
+ * Idle grace in ms: reopening the app within this window of backgrounding
737
+ * resumes the same session; beyond it, a new (linked) session starts.
738
+ * Default 30_000 (30s). Lower it in tests to exercise session rolling.
739
+ */
740
+ sessionGraceMs?: number;
735
741
  }
736
742
  /**
737
743
  * Start the shared native capture engine for React Native. Idempotent.
@@ -3430,33 +3430,35 @@ function attrDiff(id, prev, next) {
3430
3430
  }
3431
3431
  function diff(prev, next, mirror) {
3432
3432
  const { next: nextIndex, order } = indexTree(next, mirror);
3433
- const messages = [];
3434
- const removed = /* @__PURE__ */ new Set();
3435
- for (const id of prev.keys()) if (!nextIndex.has(id)) removed.add(id);
3436
- for (const id of removed) {
3437
- const rec = prev.get(id);
3438
- if (!removed.has(rec.parentId)) messages.push(RemoveNode(id));
3439
- }
3433
+ const creates = [];
3434
+ const updates = [];
3440
3435
  for (const id of order) {
3441
3436
  const rec = nextIndex.get(id);
3442
3437
  const before = prev.get(id);
3443
3438
  if (!before) {
3444
3439
  if (rec.tag === "#text") {
3445
- messages.push(CreateTextNode(id, rec.parentId, rec.index));
3440
+ creates.push(CreateTextNode(id, rec.parentId, rec.index));
3446
3441
  } else {
3447
- messages.push(CreateElementNode(id, rec.parentId, rec.index, rec.tag));
3442
+ creates.push(CreateElementNode(id, rec.parentId, rec.index, rec.tag));
3448
3443
  }
3449
- for (const k of Object.keys(rec.attrs)) messages.push(SetNodeAttribute(id, k, rec.attrs[k]));
3450
- if (rec.text !== void 0 && rec.text !== "") messages.push(SetNodeData(id, rec.text));
3444
+ for (const k of Object.keys(rec.attrs)) creates.push(SetNodeAttribute(id, k, rec.attrs[k]));
3445
+ if (rec.text !== void 0 && rec.text !== "") creates.push(SetNodeData(id, rec.text));
3451
3446
  } else {
3452
3447
  if (before.parentId !== rec.parentId || before.index !== rec.index) {
3453
- messages.push(MoveNode(id, rec.parentId, rec.index));
3448
+ updates.push(MoveNode(id, rec.parentId, rec.index));
3454
3449
  }
3455
- messages.push(...attrDiff(id, before.attrs, rec.attrs));
3456
- if ((before.text ?? "") !== (rec.text ?? "")) messages.push(SetNodeData(id, rec.text ?? ""));
3450
+ updates.push(...attrDiff(id, before.attrs, rec.attrs));
3451
+ if ((before.text ?? "") !== (rec.text ?? "")) updates.push(SetNodeData(id, rec.text ?? ""));
3457
3452
  }
3458
3453
  }
3459
- return { messages, index: nextIndex };
3454
+ const removed = /* @__PURE__ */ new Set();
3455
+ for (const id of prev.keys()) if (!nextIndex.has(id)) removed.add(id);
3456
+ const removes = [];
3457
+ for (const id of removed) {
3458
+ const rec = prev.get(id);
3459
+ if (!removed.has(rec.parentId)) removes.push(RemoveNode(id));
3460
+ }
3461
+ return { messages: [...creates, ...updates, ...removes], index: nextIndex };
3460
3462
  }
3461
3463
  function stableAnchor(step) {
3462
3464
  if (step.attrs.testID) return `@${step.attrs.testID}`;
@@ -3578,6 +3580,21 @@ var Recorder = class {
3578
3580
  emit(t, m) {
3579
3581
  this.stream.push({ t, m });
3580
3582
  }
3583
+ /**
3584
+ * Reinitialize for a brand-new session — fresh id space, empty tree/stream,
3585
+ * no pending outcome windows. The capture engine calls this when it rolls to
3586
+ * a new session (app reopened after the idle grace) so the new stream begins
3587
+ * with a clean full snapshot rather than diffing against the prior session's
3588
+ * tree (which would leave the new session missing its initial creates).
3589
+ */
3590
+ reset() {
3591
+ this.mirror = new Mirror();
3592
+ this.index = emptyIndex();
3593
+ this.analyzer = new InteractionAnalyzer();
3594
+ this.stream = [];
3595
+ this.pending = [];
3596
+ this.viewport = { w: 0, h: 0 };
3597
+ }
3581
3598
  setViewport(w, h, t) {
3582
3599
  this.viewport = { w, h };
3583
3600
  this.emit(t, [5, w, h]);
@@ -3991,6 +4008,7 @@ var StructuralCapture = class {
3991
4008
  this.scrollOffsets.clear();
3992
4009
  this.overloadSkip = 0;
3993
4010
  this.explicitRoot = null;
4011
+ this.recorder.reset();
3994
4012
  const { width, height } = reactNative.Dimensions.get("window");
3995
4013
  this.recorder.setViewport(Math.round(width), Math.round(height), 0);
3996
4014
  this.sampleTimer = setInterval(() => this.sample(), cfg.sampleIntervalMs ?? 120);
@@ -4718,7 +4736,8 @@ function startWatchtower(config) {
4718
4736
  projectId: config.projectId,
4719
4737
  endpoint: config.endpoint,
4720
4738
  sampleRate: config.sampleRate ?? 0.1,
4721
- platform: config.platform ?? "react-native-ios"
4739
+ platform: config.platform ?? "react-native-ios",
4740
+ sessionGraceMs: config.sessionGraceMs ?? 3e4
4722
4741
  });
4723
4742
  started = true;
4724
4743
  if (config.distinctId) setWatchtowerUser(config.distinctId);
@@ -4776,9 +4795,60 @@ function StructuralCaptureProvider({ children, ...cfg }) {
4776
4795
  cfgRef.current = cfg;
4777
4796
  const rootRef = React2.useRef(null);
4778
4797
  React2.useEffect(() => {
4779
- structuralCapture.start(cfgRef.current);
4780
- structuralCapture.setRootInstance(rootRef.current);
4798
+ let cancelled = false;
4799
+ let poll = null;
4800
+ let adoptTimer = null;
4801
+ let currentSid;
4802
+ let readopting = false;
4803
+ const begin = (sessionId) => {
4804
+ if (cancelled) return;
4805
+ currentSid = sessionId;
4806
+ structuralCapture.start({ ...cfgRef.current, sessionId });
4807
+ structuralCapture.setRootInstance(rootRef.current);
4808
+ };
4809
+ const readopt = async (sessionId) => {
4810
+ if (cancelled || readopting) return;
4811
+ readopting = true;
4812
+ try {
4813
+ await structuralCapture.stop();
4814
+ begin(sessionId);
4815
+ } finally {
4816
+ readopting = false;
4817
+ }
4818
+ };
4819
+ const watchForRoll = () => {
4820
+ adoptTimer = setInterval(() => {
4821
+ if (cancelled || readopting) return;
4822
+ const wt = getWatchtowerSessionId();
4823
+ if (wt && wt !== currentSid) void readopt(wt);
4824
+ }, 1e3);
4825
+ };
4826
+ const explicit = cfgRef.current.sessionId;
4827
+ if (explicit) {
4828
+ begin(explicit);
4829
+ } else {
4830
+ let tries = 0;
4831
+ const resolve = () => {
4832
+ if (cancelled) return;
4833
+ const wt = getWatchtowerSessionId();
4834
+ if (wt) {
4835
+ begin(wt);
4836
+ watchForRoll();
4837
+ return;
4838
+ }
4839
+ if (++tries >= 25) {
4840
+ begin(void 0);
4841
+ watchForRoll();
4842
+ return;
4843
+ }
4844
+ poll = setTimeout(resolve, 200);
4845
+ };
4846
+ resolve();
4847
+ }
4781
4848
  return () => {
4849
+ cancelled = true;
4850
+ if (poll) clearTimeout(poll);
4851
+ if (adoptTimer) clearInterval(adoptTimer);
4782
4852
  void structuralCapture.stop();
4783
4853
  };
4784
4854
  }, []);