@goliapkg/sentori-react-native 0.8.1 → 0.8.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAoD,IAAI,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5F,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAgB5D,eAAO,MAAM,+BAA+B,QAAO,IAElD,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,IAAI,GAAG,IAAI,KAAG,IAE3C,CAAC;AAEF,eAAO,MAAM,OAAO,QAAO,IAAI,GAAG,IAAa,CAAC;AAEhD,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;;;qDAGiD;IACjD,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAAU,OAAO;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,KAAG,OAAO,CAAC,IAAI,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,IAAI,GAAG,MAAM,CAAA;CAAE,CAKxD,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,OAAO,KAAK,EAAE,SAAS,aAAa,KAAG,IA6DnE,CAAC;AAoCF,eAAO,MAAM,gBAAgB,UAjGO,KAAK,WAAW,aAAa,KAAG,IAiGxB,CAAC"}
1
+ {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAoD,IAAI,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5F,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAgB5D,eAAO,MAAM,+BAA+B,QAAO,IAElD,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,IAAI,GAAG,IAAI,KAAG,IAE3C,CAAC;AAEF,eAAO,MAAM,OAAO,QAAO,IAAI,GAAG,IAAa,CAAC;AAEhD,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;;;qDAGiD;IACjD,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAAU,OAAO;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,KAAG,OAAO,CAAC,IAAI,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,IAAI,GAAG,MAAM,CAAA;CAAE,CAKxD,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,OAAO,KAAK,EAAE,SAAS,aAAa,KAAG,IAqEnE,CAAC;AAiEF,eAAO,MAAM,gBAAgB,UAtIO,KAAK,WAAW,aAAa,KAAG,IAsIxB,CAAC"}
package/lib/capture.js CHANGED
@@ -3,6 +3,7 @@ import { addBreadcrumb, getBreadcrumbs } from './breadcrumbs';
3
3
  import { getBundleInfo } from './bundle-info';
4
4
  import { getConfig, isInitialized } from './config';
5
5
  import { getFeatureFlagSnapshot } from './feature-flags';
6
+ import { clearStateSnapshots, getStateSnapshots } from './state-snapshots';
6
7
  import { symbolicateErrorViaMetro } from './handlers/dev-symbolicate';
7
8
  import { captureScreenshot } from './handlers/screenshot';
8
9
  import { markSessionErrored } from './session-tracker';
@@ -112,10 +113,39 @@ export const captureError = (error, extras) => {
112
113
  if (config.sessionTrailEnabled && trail.size() > 0) {
113
114
  await captureAndAttachSessionTrail(event);
114
115
  }
116
+ // v0.9.2 +S2 — state time-travel attachment. Only if anything has
117
+ // been bound or recorded; cleared on success so the next crash's
118
+ // ring doesn't carry stale entries.
119
+ const stateSnapshots = getStateSnapshots();
120
+ if (stateSnapshots.length > 0) {
121
+ await captureAndAttachStateSnapshots(event, stateSnapshots);
122
+ clearStateSnapshots();
123
+ }
115
124
  enqueue(event);
116
125
  };
117
126
  void pipeline();
118
127
  };
128
+ /** v0.9.2 +S2 — upload the rolling state-snapshot ring as a
129
+ * `stateSnapshot` attachment so the dashboard time-travel viewer can
130
+ * scrub through diffs alongside the breadcrumb timeline. */
131
+ async function captureAndAttachStateSnapshots(event, snapshots) {
132
+ try {
133
+ const payload = JSON.stringify({ snapshots });
134
+ const base64 = typeof globalThis.btoa === 'function'
135
+ ? globalThis.btoa(payload)
136
+ : // Bun / node fallback
137
+ Buffer.from(payload, 'utf8').toString('base64');
138
+ const meta = await uploadAttachment(event.id, 'stateSnapshot', { base64, mediaType: 'application/json' }, { source: 'js' });
139
+ if (meta) {
140
+ if (!event.attachments)
141
+ event.attachments = [];
142
+ event.attachments.push(meta);
143
+ }
144
+ }
145
+ catch {
146
+ // best-effort
147
+ }
148
+ }
119
149
  /**
120
150
  * Phase 46 — seal the trail buffer, upload it as a `sessionTrail`
121
151
  * attachment, attach the ref. Best-effort: any failure leaves a
@@ -1 +1 @@
1
- {"version":3,"file":"capture.js","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"capture.js","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import { ErrorBoundary } from './error-boundary';
2
2
  import { type FeedbackButtonHandle, type FeedbackButtonProps } from './feedback-widget';
3
3
  import { clearMaskQuery, registerMaskQuery } from './mask';
4
4
  import { measureFn } from './measure';
5
+ import { bindState, recordState, unbindState } from './state-snapshots';
5
6
  import { startMoment } from '@goliapkg/sentori-core';
6
7
  import { flushMetrics, recordMetric } from './metrics';
7
8
  import { RageTapCapture } from './rage-tap';
@@ -27,6 +28,9 @@ export declare const sentori: {
27
28
  flushMetrics: typeof flushMetrics;
28
29
  measureFn: typeof measureFn;
29
30
  startMoment: typeof startMoment;
31
+ bindState: typeof bindState;
32
+ recordState: typeof recordState;
33
+ unbindState: typeof unbindState;
30
34
  setFeatureFlag: (name: string, value: string) => void;
31
35
  clearFeatureFlag: (name: string) => void;
32
36
  clearAllFeatureFlags: () => void;
@@ -51,6 +55,7 @@ export { clearMaskQuery, registerMaskQuery } from './mask';
51
55
  export { flushMetrics, recordMetric } from './metrics';
52
56
  export { measureFn } from './measure';
53
57
  export { MomentHandle, type MomentProperties, startMoment } from '@goliapkg/sentori-core';
58
+ export { bindState, recordState, type StateSnapshot, unbindState, } from './state-snapshots';
54
59
  export { RageTapCapture } from './rage-tap';
55
60
  export { startAnrWatchdog, stopAnrWatchdog, triggerNativeCrash, } from './native';
56
61
  export { endSession, markSessionCrashed, startSession, } from './session-tracker';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAkB,KAAK,oBAAoB,EAAE,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAOxG,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAO5C,eAAO,MAAM,OAAO;;;;;;;;;;aA8E+a,CAAC;eAAmB,CAAC;YAAgB,CAAC;;;;;;;;;;;;;;;;;;;;;;CArDxe,CAAC;AAEF,eAAe,OAAO,CAAC;AAEvB,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,OAAO,EACP,gBAAgB,EAChB,OAAO,GACR,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,KAAK,oBAAoB,EAAE,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxG,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,KAAK,gBAAgB,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,kBAAkB,GACnB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,YAAY,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,KAAK,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAE1E,YAAY,EACV,KAAK,EACL,YAAY,EACZ,KAAK,EACL,UAAU,EACV,cAAc,EACd,MAAM,EACN,QAAQ,EACR,GAAG,EACH,IAAI,EACJ,IAAI,EACJ,SAAS,EACT,QAAQ,GACT,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAkB,KAAK,oBAAoB,EAAE,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAOxG,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAO5C,eAAO,MAAM,OAAO;;;;;;;;;;aAuFiS,CAAC;eAAmB,CAAC;YAAgB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;CA3D1V,CAAC;AAEF,eAAe,OAAO,CAAC;AAEvB,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,OAAO,EACP,gBAAgB,EAChB,OAAO,GACR,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,KAAK,oBAAoB,EAAE,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxG,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,KAAK,gBAAgB,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,EACL,SAAS,EACT,WAAW,EACX,KAAK,aAAa,EAClB,WAAW,GACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,kBAAkB,GACnB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,YAAY,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,KAAK,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAE1E,YAAY,EACV,KAAK,EACL,YAAY,EACZ,KAAK,EACL,UAAU,EACV,cAAc,EACd,MAAM,EACN,QAAQ,EACR,GAAG,EACH,IAAI,EACJ,IAAI,EACJ,SAAS,EACT,QAAQ,GACT,MAAM,SAAS,CAAC"}
package/lib/index.js CHANGED
@@ -6,6 +6,7 @@ import { FeedbackButton } from './feedback-widget';
6
6
  import { clearAllFeatureFlags, clearFeatureFlag, getFeatureFlags, setFeatureFlag, } from './feature-flags';
7
7
  import { clearMaskQuery, registerMaskQuery } from './mask';
8
8
  import { measureFn } from './measure';
9
+ import { bindState, recordState, unbindState } from './state-snapshots';
9
10
  import { startMoment } from '@goliapkg/sentori-core';
10
11
  import { flushMetrics, recordMetric } from './metrics';
11
12
  import { RageTapCapture } from './rage-tap';
@@ -23,6 +24,9 @@ export const sentori = {
23
24
  flushMetrics,
24
25
  measureFn,
25
26
  startMoment,
27
+ bindState,
28
+ recordState,
29
+ unbindState,
26
30
  setFeatureFlag,
27
31
  clearFeatureFlag,
28
32
  clearAllFeatureFlags,
@@ -47,6 +51,7 @@ export { clearMaskQuery, registerMaskQuery } from './mask';
47
51
  export { flushMetrics, recordMetric } from './metrics';
48
52
  export { measureFn } from './measure';
49
53
  export { MomentHandle, startMoment } from '@goliapkg/sentori-core';
54
+ export { bindState, recordState, unbindState, } from './state-snapshots';
50
55
  export { RageTapCapture } from './rage-tap';
51
56
  export { startAnrWatchdog, stopAnrWatchdog, triggerNativeCrash, } from './native';
52
57
  export { endSession, markSessionCrashed, startSession, } from './session-tracker';
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,OAAO,EACP,gBAAgB,EAChB,OAAO,GACR,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAuD,MAAM,mBAAmB,CAAC;AACxG,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,YAAY,GACb,MAAM,mBAAmB,CAAC;AAE3B,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,IAAI;IACJ,aAAa;IACb,OAAO;IACP,OAAO;IACP,YAAY;IACZ,gBAAgB;IAChB,WAAW;IACX,gBAAgB;IAChB,YAAY;IACZ,YAAY;IACZ,SAAS;IACT,WAAW;IACX,cAAc;IACd,gBAAgB;IAChB,oBAAoB;IACpB,eAAe;IACf,aAAa;IACb,cAAc;IACd,cAAc;IACd,iBAAiB;IACjB,cAAc;IACd,YAAY;IACZ,UAAU;IACV,kBAAkB;CACnB,CAAC;AAEF,eAAe,OAAO,CAAC;AAEvB,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,OAAO,EACP,gBAAgB,EAChB,OAAO,GACR,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAuD,MAAM,mBAAmB,CAAC;AACxG,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,YAAY,EAAyB,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,kBAAkB,GACnB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,YAAY,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAA0B,kBAAkB,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,OAAO,EACP,gBAAgB,EAChB,OAAO,GACR,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAuD,MAAM,mBAAmB,CAAC;AACxG,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,YAAY,GACb,MAAM,mBAAmB,CAAC;AAE3B,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,IAAI;IACJ,aAAa;IACb,OAAO;IACP,OAAO;IACP,YAAY;IACZ,gBAAgB;IAChB,WAAW;IACX,gBAAgB;IAChB,YAAY;IACZ,YAAY;IACZ,SAAS;IACT,WAAW;IACX,SAAS;IACT,WAAW;IACX,WAAW;IACX,cAAc;IACd,gBAAgB;IAChB,oBAAoB;IACpB,eAAe;IACf,aAAa;IACb,cAAc;IACd,cAAc;IACd,iBAAiB;IACjB,cAAc;IACd,YAAY;IACZ,UAAU;IACV,kBAAkB;CACnB,CAAC;AAEF,eAAe,OAAO,CAAC;AAEvB,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,OAAO,EACP,gBAAgB,EAChB,OAAO,GACR,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAuD,MAAM,mBAAmB,CAAC;AACxG,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,YAAY,EAAyB,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,EACL,SAAS,EACT,WAAW,EAEX,WAAW,GACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,kBAAkB,GACnB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,YAAY,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAA0B,kBAAkB,EAAE,MAAM,cAAc,CAAC"}
package/lib/init.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type PreCrashChannel } from './pre-crash-sentinel';
1
2
  import type { AttachmentMeta } from './types';
2
3
  export type InitOptions = {
3
4
  /** Project token starting with `st_pk_`. Required. */
@@ -36,6 +37,13 @@ export type InitOptions = {
36
37
  * the buffer is sealed and uploaded as a `sessionTrail`
37
38
  * attachment. Defaults to false. */
38
39
  sessionTrail?: boolean;
40
+ /** v0.9.1 +S4 — pre-crash sentinel. Subscribes to JS-thread
41
+ * frame timing; when ≥ 50% of a 60-frame window misses the
42
+ * budget (default 32 ms / < 30 fps), emits a `kind: nearCrash`
43
+ * event proactively so dashboards see the "about-to-die"
44
+ * signal before an actual crash. */
45
+ preCrashSentinel?: boolean;
46
+ sentinelChannels?: PreCrashChannel[];
39
47
  /** v0.9.0 #3 — launch-crash loop guard. When two consecutive
40
48
  * launches don't reach `markLaunchCompleted()` (typical of an
41
49
  * OTA update with a fatal bug), invoke the host callback with
package/lib/init.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAkB,cAAc,EAA2B,MAAM,SAAS,CAAC;AAIvF,MAAM,MAAM,WAAW,GAAG;IACxB,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,OAAO,CAAC,EAAE;QACR,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAC5B,OAAO,CAAC,EACJ,OAAO,GACP;YACE;;qEAEyD;YACzD,OAAO,CAAC,EAAE,OAAO,CAAC;SACnB,CAAC;QACN;;8DAEsD;QACtD,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB;;;;;;;uBAOe;QACf,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB;;;6CAGqC;QACrC,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB;;;sEAG8D;QAC9D,gBAAgB,CAAC,EAAE;YACjB,OAAO,EAAE,OAAO,CAAC;YACjB,qBAAqB,CAAC,EAAE,CACtB,IAAI,EAAE,OAAO,sBAAsB,EAAE,eAAe,KAElD,OAAO,sBAAsB,EAAE,iBAAiB,GAChD,OAAO,CAAC,OAAO,sBAAsB,EAAE,iBAAiB,CAAC,CAAC;YAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,SAAS,CAAC,EAAE,MAAM,CAAC;SACpB,CAAC;KACH,CAAC;IACF;;;;;gEAK4D;IAC5D,QAAQ,CAAC,EAAE;QACT,MAAM,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;QACvB,MAAM,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;KACxB,CAAC;CACH,CAAC;AAIF,eAAO,MAAM,IAAI,GAAI,SAAS,WAAW,KAAG,IAoH3C,CAAC;AAiBF,YAAY,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAaA,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAQnF,OAAO,KAAK,EAAkB,cAAc,EAA2B,MAAM,SAAS,CAAC;AAIvF,MAAM,MAAM,WAAW,GAAG;IACxB,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,OAAO,CAAC,EAAE;QACR,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAC5B,OAAO,CAAC,EACJ,OAAO,GACP;YACE;;qEAEyD;YACzD,OAAO,CAAC,EAAE,OAAO,CAAC;SACnB,CAAC;QACN;;8DAEsD;QACtD,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB;;;;;;;uBAOe;QACf,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB;;;6CAGqC;QACrC,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB;;;;6CAIqC;QACrC,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAC3B,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;QACrC;;;sEAG8D;QAC9D,gBAAgB,CAAC,EAAE;YACjB,OAAO,EAAE,OAAO,CAAC;YACjB,qBAAqB,CAAC,EAAE,CACtB,IAAI,EAAE,OAAO,sBAAsB,EAAE,eAAe,KAElD,OAAO,sBAAsB,EAAE,iBAAiB,GAChD,OAAO,CAAC,OAAO,sBAAsB,EAAE,iBAAiB,CAAC,CAAC;YAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,SAAS,CAAC,EAAE,MAAM,CAAC;SACpB,CAAC;KACH,CAAC;IACF;;;;;gEAK4D;IAC5D,QAAQ,CAAC,EAAE;QACT,MAAM,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;QACvB,MAAM,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;KACxB,CAAC;CACH,CAAC;AAIF,eAAO,MAAM,IAAI,GAAI,SAAS,WAAW,KAAG,IA4H3C,CAAC;AAiBF,YAAY,EAAE,cAAc,EAAE,CAAC"}
package/lib/init.js CHANGED
@@ -8,6 +8,7 @@ import { markLaunchCompleted, runLaunchCrashGuard, } from './launch-crash-guard'
8
8
  import { startMetricsTimer } from './metrics';
9
9
  import { drainNativePending, setNativeConfig } from './native';
10
10
  import { startNetworkTypeWatch } from './netinfo';
11
+ import { startPreCrashSentinel } from './pre-crash-sentinel';
11
12
  import { startSession } from './session-tracker';
12
13
  import { drainOfflineQueue, enqueue, startTransport, uploadAttachment, } from './transport';
13
14
  const DEFAULT_INGEST_URL = 'https://ingest.sentori.golia.jp';
@@ -53,6 +54,14 @@ export const init = (options) => {
53
54
  startNetworkTypeWatch();
54
55
  // v0.8.3 — drain custom-metric ring every 30 s.
55
56
  startMetricsTimer();
57
+ // v0.9.1 +S4 — pre-crash sentinel. Off by default; opt-in via
58
+ // `capture.preCrashSentinel: true`.
59
+ if (options.capture?.preCrashSentinel === true) {
60
+ startPreCrashSentinel({
61
+ enabled: true,
62
+ channels: options.capture.sentinelChannels,
63
+ });
64
+ }
56
65
  const capture = options.capture ?? {};
57
66
  if (capture.globalErrors !== false)
58
67
  installGlobalHandler();
package/lib/init.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export type PreCrashChannel = 'frame-budget-overrun' | 'memory-pressure' | 'oom-warning' | 'storage-low';
2
+ export type PreCrashSentinelOptions = {
3
+ enabled: boolean;
4
+ channels?: PreCrashChannel[];
5
+ /** Lower → more sensitive. Default 32 ms (< 30 fps). */
6
+ frameBudgetMs?: number;
7
+ /** Fraction of frames in the window that must miss budget. Default 0.5. */
8
+ tripRatio?: number;
9
+ };
10
+ export declare function startPreCrashSentinel(opts: PreCrashSentinelOptions): void;
11
+ export declare function stopPreCrashSentinel(): void;
12
+ //# sourceMappingURL=pre-crash-sentinel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pre-crash-sentinel.d.ts","sourceRoot":"","sources":["../src/pre-crash-sentinel.ts"],"names":[],"mappings":"AAmCA,MAAM,MAAM,eAAe,GACvB,sBAAsB,GACtB,iBAAiB,GACjB,aAAa,GACb,aAAa,CAAC;AAElB,MAAM,MAAM,uBAAuB,GAAG;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;IAC7B,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,uBAAuB,GAAG,IAAI,CAUzE;AAED,wBAAgB,oBAAoB,IAAI,IAAI,CAK3C"}
@@ -0,0 +1,116 @@
1
+ // v0.9.1 +S4 — pre-crash sentinel.
2
+ //
3
+ // Predictive (vs reactive) telemetry. Subscribes to JS-thread frame
4
+ // timing via requestAnimationFrame and, when a rolling 60-frame
5
+ // window has ≥ 50% of frames slower than 32 ms (i.e. < 30 fps for
6
+ // half the window), emits an `event.kind = nearCrash` to the server.
7
+ // Backend stores it in the same events stream so the dashboard shows
8
+ // "X user sessions had near-crash signals 4 minutes before the
9
+ // actual NSException".
10
+ //
11
+ // Memory pressure / OOM / storage low signals will need native
12
+ // system observers and ship in v1.0. v0.9.1 covers the most common
13
+ // runaway-render-loop case purely from JS.
14
+ import { startSpan } from '@goliapkg/sentori-core';
15
+ import { getBundleInfo } from './bundle-info';
16
+ import { collectDeviceForSentinel, getAppForSentinel } from './sentinel-context';
17
+ import { getConfig, isInitialized } from './config';
18
+ import { enqueue } from './transport';
19
+ import { uuidV7 } from './uuid';
20
+ const FRAME_BUDGET_MS = 32; // < 30 fps
21
+ const WINDOW_FRAMES = 60; // ~1 s at 60 fps
22
+ const TRIP_RATIO = 0.5;
23
+ const COOLDOWN_MS = 60_000; // don't spam: one nearCrash event per minute
24
+ let _running = false;
25
+ let _lastFrameAt = 0;
26
+ let _slowFrames = 0;
27
+ let _totalFrames = 0;
28
+ let _lastEmitAt = 0;
29
+ let _channels = new Set();
30
+ export function startPreCrashSentinel(opts) {
31
+ if (!opts.enabled || _running)
32
+ return;
33
+ _running = true;
34
+ _channels = new Set(opts.channels ?? ['frame-budget-overrun']);
35
+ if (_channels.has('frame-budget-overrun')) {
36
+ startFrameBudgetWatch(opts.frameBudgetMs ?? FRAME_BUDGET_MS, opts.tripRatio ?? TRIP_RATIO);
37
+ }
38
+ // Native channels (memory-pressure, oom-warning, storage-low) hook
39
+ // through a TODO native module in v1.0.
40
+ }
41
+ export function stopPreCrashSentinel() {
42
+ _running = false;
43
+ _slowFrames = 0;
44
+ _totalFrames = 0;
45
+ _channels.clear();
46
+ }
47
+ function startFrameBudgetWatch(budgetMs, tripRatio) {
48
+ if (typeof requestAnimationFrame !== 'function')
49
+ return;
50
+ _lastFrameAt = Date.now();
51
+ function tick() {
52
+ if (!_running)
53
+ return;
54
+ const now = Date.now();
55
+ const delta = now - _lastFrameAt;
56
+ _lastFrameAt = now;
57
+ if (delta >= budgetMs)
58
+ _slowFrames++;
59
+ _totalFrames++;
60
+ if (_totalFrames >= WINDOW_FRAMES) {
61
+ const ratio = _slowFrames / _totalFrames;
62
+ if (ratio >= tripRatio && now - _lastEmitAt > COOLDOWN_MS) {
63
+ _lastEmitAt = now;
64
+ emitNearCrash({
65
+ slowFrames: _slowFrames,
66
+ totalFrames: _totalFrames,
67
+ ratio,
68
+ windowMs: WINDOW_FRAMES * (budgetMs / 2), // approximate
69
+ channel: 'frame-budget-overrun',
70
+ });
71
+ }
72
+ _slowFrames = 0;
73
+ _totalFrames = 0;
74
+ }
75
+ requestAnimationFrame(tick);
76
+ }
77
+ requestAnimationFrame(tick);
78
+ }
79
+ function emitNearCrash(data) {
80
+ if (!isInitialized())
81
+ return;
82
+ const config = getConfig();
83
+ if (!config)
84
+ return;
85
+ const span = startSpan('sentori.nearCrash', {
86
+ name: data.channel,
87
+ tags: {
88
+ 'nearCrash.channel': data.channel,
89
+ 'nearCrash.ratio': data.ratio.toFixed(3),
90
+ 'nearCrash.slow_frames': String(data.slowFrames),
91
+ 'nearCrash.total_frames': String(data.totalFrames),
92
+ },
93
+ });
94
+ span.finish({ status: 'ok' });
95
+ const event = {
96
+ id: uuidV7(),
97
+ timestamp: new Date().toISOString(),
98
+ kind: 'nearCrash',
99
+ platform: 'javascript',
100
+ release: config.release,
101
+ environment: config.environment,
102
+ device: collectDeviceForSentinel(),
103
+ app: getAppForSentinel(config.release),
104
+ ...(getBundleInfo() ? { bundle: getBundleInfo() } : {}),
105
+ error: {
106
+ type: 'NearCrash',
107
+ message: `frame budget overrun: ${(data.ratio * 100).toFixed(0)}% of last ${data.totalFrames} frames slow`,
108
+ stack: [],
109
+ },
110
+ tags: {
111
+ 'nearCrash.channel': data.channel,
112
+ },
113
+ };
114
+ enqueue(event);
115
+ }
116
+ //# sourceMappingURL=pre-crash-sentinel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pre-crash-sentinel.js","sourceRoot":"","sources":["../src/pre-crash-sentinel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { App, Device } from './types';
2
+ export declare const collectDeviceForSentinel: () => Device;
3
+ export declare const getAppForSentinel: (release: string) => App;
4
+ //# sourceMappingURL=sentinel-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentinel-context.d.ts","sourceRoot":"","sources":["../src/sentinel-context.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAE3C,eAAO,MAAM,wBAAwB,QAAO,MAgC3C,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,SAAS,MAAM,KAAG,GAkBnD,CAAC"}
@@ -0,0 +1,52 @@
1
+ // Shared device + app collectors. Used by both capture.ts (errors)
2
+ // and pre-crash-sentinel.ts (nearCrash). Same shape so the dashboard
3
+ // can render both kinds of events through the same UI components.
4
+ import { getCachedNetworkType } from './netinfo';
5
+ export const collectDeviceForSentinel = () => {
6
+ let os = 'other';
7
+ let osVersion = '0';
8
+ let locale;
9
+ const networkType = getCachedNetworkType();
10
+ try {
11
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
12
+ const RN = require('react-native');
13
+ const rnOS = RN.Platform.OS;
14
+ os = rnOS === 'android' || rnOS === 'ios' || rnOS === 'web' ? rnOS : 'other';
15
+ osVersion = String(RN.Platform.Version);
16
+ if (rnOS === 'ios') {
17
+ const s = RN.NativeModules.SettingsManager?.settings;
18
+ locale = s?.AppleLocale ?? s?.AppleLanguages?.[0];
19
+ }
20
+ else if (rnOS === 'android') {
21
+ locale = RN.NativeModules.I18nManager?.localeIdentifier;
22
+ }
23
+ }
24
+ catch {
25
+ // not in RN runtime
26
+ }
27
+ const device = { os, osVersion };
28
+ if (locale)
29
+ device.locale = locale;
30
+ if (networkType)
31
+ device.networkType = networkType;
32
+ return device;
33
+ };
34
+ export const getAppForSentinel = (release) => {
35
+ const m = /^(?:[^@]+@)?([^+]+)(?:\+(.+))?$/.exec(release);
36
+ const version = m?.[1] ?? '0.0.0';
37
+ const build = m?.[2];
38
+ let rnVersion = 'unknown';
39
+ try {
40
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
41
+ rnVersion = require('react-native/package.json').version;
42
+ }
43
+ catch {
44
+ // not in RN runtime
45
+ }
46
+ return {
47
+ build,
48
+ framework: { name: 'react-native', version: rnVersion },
49
+ version,
50
+ };
51
+ };
52
+ //# sourceMappingURL=sentinel-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentinel-context.js","sourceRoot":"","sources":["../src/sentinel-context.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,qEAAqE;AACrE,kEAAkE;AAElE,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAGjD,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAW,EAAE;IACnD,IAAI,EAAE,GAAiB,OAAO,CAAC;IAC/B,IAAI,SAAS,GAAG,GAAG,CAAC;IACpB,IAAI,MAA0B,CAAC;IAC/B,MAAM,WAAW,GAAG,oBAAoB,EAAE,CAAC;IAC3C,IAAI,CAAC;QACH,iEAAiE;QACjE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAQhC,CAAC;QACF,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,EAAE,GAAG,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;QAC7E,SAAS,GAAG,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACxC,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;YACnB,MAAM,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,eAAe,EAAE,QAAQ,CAAC;YACrD,MAAM,GAAG,CAAC,EAAE,WAAW,IAAI,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,CAAC;QACpD,CAAC;aAAM,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,GAAG,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,gBAAgB,CAAC;QAC1D,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,oBAAoB;IACtB,CAAC;IACD,MAAM,MAAM,GAAW,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC;IACzC,IAAI,MAAM;QAAE,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;IACnC,IAAI,WAAW;QAAE,MAAM,CAAC,WAAW,GAAG,WAAW,CAAC;IAClD,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,OAAe,EAAO,EAAE;IACxD,MAAM,CAAC,GAAG,iCAAiC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC1D,MAAM,OAAO,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC;IAClC,MAAM,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAErB,IAAI,SAAS,GAAG,SAAS,CAAC;IAC1B,IAAI,CAAC;QACH,iEAAiE;QACjE,SAAS,GAAI,OAAO,CAAC,2BAA2B,CAAyB,CAAC,OAAO,CAAC;IACpF,CAAC;IAAC,MAAM,CAAC;QACP,oBAAoB;IACtB,CAAC;IAED,OAAO;QACL,KAAK;QACL,SAAS,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE;QACvD,OAAO;KACR,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,33 @@
1
+ type StoreLike = {
2
+ getState?: () => unknown;
3
+ subscribe?: (cb: () => void) => () => void;
4
+ };
5
+ export type StateSnapshot = {
6
+ /** Wall-clock when the diff fired. */
7
+ ts: number;
8
+ /** Top-level key/value diff vs the previous snapshot. Empty diff
9
+ * doesn't get recorded. */
10
+ diff: Record<string, unknown>;
11
+ /** Source label so the viewer can show "Redux" / "Zustand" / "Manual". */
12
+ source: string;
13
+ };
14
+ export declare function bindState(opts: {
15
+ redux?: StoreLike;
16
+ zustand?: StoreLike;
17
+ /** Additional named stores. The label is used as the snapshot's
18
+ * `source` and as the diff bucket key. */
19
+ custom?: Record<string, StoreLike>;
20
+ }): void;
21
+ export declare function unbindState(): void;
22
+ /** Manual recording for state not in a subscribed store. */
23
+ export declare function recordState(snapshot: Record<string, unknown>, source?: string): void;
24
+ export declare function getStateSnapshots(): StateSnapshot[];
25
+ export declare function clearStateSnapshots(): void;
26
+ /** Returns the top-level key/value diff (next has the value, prev
27
+ * may not contain the key). Empty diff returns `{}` so callers can
28
+ * no-op on empty. */
29
+ export declare function shallowDiff(prev: unknown, next: unknown): null | Record<string, unknown>;
30
+ /** Test-only. */
31
+ export declare function __resetStateSnapshotsForTests(): void;
32
+ export {};
33
+ //# sourceMappingURL=state-snapshots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-snapshots.d.ts","sourceRoot":"","sources":["../src/state-snapshots.ts"],"names":[],"mappings":"AAwBA,KAAK,SAAS,GAAG;IACf,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,sCAAsC;IACtC,EAAE,EAAE,MAAM,CAAC;IACX;gCAC4B;IAC5B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAMF,wBAAgB,SAAS,CAAC,IAAI,EAAE;IAC9B,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;+CAC2C;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CACpC,GAAG,IAAI,CASP;AAsBD,wBAAgB,WAAW,IAAI,IAAI,CAUlC;AAED,4DAA4D;AAC5D,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,SAAW,GAAG,IAAI,CAEtF;AAED,wBAAgB,iBAAiB,IAAI,aAAa,EAAE,CAEnD;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAOD;;sBAEsB;AACtB,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAaxF;AAED,iBAAiB;AACjB,wBAAgB,6BAA6B,IAAI,IAAI,CAGpD"}
@@ -0,0 +1,109 @@
1
+ // v0.9.2 +S2 — State Time-travel (SDK side).
2
+ //
3
+ // `sentori.bindState({ redux, zustand })` subscribes to a store-like
4
+ // object and records a shallow diff every time it emits a change.
5
+ // A ring buffer of the last N snapshots travels with each
6
+ // captureException as a `stateSnapshot` attachment — the dashboard's
7
+ // time-travel viewer (v0.9.2.1) renders the timeline.
8
+ //
9
+ // Why diffs and not full snapshots:
10
+ // • prod stores can be huge (carts, paginated lists, etc.)
11
+ // • a diff drops to ~1% of full snapshot size in typical apps
12
+ // • the viewer rehydrates by applying diffs forward from a baseline
13
+ //
14
+ // `recordState(obj)` is the manual escape hatch for state that isn't
15
+ // in a redux/zustand store (e.g. `useState` in a deeply-nested
16
+ // component, or React Context). The host calls it where it makes sense.
17
+ //
18
+ // Privacy: the same mask query that protects screenshots is consulted
19
+ // before serializing — any path matching `nativeID` shape isn't yet,
20
+ // but `mask.matchPath` can be added later. v0.9.2 ships without
21
+ // path masking; consumers should keep PII out of bindState scopes.
22
+ const MAX_SNAPSHOTS = 50;
23
+ let _snapshots = [];
24
+ let _unsubscribers = [];
25
+ let _lastByLabel = {};
26
+ export function bindState(opts) {
27
+ unbindState();
28
+ bindOne('redux', opts.redux);
29
+ bindOne('zustand', opts.zustand);
30
+ if (opts.custom) {
31
+ for (const [label, store] of Object.entries(opts.custom)) {
32
+ bindOne(label, store);
33
+ }
34
+ }
35
+ }
36
+ function bindOne(label, store) {
37
+ if (!store)
38
+ return;
39
+ if (typeof store.getState !== 'function' || typeof store.subscribe !== 'function')
40
+ return;
41
+ try {
42
+ _lastByLabel[label] = store.getState();
43
+ const unsub = store.subscribe(() => {
44
+ const next = store.getState();
45
+ const prev = _lastByLabel[label];
46
+ const diff = shallowDiff(prev, next);
47
+ if (diff && Object.keys(diff).length > 0) {
48
+ push({ diff, source: label, ts: Date.now() });
49
+ }
50
+ _lastByLabel[label] = next;
51
+ });
52
+ _unsubscribers.push(unsub);
53
+ }
54
+ catch {
55
+ // ignore bad stores
56
+ }
57
+ }
58
+ export function unbindState() {
59
+ for (const u of _unsubscribers) {
60
+ try {
61
+ u();
62
+ }
63
+ catch {
64
+ // ignore
65
+ }
66
+ }
67
+ _unsubscribers = [];
68
+ _lastByLabel = {};
69
+ }
70
+ /** Manual recording for state not in a subscribed store. */
71
+ export function recordState(snapshot, source = 'manual') {
72
+ push({ diff: snapshot, source, ts: Date.now() });
73
+ }
74
+ export function getStateSnapshots() {
75
+ return _snapshots.slice();
76
+ }
77
+ export function clearStateSnapshots() {
78
+ _snapshots = [];
79
+ }
80
+ function push(s) {
81
+ _snapshots.push(s);
82
+ while (_snapshots.length > MAX_SNAPSHOTS)
83
+ _snapshots.shift();
84
+ }
85
+ /** Returns the top-level key/value diff (next has the value, prev
86
+ * may not contain the key). Empty diff returns `{}` so callers can
87
+ * no-op on empty. */
88
+ export function shallowDiff(prev, next) {
89
+ if (typeof next !== 'object' || next === null)
90
+ return null;
91
+ const nObj = next;
92
+ if (typeof prev !== 'object' || prev === null) {
93
+ return { ...nObj };
94
+ }
95
+ const pObj = prev;
96
+ const out = {};
97
+ const keys = new Set([...Object.keys(pObj), ...Object.keys(nObj)]);
98
+ for (const k of keys) {
99
+ if (pObj[k] !== nObj[k])
100
+ out[k] = nObj[k];
101
+ }
102
+ return out;
103
+ }
104
+ /** Test-only. */
105
+ export function __resetStateSnapshotsForTests() {
106
+ unbindState();
107
+ _snapshots = [];
108
+ }
109
+ //# sourceMappingURL=state-snapshots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-snapshots.js","sourceRoot":"","sources":["../src/state-snapshots.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goliapkg/sentori-react-native",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
4
4
  "description": "Sentori SDK for React Native \u2014 JS-layer error capture, native crash handlers (iOS / Android), batched transport, fetch + react-navigation tracing.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://sentori.golia.jp",
@@ -80,6 +80,6 @@
80
80
  "access": "public"
81
81
  },
82
82
  "dependencies": {
83
- "@goliapkg/sentori-core": "0.8.0"
83
+ "@goliapkg/sentori-core": "0.8.1"
84
84
  }
85
85
  }
@@ -0,0 +1,119 @@
1
+ import { afterEach, describe, expect, test } from 'bun:test';
2
+
3
+ import {
4
+ __resetStateSnapshotsForTests,
5
+ bindState,
6
+ getStateSnapshots,
7
+ recordState,
8
+ shallowDiff,
9
+ unbindState,
10
+ } from '../state-snapshots';
11
+
12
+ afterEach(() => {
13
+ __resetStateSnapshotsForTests();
14
+ });
15
+
16
+ describe('shallowDiff', () => {
17
+ test('emits only changed top-level keys', () => {
18
+ const a = { foo: 1, bar: 2 };
19
+ const b = { bar: 2, foo: 3 };
20
+ expect(shallowDiff(a, b)).toEqual({ foo: 3 });
21
+ });
22
+
23
+ test('emits new keys from next', () => {
24
+ expect(shallowDiff({ a: 1 }, { a: 1, b: 2 })).toEqual({ b: 2 });
25
+ });
26
+
27
+ test('treats deletion as undefined', () => {
28
+ expect(shallowDiff({ a: 1, b: 2 }, { a: 1 })).toEqual({ b: undefined });
29
+ });
30
+
31
+ test('no change returns empty object', () => {
32
+ expect(shallowDiff({ a: 1 }, { a: 1 })).toEqual({});
33
+ });
34
+
35
+ test('non-object prev → full clone of next', () => {
36
+ expect(shallowDiff(undefined, { a: 1 })).toEqual({ a: 1 });
37
+ });
38
+ });
39
+
40
+ describe('recordState', () => {
41
+ test('appends snapshot with default source=manual', () => {
42
+ recordState({ cart: 3 });
43
+ const snaps = getStateSnapshots();
44
+ expect(snaps.length).toBe(1);
45
+ expect(snaps[0]!.source).toBe('manual');
46
+ expect(snaps[0]!.diff).toEqual({ cart: 3 });
47
+ });
48
+
49
+ test('caps at 50 snapshots (ring buffer)', () => {
50
+ for (let i = 0; i < 60; i++) recordState({ n: i });
51
+ expect(getStateSnapshots().length).toBe(50);
52
+ // oldest 10 dropped; first kept entry has n=10
53
+ expect(getStateSnapshots()[0]!.diff).toEqual({ n: 10 });
54
+ });
55
+ });
56
+
57
+ describe('bindState', () => {
58
+ test('subscribes to a redux-like store and records diffs', () => {
59
+ let state: { count: number } = { count: 0 };
60
+ const listeners: (() => void)[] = [];
61
+ const store = {
62
+ getState: () => state,
63
+ subscribe: (cb: () => void) => {
64
+ listeners.push(cb);
65
+ return () => {
66
+ const i = listeners.indexOf(cb);
67
+ if (i >= 0) listeners.splice(i, 1);
68
+ };
69
+ },
70
+ };
71
+ bindState({ redux: store });
72
+ state = { count: 1 };
73
+ listeners.forEach((l) => l());
74
+ state = { count: 2 };
75
+ listeners.forEach((l) => l());
76
+
77
+ const snaps = getStateSnapshots();
78
+ expect(snaps.length).toBe(2);
79
+ expect(snaps[0]!.diff).toEqual({ count: 1 });
80
+ expect(snaps[1]!.diff).toEqual({ count: 2 });
81
+ expect(snaps[0]!.source).toBe('redux');
82
+ });
83
+
84
+ test('unbindState stops recording', () => {
85
+ let state: { v: number } = { v: 0 };
86
+ const listeners: (() => void)[] = [];
87
+ const store = {
88
+ getState: () => state,
89
+ subscribe: (cb: () => void) => {
90
+ listeners.push(cb);
91
+ return () => listeners.splice(listeners.indexOf(cb), 1);
92
+ },
93
+ };
94
+ bindState({ redux: store });
95
+ state = { v: 1 };
96
+ listeners.forEach((l) => l());
97
+ unbindState();
98
+ state = { v: 2 };
99
+ listeners.forEach((l) => l());
100
+
101
+ expect(getStateSnapshots().length).toBe(1);
102
+ });
103
+
104
+ test('custom stores produce labeled snapshots', () => {
105
+ let auth = { user: null };
106
+ const listeners: (() => void)[] = [];
107
+ const store = {
108
+ getState: () => auth,
109
+ subscribe: (cb: () => void) => {
110
+ listeners.push(cb);
111
+ return () => listeners.splice(listeners.indexOf(cb), 1);
112
+ },
113
+ };
114
+ bindState({ custom: { auth: store } });
115
+ auth = { user: 'alice' as never };
116
+ listeners.forEach((l) => l());
117
+ expect(getStateSnapshots()[0]!.source).toBe('auth');
118
+ });
119
+ });
package/src/capture.ts CHANGED
@@ -4,6 +4,7 @@ import { addBreadcrumb, getBreadcrumbs } from './breadcrumbs';
4
4
  import { getBundleInfo } from './bundle-info';
5
5
  import { getConfig, isInitialized } from './config';
6
6
  import { getFeatureFlagSnapshot } from './feature-flags';
7
+ import { clearStateSnapshots, getStateSnapshots } from './state-snapshots';
7
8
  import { symbolicateErrorViaMetro } from './handlers/dev-symbolicate';
8
9
  import { captureScreenshot } from './handlers/screenshot';
9
10
  import { markSessionErrored } from './session-tracker';
@@ -144,11 +145,48 @@ export const captureError = (error: Error, extras?: CaptureExtras): void => {
144
145
  if (config.sessionTrailEnabled && trail.size() > 0) {
145
146
  await captureAndAttachSessionTrail(event);
146
147
  }
148
+ // v0.9.2 +S2 — state time-travel attachment. Only if anything has
149
+ // been bound or recorded; cleared on success so the next crash's
150
+ // ring doesn't carry stale entries.
151
+ const stateSnapshots = getStateSnapshots();
152
+ if (stateSnapshots.length > 0) {
153
+ await captureAndAttachStateSnapshots(event, stateSnapshots);
154
+ clearStateSnapshots();
155
+ }
147
156
  enqueue(event);
148
157
  };
149
158
  void pipeline();
150
159
  };
151
160
 
161
+ /** v0.9.2 +S2 — upload the rolling state-snapshot ring as a
162
+ * `stateSnapshot` attachment so the dashboard time-travel viewer can
163
+ * scrub through diffs alongside the breadcrumb timeline. */
164
+ async function captureAndAttachStateSnapshots(
165
+ event: Event,
166
+ snapshots: ReturnType<typeof getStateSnapshots>,
167
+ ): Promise<void> {
168
+ try {
169
+ const payload = JSON.stringify({ snapshots });
170
+ const base64 =
171
+ typeof globalThis.btoa === 'function'
172
+ ? globalThis.btoa(payload)
173
+ : // Bun / node fallback
174
+ Buffer.from(payload, 'utf8').toString('base64');
175
+ const meta = await uploadAttachment(
176
+ event.id,
177
+ 'stateSnapshot',
178
+ { base64, mediaType: 'application/json' },
179
+ { source: 'js' },
180
+ );
181
+ if (meta) {
182
+ if (!event.attachments) event.attachments = [];
183
+ event.attachments.push(meta);
184
+ }
185
+ } catch {
186
+ // best-effort
187
+ }
188
+ }
189
+
152
190
  /**
153
191
  * Phase 46 — seal the trail buffer, upload it as a `sessionTrail`
154
192
  * attachment, attach the ref. Best-effort: any failure leaves a
package/src/index.ts CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  } from './feature-flags';
19
19
  import { clearMaskQuery, registerMaskQuery } from './mask';
20
20
  import { measureFn } from './measure';
21
+ import { bindState, recordState, unbindState } from './state-snapshots';
21
22
  import { startMoment } from '@goliapkg/sentori-core';
22
23
  import { flushMetrics, recordMetric } from './metrics';
23
24
  import { RageTapCapture } from './rage-tap';
@@ -40,6 +41,9 @@ export const sentori = {
40
41
  flushMetrics,
41
42
  measureFn,
42
43
  startMoment,
44
+ bindState,
45
+ recordState,
46
+ unbindState,
43
47
  setFeatureFlag,
44
48
  clearFeatureFlag,
45
49
  clearAllFeatureFlags,
@@ -78,6 +82,12 @@ export { clearMaskQuery, registerMaskQuery } from './mask';
78
82
  export { flushMetrics, recordMetric } from './metrics';
79
83
  export { measureFn } from './measure';
80
84
  export { MomentHandle, type MomentProperties, startMoment } from '@goliapkg/sentori-core';
85
+ export {
86
+ bindState,
87
+ recordState,
88
+ type StateSnapshot,
89
+ unbindState,
90
+ } from './state-snapshots';
81
91
  export { RageTapCapture } from './rage-tap';
82
92
  export {
83
93
  startAnrWatchdog,
package/src/init.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  import { startMetricsTimer } from './metrics';
12
12
  import { drainNativePending, setNativeConfig } from './native';
13
13
  import { startNetworkTypeWatch } from './netinfo';
14
+ import { startPreCrashSentinel, type PreCrashChannel } from './pre-crash-sentinel';
14
15
  import { startSession } from './session-tracker';
15
16
  import {
16
17
  drainOfflineQueue,
@@ -61,6 +62,13 @@ export type InitOptions = {
61
62
  * the buffer is sealed and uploaded as a `sessionTrail`
62
63
  * attachment. Defaults to false. */
63
64
  sessionTrail?: boolean;
65
+ /** v0.9.1 +S4 — pre-crash sentinel. Subscribes to JS-thread
66
+ * frame timing; when ≥ 50% of a 60-frame window misses the
67
+ * budget (default 32 ms / < 30 fps), emits a `kind: nearCrash`
68
+ * event proactively so dashboards see the "about-to-die"
69
+ * signal before an actual crash. */
70
+ preCrashSentinel?: boolean;
71
+ sentinelChannels?: PreCrashChannel[];
64
72
  /** v0.9.0 #3 — launch-crash loop guard. When two consecutive
65
73
  * launches don't reach `markLaunchCompleted()` (typical of an
66
74
  * OTA update with a fatal bug), invoke the host callback with
@@ -142,6 +150,14 @@ export const init = (options: InitOptions): void => {
142
150
  startNetworkTypeWatch();
143
151
  // v0.8.3 — drain custom-metric ring every 30 s.
144
152
  startMetricsTimer();
153
+ // v0.9.1 +S4 — pre-crash sentinel. Off by default; opt-in via
154
+ // `capture.preCrashSentinel: true`.
155
+ if (options.capture?.preCrashSentinel === true) {
156
+ startPreCrashSentinel({
157
+ enabled: true,
158
+ channels: options.capture.sentinelChannels,
159
+ });
160
+ }
145
161
 
146
162
  const capture = options.capture ?? {};
147
163
  if (capture.globalErrors !== false) installGlobalHandler();
@@ -0,0 +1,140 @@
1
+ // v0.9.1 +S4 — pre-crash sentinel.
2
+ //
3
+ // Predictive (vs reactive) telemetry. Subscribes to JS-thread frame
4
+ // timing via requestAnimationFrame and, when a rolling 60-frame
5
+ // window has ≥ 50% of frames slower than 32 ms (i.e. < 30 fps for
6
+ // half the window), emits an `event.kind = nearCrash` to the server.
7
+ // Backend stores it in the same events stream so the dashboard shows
8
+ // "X user sessions had near-crash signals 4 minutes before the
9
+ // actual NSException".
10
+ //
11
+ // Memory pressure / OOM / storage low signals will need native
12
+ // system observers and ship in v1.0. v0.9.1 covers the most common
13
+ // runaway-render-loop case purely from JS.
14
+
15
+ import { startSpan } from '@goliapkg/sentori-core';
16
+
17
+ import { getBundleInfo } from './bundle-info';
18
+ import { collectDeviceForSentinel, getAppForSentinel } from './sentinel-context';
19
+ import { getConfig, isInitialized } from './config';
20
+ import { enqueue } from './transport';
21
+ import { uuidV7 } from './uuid';
22
+ import type { Event } from './types';
23
+
24
+ const FRAME_BUDGET_MS = 32; // < 30 fps
25
+ const WINDOW_FRAMES = 60; // ~1 s at 60 fps
26
+ const TRIP_RATIO = 0.5;
27
+ const COOLDOWN_MS = 60_000; // don't spam: one nearCrash event per minute
28
+
29
+ let _running = false;
30
+ let _lastFrameAt = 0;
31
+ let _slowFrames = 0;
32
+ let _totalFrames = 0;
33
+ let _lastEmitAt = 0;
34
+ let _channels: Set<string> = new Set();
35
+
36
+ export type PreCrashChannel =
37
+ | 'frame-budget-overrun'
38
+ | 'memory-pressure' // native, v1.0
39
+ | 'oom-warning' // native, v1.0
40
+ | 'storage-low'; // native, v1.0
41
+
42
+ export type PreCrashSentinelOptions = {
43
+ enabled: boolean;
44
+ channels?: PreCrashChannel[];
45
+ /** Lower → more sensitive. Default 32 ms (< 30 fps). */
46
+ frameBudgetMs?: number;
47
+ /** Fraction of frames in the window that must miss budget. Default 0.5. */
48
+ tripRatio?: number;
49
+ };
50
+
51
+ export function startPreCrashSentinel(opts: PreCrashSentinelOptions): void {
52
+ if (!opts.enabled || _running) return;
53
+ _running = true;
54
+ _channels = new Set(opts.channels ?? ['frame-budget-overrun']);
55
+
56
+ if (_channels.has('frame-budget-overrun')) {
57
+ startFrameBudgetWatch(opts.frameBudgetMs ?? FRAME_BUDGET_MS, opts.tripRatio ?? TRIP_RATIO);
58
+ }
59
+ // Native channels (memory-pressure, oom-warning, storage-low) hook
60
+ // through a TODO native module in v1.0.
61
+ }
62
+
63
+ export function stopPreCrashSentinel(): void {
64
+ _running = false;
65
+ _slowFrames = 0;
66
+ _totalFrames = 0;
67
+ _channels.clear();
68
+ }
69
+
70
+ function startFrameBudgetWatch(budgetMs: number, tripRatio: number): void {
71
+ if (typeof requestAnimationFrame !== 'function') return;
72
+ _lastFrameAt = Date.now();
73
+ function tick() {
74
+ if (!_running) return;
75
+ const now = Date.now();
76
+ const delta = now - _lastFrameAt;
77
+ _lastFrameAt = now;
78
+ if (delta >= budgetMs) _slowFrames++;
79
+ _totalFrames++;
80
+ if (_totalFrames >= WINDOW_FRAMES) {
81
+ const ratio = _slowFrames / _totalFrames;
82
+ if (ratio >= tripRatio && now - _lastEmitAt > COOLDOWN_MS) {
83
+ _lastEmitAt = now;
84
+ emitNearCrash({
85
+ slowFrames: _slowFrames,
86
+ totalFrames: _totalFrames,
87
+ ratio,
88
+ windowMs: WINDOW_FRAMES * (budgetMs / 2), // approximate
89
+ channel: 'frame-budget-overrun',
90
+ });
91
+ }
92
+ _slowFrames = 0;
93
+ _totalFrames = 0;
94
+ }
95
+ requestAnimationFrame(tick);
96
+ }
97
+ requestAnimationFrame(tick);
98
+ }
99
+
100
+ function emitNearCrash(data: {
101
+ slowFrames: number;
102
+ totalFrames: number;
103
+ ratio: number;
104
+ windowMs: number;
105
+ channel: PreCrashChannel;
106
+ }): void {
107
+ if (!isInitialized()) return;
108
+ const config = getConfig();
109
+ if (!config) return;
110
+ const span = startSpan('sentori.nearCrash', {
111
+ name: data.channel,
112
+ tags: {
113
+ 'nearCrash.channel': data.channel,
114
+ 'nearCrash.ratio': data.ratio.toFixed(3),
115
+ 'nearCrash.slow_frames': String(data.slowFrames),
116
+ 'nearCrash.total_frames': String(data.totalFrames),
117
+ },
118
+ });
119
+ span.finish({ status: 'ok' });
120
+ const event: Event = {
121
+ id: uuidV7(),
122
+ timestamp: new Date().toISOString(),
123
+ kind: 'nearCrash',
124
+ platform: 'javascript',
125
+ release: config.release,
126
+ environment: config.environment,
127
+ device: collectDeviceForSentinel(),
128
+ app: getAppForSentinel(config.release),
129
+ ...(getBundleInfo() ? { bundle: getBundleInfo() as { id: string } } : {}),
130
+ error: {
131
+ type: 'NearCrash',
132
+ message: `frame budget overrun: ${(data.ratio * 100).toFixed(0)}% of last ${data.totalFrames} frames slow`,
133
+ stack: [],
134
+ },
135
+ tags: {
136
+ 'nearCrash.channel': data.channel,
137
+ },
138
+ };
139
+ enqueue(event);
140
+ }
@@ -0,0 +1,60 @@
1
+ // Shared device + app collectors. Used by both capture.ts (errors)
2
+ // and pre-crash-sentinel.ts (nearCrash). Same shape so the dashboard
3
+ // can render both kinds of events through the same UI components.
4
+
5
+ import { getCachedNetworkType } from './netinfo';
6
+ import type { App, Device } from './types';
7
+
8
+ export const collectDeviceForSentinel = (): Device => {
9
+ let os: Device['os'] = 'other';
10
+ let osVersion = '0';
11
+ let locale: string | undefined;
12
+ const networkType = getCachedNetworkType();
13
+ try {
14
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
15
+ const RN = require('react-native') as {
16
+ NativeModules: {
17
+ I18nManager?: { localeIdentifier?: string };
18
+ SettingsManager?: {
19
+ settings?: { AppleLanguages?: string[]; AppleLocale?: string };
20
+ };
21
+ };
22
+ Platform: { OS: string; Version: number | string };
23
+ };
24
+ const rnOS = RN.Platform.OS;
25
+ os = rnOS === 'android' || rnOS === 'ios' || rnOS === 'web' ? rnOS : 'other';
26
+ osVersion = String(RN.Platform.Version);
27
+ if (rnOS === 'ios') {
28
+ const s = RN.NativeModules.SettingsManager?.settings;
29
+ locale = s?.AppleLocale ?? s?.AppleLanguages?.[0];
30
+ } else if (rnOS === 'android') {
31
+ locale = RN.NativeModules.I18nManager?.localeIdentifier;
32
+ }
33
+ } catch {
34
+ // not in RN runtime
35
+ }
36
+ const device: Device = { os, osVersion };
37
+ if (locale) device.locale = locale;
38
+ if (networkType) device.networkType = networkType;
39
+ return device;
40
+ };
41
+
42
+ export const getAppForSentinel = (release: string): App => {
43
+ const m = /^(?:[^@]+@)?([^+]+)(?:\+(.+))?$/.exec(release);
44
+ const version = m?.[1] ?? '0.0.0';
45
+ const build = m?.[2];
46
+
47
+ let rnVersion = 'unknown';
48
+ try {
49
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
50
+ rnVersion = (require('react-native/package.json') as { version: string }).version;
51
+ } catch {
52
+ // not in RN runtime
53
+ }
54
+
55
+ return {
56
+ build,
57
+ framework: { name: 'react-native', version: rnVersion },
58
+ version,
59
+ };
60
+ };
@@ -0,0 +1,133 @@
1
+ // v0.9.2 +S2 — State Time-travel (SDK side).
2
+ //
3
+ // `sentori.bindState({ redux, zustand })` subscribes to a store-like
4
+ // object and records a shallow diff every time it emits a change.
5
+ // A ring buffer of the last N snapshots travels with each
6
+ // captureException as a `stateSnapshot` attachment — the dashboard's
7
+ // time-travel viewer (v0.9.2.1) renders the timeline.
8
+ //
9
+ // Why diffs and not full snapshots:
10
+ // • prod stores can be huge (carts, paginated lists, etc.)
11
+ // • a diff drops to ~1% of full snapshot size in typical apps
12
+ // • the viewer rehydrates by applying diffs forward from a baseline
13
+ //
14
+ // `recordState(obj)` is the manual escape hatch for state that isn't
15
+ // in a redux/zustand store (e.g. `useState` in a deeply-nested
16
+ // component, or React Context). The host calls it where it makes sense.
17
+ //
18
+ // Privacy: the same mask query that protects screenshots is consulted
19
+ // before serializing — any path matching `nativeID` shape isn't yet,
20
+ // but `mask.matchPath` can be added later. v0.9.2 ships without
21
+ // path masking; consumers should keep PII out of bindState scopes.
22
+
23
+ const MAX_SNAPSHOTS = 50;
24
+
25
+ type StoreLike = {
26
+ getState?: () => unknown;
27
+ subscribe?: (cb: () => void) => () => void;
28
+ };
29
+
30
+ export type StateSnapshot = {
31
+ /** Wall-clock when the diff fired. */
32
+ ts: number;
33
+ /** Top-level key/value diff vs the previous snapshot. Empty diff
34
+ * doesn't get recorded. */
35
+ diff: Record<string, unknown>;
36
+ /** Source label so the viewer can show "Redux" / "Zustand" / "Manual". */
37
+ source: string;
38
+ };
39
+
40
+ let _snapshots: StateSnapshot[] = [];
41
+ let _unsubscribers: (() => void)[] = [];
42
+ let _lastByLabel: Record<string, unknown> = {};
43
+
44
+ export function bindState(opts: {
45
+ redux?: StoreLike;
46
+ zustand?: StoreLike;
47
+ /** Additional named stores. The label is used as the snapshot's
48
+ * `source` and as the diff bucket key. */
49
+ custom?: Record<string, StoreLike>;
50
+ }): void {
51
+ unbindState();
52
+ bindOne('redux', opts.redux);
53
+ bindOne('zustand', opts.zustand);
54
+ if (opts.custom) {
55
+ for (const [label, store] of Object.entries(opts.custom)) {
56
+ bindOne(label, store);
57
+ }
58
+ }
59
+ }
60
+
61
+ function bindOne(label: string, store?: StoreLike): void {
62
+ if (!store) return;
63
+ if (typeof store.getState !== 'function' || typeof store.subscribe !== 'function') return;
64
+ try {
65
+ _lastByLabel[label] = store.getState();
66
+ const unsub = store.subscribe(() => {
67
+ const next = store.getState!();
68
+ const prev = _lastByLabel[label];
69
+ const diff = shallowDiff(prev, next);
70
+ if (diff && Object.keys(diff).length > 0) {
71
+ push({ diff, source: label, ts: Date.now() });
72
+ }
73
+ _lastByLabel[label] = next;
74
+ });
75
+ _unsubscribers.push(unsub);
76
+ } catch {
77
+ // ignore bad stores
78
+ }
79
+ }
80
+
81
+ export function unbindState(): void {
82
+ for (const u of _unsubscribers) {
83
+ try {
84
+ u();
85
+ } catch {
86
+ // ignore
87
+ }
88
+ }
89
+ _unsubscribers = [];
90
+ _lastByLabel = {};
91
+ }
92
+
93
+ /** Manual recording for state not in a subscribed store. */
94
+ export function recordState(snapshot: Record<string, unknown>, source = 'manual'): void {
95
+ push({ diff: snapshot, source, ts: Date.now() });
96
+ }
97
+
98
+ export function getStateSnapshots(): StateSnapshot[] {
99
+ return _snapshots.slice();
100
+ }
101
+
102
+ export function clearStateSnapshots(): void {
103
+ _snapshots = [];
104
+ }
105
+
106
+ function push(s: StateSnapshot): void {
107
+ _snapshots.push(s);
108
+ while (_snapshots.length > MAX_SNAPSHOTS) _snapshots.shift();
109
+ }
110
+
111
+ /** Returns the top-level key/value diff (next has the value, prev
112
+ * may not contain the key). Empty diff returns `{}` so callers can
113
+ * no-op on empty. */
114
+ export function shallowDiff(prev: unknown, next: unknown): null | Record<string, unknown> {
115
+ if (typeof next !== 'object' || next === null) return null;
116
+ const nObj = next as Record<string, unknown>;
117
+ if (typeof prev !== 'object' || prev === null) {
118
+ return { ...nObj };
119
+ }
120
+ const pObj = prev as Record<string, unknown>;
121
+ const out: Record<string, unknown> = {};
122
+ const keys = new Set([...Object.keys(pObj), ...Object.keys(nObj)]);
123
+ for (const k of keys) {
124
+ if (pObj[k] !== nObj[k]) out[k] = nObj[k];
125
+ }
126
+ return out;
127
+ }
128
+
129
+ /** Test-only. */
130
+ export function __resetStateSnapshotsForTests(): void {
131
+ unbindState();
132
+ _snapshots = [];
133
+ }