@keepsnappy/react-native 0.1.0-alpha.12 → 0.1.0-alpha.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -35
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/{perflab/PerfLabModule.kt → keepsnappy/KeepSnappyModule.kt} +23 -23
- package/android/src/main/java/expo/modules/{perflab/PerfLabSampler.kt → keepsnappy/KeepSnappySampler.kt} +17 -17
- package/app.plugin.js +7 -6
- package/expo-module.config.json +2 -2
- package/ios/{PerfLab.podspec → KeepSnappy.podspec} +2 -2
- package/ios/{PerfLabHermes.h → KeepSnappyHermes.h} +1 -1
- package/ios/{PerfLabHermes.mm → KeepSnappyHermes.mm} +2 -2
- package/ios/{PerfLabModule.swift → KeepSnappyModule.swift} +24 -24
- package/ios/{PerfLabSampler.swift → KeepSnappySampler.swift} +10 -10
- package/js/babel-transformer.js +2 -2
- package/js/hook.polyfill.js +11 -11
- package/js/renderer-switch.js +1 -1
- package/js/sdk/activation.ts +15 -15
- package/js/sdk/active.ts +3 -3
- package/js/sdk/artifacts.ts +3 -3
- package/js/sdk/boot.ts +4 -4
- package/js/sdk/causes.ts +2 -2
- package/js/sdk/core.ts +45 -45
- package/js/sdk/cpuprof.ts +1 -1
- package/js/sdk/dormant.ts +7 -7
- package/js/sdk/links.ts +13 -13
- package/js/sdk/net.ts +2 -2
- package/js/sdk/profile-export.ts +7 -7
- package/js/sdk/push-install.ts +3 -3
- package/js/sdk/push.ts +2 -2
- package/js/sdk/sampler.ts +1 -1
- package/js/zustand-vanilla.js +2 -2
- package/package.json +1 -1
package/js/sdk/core.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
//
|
|
1
|
+
// keepsnappy core: settings, windows, the commit walk, observers, faults, logs.
|
|
2
2
|
// Loaded by boot.ts, which Metro runs before the app's entry module.
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
type ActivationStore,
|
|
6
6
|
type Decision,
|
|
7
7
|
MODE,
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
KEEPSNAPPY_DEEP_LINK_PATH,
|
|
9
|
+
KEEPSNAPPY_ENABLED,
|
|
10
10
|
parseQuery,
|
|
11
11
|
type PolyfillActivation,
|
|
12
12
|
} from './activation';
|
|
@@ -90,7 +90,7 @@ import {
|
|
|
90
90
|
windowAggregate,
|
|
91
91
|
} from './sampler';
|
|
92
92
|
|
|
93
|
-
export {
|
|
93
|
+
export { KEEPSNAPPY_DEEP_LINK_PATH, KEEPSNAPPY_ENABLED, parseQuery };
|
|
94
94
|
|
|
95
95
|
type LogSink = 'console' | 'native' | 'both';
|
|
96
96
|
|
|
@@ -126,7 +126,7 @@ interface Settings extends SamplerSettings {
|
|
|
126
126
|
diag: boolean;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
interface
|
|
129
|
+
interface KeepSnappyNative extends Partial<CpuProfileNative> {
|
|
130
130
|
beginAsyncSection(name: string, cookie: number): void;
|
|
131
131
|
endAsyncSection(name: string, cookie: number): void;
|
|
132
132
|
setCounter(name: string, value: number): void;
|
|
@@ -143,13 +143,13 @@ interface PerfLabNative extends Partial<CpuProfileNative> {
|
|
|
143
143
|
samplerRecent(sinceT: number): SamplerRecord[];
|
|
144
144
|
samplerNow(): number;
|
|
145
145
|
flushSignal?(status: string, detail: string): void;
|
|
146
|
-
// GC before the action: Android finishes the pending command broadcast with this result; iOS shows it on `
|
|
146
|
+
// GC before the action: Android finishes the pending command broadcast with this result; iOS shows it on `keepsnappy.ack`.
|
|
147
147
|
commandDone?(n: number, data: string): void;
|
|
148
148
|
/** Android: whether the broadcast of command n still waits for its answer (native finishes it at a deadline). */
|
|
149
149
|
commandPending?(n: number): boolean;
|
|
150
150
|
ackSignal?(text: string): void;
|
|
151
151
|
commandListen?(on: boolean): void;
|
|
152
|
-
addListener?(event: '
|
|
152
|
+
addListener?(event: 'keepsnappyCommand', listener: (e: { cmd: string; n: number; tNative: number }) => void): { remove(): void };
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
interface RendererInternals {
|
|
@@ -232,7 +232,7 @@ interface PolyfillHook {
|
|
|
232
232
|
attach: (listener: HookListener | null) => void;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
interface
|
|
235
|
+
interface KeepSnappyGlobal {
|
|
236
236
|
HermesInternal?: HermesInternalApi;
|
|
237
237
|
PerformanceObserver?: PerfObserverCtor & { supportedEntryTypes?: string[] };
|
|
238
238
|
performance?: PerformanceLike;
|
|
@@ -240,12 +240,12 @@ interface PerfLabGlobal {
|
|
|
240
240
|
console: { log: (...args: unknown[]) => void; timeStamp?: unknown };
|
|
241
241
|
expo?: { modules?: Record<string, unknown> };
|
|
242
242
|
__REACT_DEVTOOLS_GLOBAL_HOOK__?: Record<string, unknown>;
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
__KEEPSNAPPY_HOOK__?: PolyfillHook;
|
|
244
|
+
__KEEPSNAPPY_ACTIVATION__?: PolyfillActivation;
|
|
245
|
+
__KEEPSNAPPY__?: unknown;
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
const g = globalThis as unknown as
|
|
248
|
+
const g = globalThis as unknown as KeepSnappyGlobal;
|
|
249
249
|
|
|
250
250
|
const NO_FAULTS: Faults = {
|
|
251
251
|
cpu: 0,
|
|
@@ -296,14 +296,14 @@ export const now = () => (g.performance ? g.performance.now() : Date.now());
|
|
|
296
296
|
export const fmt = (ms: number) => ms.toFixed(3);
|
|
297
297
|
const noop = () => undefined;
|
|
298
298
|
|
|
299
|
-
let nativeModule:
|
|
299
|
+
let nativeModule: KeepSnappyNative | null | undefined;
|
|
300
300
|
let nativeMissingLogged = false;
|
|
301
301
|
|
|
302
|
-
const getNative = ():
|
|
302
|
+
const getNative = (): KeepSnappyNative | null => {
|
|
303
303
|
if (nativeModule) {
|
|
304
304
|
return nativeModule;
|
|
305
305
|
}
|
|
306
|
-
const found = g.expo?.modules?.
|
|
306
|
+
const found = g.expo?.modules?.KeepSnappy as KeepSnappyNative | undefined;
|
|
307
307
|
if (found) {
|
|
308
308
|
nativeModule = found;
|
|
309
309
|
return found;
|
|
@@ -311,7 +311,7 @@ const getNative = (): PerfLabNative | null => {
|
|
|
311
311
|
if (!nativeMissingLogged) {
|
|
312
312
|
nativeMissingLogged = true;
|
|
313
313
|
g.console.log(
|
|
314
|
-
'
|
|
314
|
+
'KEEPSNAPPY native module KeepSnappy missing (expo.modules.KeepSnappy)',
|
|
315
315
|
);
|
|
316
316
|
}
|
|
317
317
|
return null;
|
|
@@ -344,10 +344,10 @@ const cpu = createCpuProfiler({
|
|
|
344
344
|
});
|
|
345
345
|
|
|
346
346
|
export const perfLog = (message: string, sink: LogSink = settings.log) => {
|
|
347
|
-
if (!
|
|
347
|
+
if (!KEEPSNAPPY_ENABLED) {
|
|
348
348
|
return;
|
|
349
349
|
}
|
|
350
|
-
const line = `
|
|
350
|
+
const line = `KEEPSNAPPY ${message}`;
|
|
351
351
|
if (sink === 'console' || sink === 'both') {
|
|
352
352
|
g.console.log(line);
|
|
353
353
|
}
|
|
@@ -412,7 +412,7 @@ const nativeSamplerNow = () => {
|
|
|
412
412
|
|
|
413
413
|
// On-demand sample: logged natively like the automatic ones and returned. Null when the sampler is off.
|
|
414
414
|
export const sample = (reason = 'manual'): SamplerRecord | null => {
|
|
415
|
-
if (!
|
|
415
|
+
if (!KEEPSNAPPY_ENABLED || samplerState.mode === 'off') {
|
|
416
416
|
return null;
|
|
417
417
|
}
|
|
418
418
|
try {
|
|
@@ -816,7 +816,7 @@ const clearIdleGc = () => {
|
|
|
816
816
|
|
|
817
817
|
const scheduleIdleGc = () => {
|
|
818
818
|
clearIdleGc();
|
|
819
|
-
if (!
|
|
819
|
+
if (!KEEPSNAPPY_ENABLED || !settings.gcwin || openWindows.size > 0) {
|
|
820
820
|
return;
|
|
821
821
|
}
|
|
822
822
|
idleGcTimer = setTimeout(() => {
|
|
@@ -835,7 +835,7 @@ export const lastCommitAt = (key: string) =>
|
|
|
835
835
|
export const isWindowOpen = (key?: string) =>
|
|
836
836
|
key ? openWindows.has(key) : openWindows.size > 0;
|
|
837
837
|
|
|
838
|
-
const traceName = (name: string) => `
|
|
838
|
+
const traceName = (name: string) => `keepsnappy:${name}`;
|
|
839
839
|
|
|
840
840
|
interface StartOptions {
|
|
841
841
|
key?: string;
|
|
@@ -962,7 +962,7 @@ const endBackground = (reason: string) => {
|
|
|
962
962
|
};
|
|
963
963
|
|
|
964
964
|
const start = (name: string, options: StartOptions = {}) => {
|
|
965
|
-
if (!
|
|
965
|
+
if (!KEEPSNAPPY_ENABLED || !settings.marks) {
|
|
966
966
|
return null;
|
|
967
967
|
}
|
|
968
968
|
const key = options.key ?? name;
|
|
@@ -1091,7 +1091,7 @@ const rename = (key: string, name: string) => {
|
|
|
1091
1091
|
};
|
|
1092
1092
|
|
|
1093
1093
|
const point = (name: string) => {
|
|
1094
|
-
if (!
|
|
1094
|
+
if (!KEEPSNAPPY_ENABLED || !settings.marks) {
|
|
1095
1095
|
return;
|
|
1096
1096
|
}
|
|
1097
1097
|
const t = now();
|
|
@@ -1277,15 +1277,15 @@ const onCommitFiberUnmount = (_rendererId: number, fiber: Fiber) => {
|
|
|
1277
1277
|
};
|
|
1278
1278
|
|
|
1279
1279
|
export const lastRoot = (rendererId = 1): FiberRoot | null =>
|
|
1280
|
-
g.
|
|
1280
|
+
g.__KEEPSNAPPY_HOOK__?.state.lastRootByRenderer[rendererId] ?? null;
|
|
1281
1281
|
|
|
1282
1282
|
// The polyfill (withKeepSnappy) installed the hook before React loaded; attach to
|
|
1283
1283
|
// it. Without the polyfill, fall back to installing it now, as the prototype did.
|
|
1284
1284
|
export const attachHook = () => {
|
|
1285
|
-
if (!
|
|
1285
|
+
if (!KEEPSNAPPY_ENABLED || hookState.installed) {
|
|
1286
1286
|
return;
|
|
1287
1287
|
}
|
|
1288
|
-
const polyfill = g.
|
|
1288
|
+
const polyfill = g.__KEEPSNAPPY_HOOK__;
|
|
1289
1289
|
if (polyfill) {
|
|
1290
1290
|
polyfill.ensure();
|
|
1291
1291
|
polyfill.attach({
|
|
@@ -1310,7 +1310,7 @@ export const attachHook = () => {
|
|
|
1310
1310
|
}
|
|
1311
1311
|
if (g.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
|
|
1312
1312
|
hookState.preexisting = true;
|
|
1313
|
-
g.console.log('
|
|
1313
|
+
g.console.log('KEEPSNAPPY hook already present, not replacing it');
|
|
1314
1314
|
return;
|
|
1315
1315
|
}
|
|
1316
1316
|
let nextRendererId = 1;
|
|
@@ -1327,7 +1327,7 @@ export const attachHook = () => {
|
|
|
1327
1327
|
bundleType: internals.bundleType,
|
|
1328
1328
|
});
|
|
1329
1329
|
g.console.log(
|
|
1330
|
-
`
|
|
1330
|
+
`KEEPSNAPPY hook inject id=${id} renderer=${internals.rendererPackageName} version=${internals.version} bundleType=${internals.bundleType}`,
|
|
1331
1331
|
);
|
|
1332
1332
|
return id;
|
|
1333
1333
|
},
|
|
@@ -1753,7 +1753,7 @@ const finalizeWindow = (window: MarkerWindow) => {
|
|
|
1753
1753
|
const writeWindowRecord = (window: MarkerWindow, summary: Record<string, unknown>) => {
|
|
1754
1754
|
const json = JSON.stringify(summary);
|
|
1755
1755
|
perfLog(`window ${json}`);
|
|
1756
|
-
windowListener?.(`${Date.now()}
|
|
1756
|
+
windowListener?.(`${Date.now()} KEEPSNAPPY window ${json}`);
|
|
1757
1757
|
if (settings.file) {
|
|
1758
1758
|
const path = getNative()?.writeText(
|
|
1759
1759
|
`window-${window.id}-${window.name.replace(/[^\w.:+-]/g, '_')}.json`,
|
|
@@ -1948,7 +1948,7 @@ const forwardSamples = () => {
|
|
|
1948
1948
|
if (record.t <= samplesSentT) {
|
|
1949
1949
|
continue;
|
|
1950
1950
|
}
|
|
1951
|
-
windowListener(`${record.wallMs}
|
|
1951
|
+
windowListener(`${record.wallMs} KEEPSNAPPY sample ${JSON.stringify(record)}`);
|
|
1952
1952
|
samplesSentT = record.t;
|
|
1953
1953
|
}
|
|
1954
1954
|
} catch (error) {
|
|
@@ -1973,7 +1973,7 @@ export const writeRunEvent = (kind: 'gc' | 'appstate', record: Record<string, un
|
|
|
1973
1973
|
const json = JSON.stringify({ ...record, t: now(), wallMs: Date.now() });
|
|
1974
1974
|
perfLog(`${kind} ${json}`);
|
|
1975
1975
|
if (inRun() && (kind === 'gc' || gcRanSinceFlush)) {
|
|
1976
|
-
windowListener?.(`${Date.now()}
|
|
1976
|
+
windowListener?.(`${Date.now()} KEEPSNAPPY ${kind} ${json}`);
|
|
1977
1977
|
}
|
|
1978
1978
|
};
|
|
1979
1979
|
|
|
@@ -1995,7 +1995,7 @@ export const setFlushHandler = (handler: FlushHandler | null) => {
|
|
|
1995
1995
|
};
|
|
1996
1996
|
|
|
1997
1997
|
// `flush=1`: end the open windows, send every unsent line, then show the result
|
|
1998
|
-
// to the test driver as an accessibility element (iOS: id `
|
|
1998
|
+
// to the test driver as an accessibility element (iOS: id `keepsnappy.flushed`,
|
|
1999
1999
|
// label `ok` / `error` / `off`), so a flow can wait for it without network access.
|
|
2000
2000
|
const flushOnce = async () => {
|
|
2001
2001
|
// One deadline for the whole flush (lines, clock, artifacts); the end report of the upload comes after it.
|
|
@@ -2059,7 +2059,7 @@ export const flushNow = (): Promise<void> => {
|
|
|
2059
2059
|
|
|
2060
2060
|
// The activation's profile/cpuprof turn those on for the whole process; off
|
|
2061
2061
|
// leaves the build's env defaults.
|
|
2062
|
-
// A
|
|
2062
|
+
// A KeepSnappy run (one with a run token) forces an idle GC between windows so heap-after-GC
|
|
2063
2063
|
// readings exist for the leak slope (plan 41 Q7); `gcwin=0` in a link still turns it off.
|
|
2064
2064
|
const applyDecision = (decision: Decision) => {
|
|
2065
2065
|
settings.diag = decision.diag;
|
|
@@ -2093,13 +2093,13 @@ const activationReport = () => {
|
|
|
2093
2093
|
mode: 'active',
|
|
2094
2094
|
buildMode: decision.mode,
|
|
2095
2095
|
read: decision.read,
|
|
2096
|
-
installMs: g.
|
|
2096
|
+
installMs: g.__KEEPSNAPPY_ACTIVATION__?.installMs,
|
|
2097
2097
|
runToken: runToken !== null,
|
|
2098
2098
|
expiresAt: decision.expiresAt,
|
|
2099
2099
|
diag: decision.diag,
|
|
2100
2100
|
profile: decision.profile,
|
|
2101
2101
|
cpuprof: decision.cpuprof,
|
|
2102
|
-
renderer: g.
|
|
2102
|
+
renderer: g.__KEEPSNAPPY_ACTIVATION__?.renderer ?? null,
|
|
2103
2103
|
next: next
|
|
2104
2104
|
? {
|
|
2105
2105
|
active: next.active,
|
|
@@ -2258,24 +2258,24 @@ const syncCommandListener = () => {
|
|
|
2258
2258
|
if (!native?.commandListen) {
|
|
2259
2259
|
return;
|
|
2260
2260
|
}
|
|
2261
|
-
const on =
|
|
2261
|
+
const on = KEEPSNAPPY_ENABLED && runToken !== null;
|
|
2262
2262
|
if (on && commandSubscription === null) {
|
|
2263
2263
|
commandSubscription =
|
|
2264
|
-
native.addListener?.('
|
|
2264
|
+
native.addListener?.('keepsnappyCommand', e => {
|
|
2265
2265
|
// A command native already timed out on is not run: its GC would land in the next step's action.
|
|
2266
2266
|
if (native.commandPending?.(e.n) === false) {
|
|
2267
2267
|
perfLog(`command gc:${e.n} late, skipped`);
|
|
2268
2268
|
return;
|
|
2269
2269
|
}
|
|
2270
|
-
|
|
2270
|
+
handleKeepSnappyCommand(e.cmd, e.n, e.tNative);
|
|
2271
2271
|
}) ?? null;
|
|
2272
2272
|
}
|
|
2273
2273
|
native.commandListen(on);
|
|
2274
2274
|
};
|
|
2275
2275
|
|
|
2276
2276
|
/** A command (`cmd=<name>&n=<n>`), from a link (iOS) or the native receiver (Android); unknown commands are ignored. */
|
|
2277
|
-
export const
|
|
2278
|
-
if (!
|
|
2277
|
+
export const handleKeepSnappyCommand = (cmd: string | undefined, n: number, tNative?: number): boolean => {
|
|
2278
|
+
if (!KEEPSNAPPY_ENABLED || cmd !== 'gc' || !Number.isInteger(n) || n < 1) {
|
|
2279
2279
|
return false;
|
|
2280
2280
|
}
|
|
2281
2281
|
// Every received command is answered, so the Android receiver is never left pending.
|
|
@@ -2288,14 +2288,14 @@ export const handlePerfLabCommand = (cmd: string | undefined, n: number, tNative
|
|
|
2288
2288
|
return true;
|
|
2289
2289
|
};
|
|
2290
2290
|
|
|
2291
|
-
export const
|
|
2292
|
-
if (!
|
|
2291
|
+
export const handleKeepSnappyDeepLink = (url: string) => {
|
|
2292
|
+
if (!KEEPSNAPPY_ENABLED) {
|
|
2293
2293
|
return;
|
|
2294
2294
|
}
|
|
2295
2295
|
const params = parseQuery(url.split('?')[1]);
|
|
2296
2296
|
// A command changes no settings and writes no activation record.
|
|
2297
2297
|
if (params.cmd !== undefined) {
|
|
2298
|
-
|
|
2298
|
+
handleKeepSnappyCommand(params.cmd, Number(params.n));
|
|
2299
2299
|
return;
|
|
2300
2300
|
}
|
|
2301
2301
|
const written = activation?.store.apply(params) ?? null;
|
|
@@ -2326,7 +2326,7 @@ export const addStartupSection = (name: string, read: () => unknown) => {
|
|
|
2326
2326
|
|
|
2327
2327
|
export const logStartup = (phase: string) => {
|
|
2328
2328
|
const native = getNative();
|
|
2329
|
-
const polyfill = g.
|
|
2329
|
+
const polyfill = g.__KEEPSNAPPY_HOOK__?.state;
|
|
2330
2330
|
const extra: Record<string, unknown> = {};
|
|
2331
2331
|
for (const [name, read] of extraStartupSections) {
|
|
2332
2332
|
extra[name] = safe(read);
|
|
@@ -2391,7 +2391,7 @@ export const logStartup = (phase: string) => {
|
|
|
2391
2391
|
};
|
|
2392
2392
|
|
|
2393
2393
|
export const exposeGlobal = () => {
|
|
2394
|
-
g.
|
|
2394
|
+
g.__KEEPSNAPPY__ = {
|
|
2395
2395
|
settings,
|
|
2396
2396
|
set: applySettings,
|
|
2397
2397
|
markers,
|
package/js/sdk/cpuprof.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// overlapping windows (a press and the screen it opens) share one span and one
|
|
4
4
|
// file. Inside a run it samples on until `flush=1` and writes one file there.
|
|
5
5
|
// The file is a DevTools .cpuprofile (nodes/samples/timeDeltas) written by the
|
|
6
|
-
// native module into Documents/
|
|
6
|
+
// native module into Documents/keepsnappy.
|
|
7
7
|
|
|
8
8
|
export interface CpuProfileNative {
|
|
9
9
|
cpuProfileStart(hz: number): boolean;
|
package/js/sdk/dormant.ts
CHANGED
|
@@ -16,14 +16,14 @@ interface DormantNative {
|
|
|
16
16
|
|
|
17
17
|
interface DormantGlobal {
|
|
18
18
|
__REACT_DEVTOOLS_GLOBAL_HOOK__?: unknown;
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
__KEEPSNAPPY_HOOK__?: { state?: { mode?: string; listener?: unknown } };
|
|
20
|
+
__KEEPSNAPPY__?: unknown;
|
|
21
21
|
expo?: { modules?: Record<string, unknown> };
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const g = globalThis as unknown as DormantGlobal;
|
|
25
25
|
|
|
26
|
-
const NET_MARK = '
|
|
26
|
+
const NET_MARK = '__keepsnappyNet';
|
|
27
27
|
|
|
28
28
|
const marked = (value: unknown) =>
|
|
29
29
|
(typeof value === 'function' || (typeof value === 'object' && value !== null)) &&
|
|
@@ -54,8 +54,8 @@ export const dormantReport = (decision: Decision) => ({
|
|
|
54
54
|
buildMode: decision.mode,
|
|
55
55
|
read: decision.read,
|
|
56
56
|
reactHook: typeof g.__REACT_DEVTOOLS_GLOBAL_HOOK__,
|
|
57
|
-
|
|
58
|
-
sdkGlobal: g.
|
|
57
|
+
keepsnappyHook: g.__KEEPSNAPPY_HOOK__ ? (g.__KEEPSNAPPY_HOOK__.state?.mode ?? 'present') : 'absent',
|
|
58
|
+
sdkGlobal: g.__KEEPSNAPPY__ !== undefined,
|
|
59
59
|
fetchWrapped: globalWrapped('fetch'),
|
|
60
60
|
xhrGetterWrapped: globalWrapped('XMLHttpRequest'),
|
|
61
61
|
xhrPatched: xhrPatched(),
|
|
@@ -69,9 +69,9 @@ export const startDormant = (decision: Decision) => {
|
|
|
69
69
|
store.apply(params);
|
|
70
70
|
if (params.dump === '1') {
|
|
71
71
|
// Asked for: one os_log line (iOS) / logcat line (Android), no log file.
|
|
72
|
-
const native = g.expo?.modules?.
|
|
72
|
+
const native = g.expo?.modules?.KeepSnappy as DormantNative | undefined;
|
|
73
73
|
const report = { ...dormantReport(decision), activation: store.stats() };
|
|
74
|
-
native?.osLog?.(`
|
|
74
|
+
native?.osLog?.(`KEEPSNAPPY dormant ${JSON.stringify(report)}`);
|
|
75
75
|
}
|
|
76
76
|
});
|
|
77
77
|
};
|
package/js/sdk/links.ts
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
// Settings links (`<scheme>://devtools/
|
|
1
|
+
// Settings links (`<scheme>://devtools/keepsnappy?...`) without app code: the SDK
|
|
2
2
|
// runs before the router subscribes to Linking, so it wraps the link sources.
|
|
3
|
-
// A
|
|
3
|
+
// A keepsnappy link goes to the handler here and never reaches the app's
|
|
4
4
|
// listeners, so the router does not navigate to an unmatched route. Only a URL
|
|
5
|
-
// whose host and path are exactly `devtools/
|
|
5
|
+
// whose host and path are exactly `devtools/keepsnappy` on a custom (non-web)
|
|
6
6
|
// scheme is one; every other URL passes through unchanged after one substring
|
|
7
7
|
// check: in a dormant build this is the only work the SDK does after startup.
|
|
8
8
|
|
|
9
9
|
import { Linking } from 'react-native';
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { KEEPSNAPPY_DEEP_LINK_PATH } from './activation';
|
|
12
12
|
|
|
13
13
|
export type LinkHandler = (url: string, via: string) => void;
|
|
14
|
-
// Told when an app URL (not a
|
|
14
|
+
// Told when an app URL (not a keepsnappy one) is handed to the app, before the
|
|
15
15
|
// app sees it: the start of a link-driven navigation.
|
|
16
16
|
export type AppLinkHandler = (url: string, via: string) => void;
|
|
17
17
|
|
|
18
18
|
type UrlListener = (event: { url: string }) => void;
|
|
19
19
|
|
|
20
|
-
// `<scheme>://devtools/
|
|
21
|
-
// (`<scheme>:///devtools/
|
|
20
|
+
// `<scheme>://devtools/keepsnappy[/][?query][#fragment]`, also with an empty host
|
|
21
|
+
// (`<scheme>:///devtools/keepsnappy`). Universal links (http/https) never match,
|
|
22
22
|
// so a web page cannot route a link here.
|
|
23
|
-
// Spells out
|
|
24
|
-
const SETTINGS_LINK = /^([a-z][a-z0-9+.-]*):\/\/\/?devtools\/
|
|
23
|
+
// Spells out KEEPSNAPPY_DEEP_LINK_PATH.
|
|
24
|
+
const SETTINGS_LINK = /^([a-z][a-z0-9+.-]*):\/\/\/?devtools\/keepsnappy\/?(?:[?#]|$)/i;
|
|
25
25
|
const WEB_SCHEMES = new Set(['http', 'https']);
|
|
26
26
|
|
|
27
|
-
export const
|
|
28
|
-
if (typeof url !== 'string' || !url.includes(
|
|
27
|
+
export const isKeepSnappyUrl = (url: unknown): url is string => {
|
|
28
|
+
if (typeof url !== 'string' || !url.includes(KEEPSNAPPY_DEEP_LINK_PATH)) {
|
|
29
29
|
return false;
|
|
30
30
|
}
|
|
31
31
|
const match = SETTINGS_LINK.exec(url);
|
|
@@ -126,7 +126,7 @@ const patch = (
|
|
|
126
126
|
};
|
|
127
127
|
|
|
128
128
|
const filterInitial = (url: unknown, via: string) => {
|
|
129
|
-
if (
|
|
129
|
+
if (isKeepSnappyUrl(url)) {
|
|
130
130
|
apply(url, via);
|
|
131
131
|
return null;
|
|
132
132
|
}
|
|
@@ -157,7 +157,7 @@ export const installLinks = (
|
|
|
157
157
|
return original(type, listener);
|
|
158
158
|
}
|
|
159
159
|
const filtered: UrlListener = event => {
|
|
160
|
-
if (
|
|
160
|
+
if (isKeepSnappyUrl(event?.url)) {
|
|
161
161
|
apply(event.url, 'event');
|
|
162
162
|
return;
|
|
163
163
|
}
|
package/js/sdk/net.ts
CHANGED
|
@@ -403,8 +403,8 @@ export type NetCounter = ReturnType<typeof createNetCounter>;
|
|
|
403
403
|
// images loaded natively (Image, expo-image), WebSockets, and native SDKs
|
|
404
404
|
// (Stream, LiveKit, Firebase).
|
|
405
405
|
|
|
406
|
-
const WRAPPED = '
|
|
407
|
-
const XHR_STATE = '
|
|
406
|
+
const WRAPPED = '__keepsnappyNet';
|
|
407
|
+
const XHR_STATE = '__keepsnappyNetXhr';
|
|
408
408
|
const EXPO_BUILTIN = Symbol.for('expo.builtin');
|
|
409
409
|
|
|
410
410
|
type AnyFn = (...args: unknown[]) => unknown;
|
package/js/sdk/profile-export.ts
CHANGED
|
@@ -178,7 +178,7 @@ const changeDescription = (cause: UpdateCause) => ({
|
|
|
178
178
|
|
|
179
179
|
export interface ReactExport {
|
|
180
180
|
version: 5;
|
|
181
|
-
source: '
|
|
181
|
+
source: 'keepsnappy';
|
|
182
182
|
captureStart: { clock: 'perf-now-ms'; t0: number };
|
|
183
183
|
dataForRoots: {
|
|
184
184
|
rootID: number;
|
|
@@ -225,11 +225,11 @@ export const buildReactExport = (t0: number, commits: ExportCommit[]): ReactExpo
|
|
|
225
225
|
passiveEffectDuration: null,
|
|
226
226
|
priorityLevel: null,
|
|
227
227
|
timestamp: Math.max(0, commit.t - t0),
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
...(commit.facts ? {
|
|
228
|
+
keepsnappyCauses: commit.causes,
|
|
229
|
+
keepsnappyCarrier: commit.carrier,
|
|
230
|
+
keepsnappyStore: commit.store,
|
|
231
|
+
keepsnappyTask: commit.task,
|
|
232
|
+
...(commit.facts ? { keepsnappyFacts: commit.facts } : {}),
|
|
233
233
|
updaters: (commit.updaters ?? []).map(displayName => ({
|
|
234
234
|
displayName,
|
|
235
235
|
hocDisplayNames: null,
|
|
@@ -241,7 +241,7 @@ export const buildReactExport = (t0: number, commits: ExportCommit[]): ReactExpo
|
|
|
241
241
|
});
|
|
242
242
|
return {
|
|
243
243
|
version: 5,
|
|
244
|
-
source: '
|
|
244
|
+
source: 'keepsnappy',
|
|
245
245
|
captureStart: { clock: 'perf-now-ms', t0 },
|
|
246
246
|
dataForRoots: [
|
|
247
247
|
{
|
package/js/sdk/push-install.ts
CHANGED
|
@@ -80,7 +80,7 @@ interface SpoolNative {
|
|
|
80
80
|
const nativeArtifacts = (): ArtifactUploadNative | null => {
|
|
81
81
|
const native = (
|
|
82
82
|
globalThis as unknown as { expo?: { modules?: Record<string, unknown> } }
|
|
83
|
-
).expo?.modules?.
|
|
83
|
+
).expo?.modules?.KeepSnappy as Partial<ArtifactUploadNative> | undefined;
|
|
84
84
|
if (!native || typeof native.artifactUpload !== 'function') {
|
|
85
85
|
return null;
|
|
86
86
|
}
|
|
@@ -88,12 +88,12 @@ const nativeArtifacts = (): ArtifactUploadNative | null => {
|
|
|
88
88
|
return { artifactUpload: (...args) => artifactUpload.apply(native, args) };
|
|
89
89
|
};
|
|
90
90
|
|
|
91
|
-
// The journal file next to the log (
|
|
91
|
+
// The journal file next to the log (keepsnappy/push-spool.jsonl). Without these
|
|
92
92
|
// native functions (an older binary) the queue is memory-only, as before.
|
|
93
93
|
const nativeSpool = (): PushSpool | null => {
|
|
94
94
|
const native = (
|
|
95
95
|
globalThis as unknown as { expo?: { modules?: Record<string, unknown> } }
|
|
96
|
-
).expo?.modules?.
|
|
96
|
+
).expo?.modules?.KeepSnappy as SpoolNative | undefined;
|
|
97
97
|
if (
|
|
98
98
|
!native ||
|
|
99
99
|
typeof native.spoolRead !== 'function' ||
|
package/js/sdk/push.ts
CHANGED
|
@@ -630,10 +630,10 @@ export const createPushClient = (deps: PushDeps) => {
|
|
|
630
630
|
});
|
|
631
631
|
const headers: Record<string, string> = {
|
|
632
632
|
'content-type': 'application/json',
|
|
633
|
-
'x-
|
|
633
|
+
'x-keepsnappy-key': deps.key,
|
|
634
634
|
};
|
|
635
635
|
if (runToken) {
|
|
636
|
-
headers['x-
|
|
636
|
+
headers['x-keepsnappy-run-token'] = runToken;
|
|
637
637
|
}
|
|
638
638
|
const budget = flushLimit !== null ? Math.max(1, Math.min(timeoutMs, flushLimit - deps.now())) : timeoutMs;
|
|
639
639
|
const controller =
|
package/js/sdk/sampler.ts
CHANGED
|
@@ -84,7 +84,7 @@ export const SAMPLER_MS_MIN = 250;
|
|
|
84
84
|
export const SAMPLER_MS_MAX = 60_000;
|
|
85
85
|
// Background interval between native samples. 4 s: Arek 2026-10-16 (followups, RULING ~09:00); windows get their own
|
|
86
86
|
// samples at open and close, so the background ones only serve peak memory in long windows and the heat trend.
|
|
87
|
-
// The native samplers keep the same number as their unconfigured fallback (
|
|
87
|
+
// The native samplers keep the same number as their unconfigured fallback (KeepSnappySampler.swift / .kt).
|
|
88
88
|
export const SAMPLER_MS_DEFAULT = 4000;
|
|
89
89
|
|
|
90
90
|
export const parseSamplerMode = (
|
package/js/zustand-vanilla.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// zustand/vanilla in a
|
|
1
|
+
// zustand/vanilla in a KeepSnappy bundle. withKeepSnappy (metro.js) resolves every
|
|
2
2
|
// `zustand/vanilla` request to this module (zustand 5's index, react and
|
|
3
3
|
// traditional entries all load it by that name), and the name below to the real
|
|
4
4
|
// one. Each store's setState goes through the cause tracker's store writer
|
|
@@ -10,7 +10,7 @@ var real = require('@keepsnappy/react-native/zustand/vanilla');
|
|
|
10
10
|
|
|
11
11
|
var wrap = function (createState) {
|
|
12
12
|
return function (set, get, api) {
|
|
13
|
-
var stores = globalThis.
|
|
13
|
+
var stores = globalThis.__KEEPSNAPPY_STORES__;
|
|
14
14
|
if (!stores) {
|
|
15
15
|
return createState(set, get, api);
|
|
16
16
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keepsnappy/react-native",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.13",
|
|
4
4
|
"description": "KeepSnappy in-app SDK for React Native / Expo: zero-code measurement windows, React commit hook, native signposts and log file",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|