@keepsnappy/react-native 0.0.0-stage → 0.1.0-alpha.11
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 +260 -2
- package/android/build.gradle +35 -0
- package/android/src/main/java/expo/modules/perflab/PerfLabModule.kt +445 -0
- package/android/src/main/java/expo/modules/perflab/PerfLabSampler.kt +515 -0
- package/app.plugin.js +105 -0
- package/expo-module.config.json +9 -0
- package/ios/PerfLab.podspec +29 -0
- package/ios/PerfLabHermes.h +18 -0
- package/ios/PerfLabHermes.mm +64 -0
- package/ios/PerfLabModule.swift +371 -0
- package/ios/PerfLabSampler.swift +364 -0
- package/js/babel-transformer.js +71 -0
- package/js/hook.polyfill.js +333 -0
- package/js/renderer-switch.js +16 -0
- package/js/sdk/activation.ts +375 -0
- package/js/sdk/active.ts +62 -0
- package/js/sdk/artifacts.ts +632 -0
- package/js/sdk/boot.ts +21 -0
- package/js/sdk/causes.ts +932 -0
- package/js/sdk/core.ts +2403 -0
- package/js/sdk/cpuprof.ts +133 -0
- package/js/sdk/dormant.ts +77 -0
- package/js/sdk/env.d.ts +2 -0
- package/js/sdk/fiber.ts +591 -0
- package/js/sdk/interactions.ts +531 -0
- package/js/sdk/links.ts +201 -0
- package/js/sdk/nav.ts +383 -0
- package/js/sdk/net-install.ts +49 -0
- package/js/sdk/net.ts +684 -0
- package/js/sdk/profile-export.ts +257 -0
- package/js/sdk/profile.ts +539 -0
- package/js/sdk/push-install.ts +158 -0
- package/js/sdk/push.ts +819 -0
- package/js/sdk/retain-floor.ts +11 -0
- package/js/sdk/sampler.ts +236 -0
- package/js/sdk/screens.ts +586 -0
- package/js/sdk/stream.ts +125 -0
- package/js/zustand-vanilla.js +31 -0
- package/metro.js +238 -0
- package/package.json +26 -4
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
// Build mode and per-run activation. Loaded in every flag-on build, dormant or
|
|
2
|
+
// not, so it stays small and imports nothing: no React Native, no core.
|
|
3
|
+
//
|
|
4
|
+
// Modes (bundle time):
|
|
5
|
+
// auto - measuring from the first frame (EXPO_PUBLIC_PERF_LAB_MODE=auto or PERF_LAB_AUTO=1)
|
|
6
|
+
// manual - markers only until a settings link turns collection on (no mode set)
|
|
7
|
+
// dormant - nothing installed until an activation link; it applies from the NEXT launch
|
|
8
|
+
//
|
|
9
|
+
// The activation record lives in the app sandbox (`perf-lab/activation.json`,
|
|
10
|
+
// written by the native module). hook.polyfill.js reads it once, synchronously,
|
|
11
|
+
// before React loads, and leaves the raw value in __PERFLAB_ACTIVATION__.
|
|
12
|
+
// Its `profile` field picks the React renderer in a PERF_LAB_REACT_PROFILING=switch
|
|
13
|
+
// bundle (js/renderer-switch.js), so a process is wholly one renderer.
|
|
14
|
+
|
|
15
|
+
export const PERF_LAB_ENABLED = (process.env.EXPO_PUBLIC_KEEPSNAPPY ?? process.env.EXPO_PUBLIC_PERF_LAB) === '1';
|
|
16
|
+
|
|
17
|
+
export const PERF_LAB_DEEP_LINK_PATH = 'devtools/perf-lab';
|
|
18
|
+
|
|
19
|
+
export type PerfLabMode = 'auto' | 'manual' | 'dormant';
|
|
20
|
+
|
|
21
|
+
export const resolveMode = (
|
|
22
|
+
mode: string | undefined,
|
|
23
|
+
auto: string | undefined,
|
|
24
|
+
): PerfLabMode => {
|
|
25
|
+
if (mode === 'dormant' || mode === 'auto') {
|
|
26
|
+
return mode;
|
|
27
|
+
}
|
|
28
|
+
return auto === '1' ? 'auto' : 'manual';
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const MODE: PerfLabMode = resolveMode(
|
|
32
|
+
(process.env.EXPO_PUBLIC_KEEPSNAPPY_MODE ?? process.env.EXPO_PUBLIC_PERF_LAB_MODE),
|
|
33
|
+
(process.env.EXPO_PUBLIC_KEEPSNAPPY_AUTO ?? process.env.EXPO_PUBLIC_PERF_LAB_AUTO),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
export const DEFAULT_TTL_S = 7200;
|
|
37
|
+
// A run token lives 6 h on the server; an activation never outlives it.
|
|
38
|
+
export const MAX_TTL_S = 21600;
|
|
39
|
+
|
|
40
|
+
export interface ActivationRecord {
|
|
41
|
+
active: boolean;
|
|
42
|
+
runToken: string | null;
|
|
43
|
+
// Wall clock, ms since epoch.
|
|
44
|
+
expiresAt: number;
|
|
45
|
+
// Diagnostic iteration: every raw profile file of the run is kept and uploaded.
|
|
46
|
+
diag: boolean;
|
|
47
|
+
// React profiling renderer (switch bundles) plus update causes and render times.
|
|
48
|
+
profile: boolean;
|
|
49
|
+
// Hermes sampling rate in Hz, 0 = off.
|
|
50
|
+
cpuprof: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type ActivationRead = 'polyfill' | 'boot' | 'unavailable' | 'none';
|
|
54
|
+
|
|
55
|
+
export interface Decision {
|
|
56
|
+
mode: PerfLabMode;
|
|
57
|
+
active: boolean;
|
|
58
|
+
runToken: string | null;
|
|
59
|
+
expiresAt: number | null;
|
|
60
|
+
diag: boolean;
|
|
61
|
+
profile: boolean;
|
|
62
|
+
cpuprof: number;
|
|
63
|
+
read: ActivationRead;
|
|
64
|
+
// The record as read at startup; links in this process update a copy.
|
|
65
|
+
record: ActivationRecord | null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ActivationNative {
|
|
69
|
+
activationRead(): string | null;
|
|
70
|
+
activationWrite(json: string): boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// What the polyfill leaves behind (see hook.polyfill.js).
|
|
74
|
+
export interface PolyfillActivation {
|
|
75
|
+
mode: string;
|
|
76
|
+
read: 'polyfill' | 'deferred' | 'none';
|
|
77
|
+
// Android only: ms the polyfill's synchronous ExpoModulesCore.installModules() took.
|
|
78
|
+
installMs?: number;
|
|
79
|
+
raw: string | null;
|
|
80
|
+
active: boolean;
|
|
81
|
+
// Load the profiling renderer (switch bundles). Set once, before React loads.
|
|
82
|
+
profile: boolean;
|
|
83
|
+
// Which renderer the switch module loaded, if any.
|
|
84
|
+
renderer?: 'prod' | 'profiling';
|
|
85
|
+
t: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const CPU_HZ_MIN = 10;
|
|
89
|
+
export const CPU_HZ_MAX = 10000;
|
|
90
|
+
export const DEFAULT_CPU_HZ = 250;
|
|
91
|
+
|
|
92
|
+
const hzOf = (value: unknown) =>
|
|
93
|
+
typeof value === 'number' && Number.isFinite(value) && value > 0
|
|
94
|
+
? Math.min(CPU_HZ_MAX, Math.max(CPU_HZ_MIN, Math.round(value)))
|
|
95
|
+
: 0;
|
|
96
|
+
|
|
97
|
+
export const parseRecord = (
|
|
98
|
+
raw: string | null | undefined,
|
|
99
|
+
): ActivationRecord | null => {
|
|
100
|
+
if (typeof raw !== 'string' || raw === '') {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const value = JSON.parse(raw) as Record<string, unknown>;
|
|
105
|
+
if (!value || typeof value !== 'object') {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
active: value.active === true,
|
|
110
|
+
runToken:
|
|
111
|
+
typeof value.runToken === 'string' && value.runToken !== ''
|
|
112
|
+
? value.runToken
|
|
113
|
+
: null,
|
|
114
|
+
expiresAt: typeof value.expiresAt === 'number' ? value.expiresAt : 0,
|
|
115
|
+
diag: value.diag === true,
|
|
116
|
+
profile: value.profile === true,
|
|
117
|
+
cpuprof: hzOf(value.cpuprof),
|
|
118
|
+
};
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const live = (record: ActivationRecord | null, nowMs: number) =>
|
|
125
|
+
record !== null && record.expiresAt > nowMs;
|
|
126
|
+
|
|
127
|
+
const OFF: ActivationRecord = {
|
|
128
|
+
active: false,
|
|
129
|
+
runToken: null,
|
|
130
|
+
expiresAt: 0,
|
|
131
|
+
diag: false,
|
|
132
|
+
profile: false,
|
|
133
|
+
cpuprof: 0,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Must agree with activeRecord() in hook.polyfill.js (tested on the same fixtures).
|
|
137
|
+
export const decide = (
|
|
138
|
+
mode: PerfLabMode,
|
|
139
|
+
record: ActivationRecord | null,
|
|
140
|
+
nowMs: number,
|
|
141
|
+
read: ActivationRead,
|
|
142
|
+
): Decision => {
|
|
143
|
+
if (mode === 'dormant') {
|
|
144
|
+
const active = live(record, nowMs) && record?.active === true;
|
|
145
|
+
return {
|
|
146
|
+
mode,
|
|
147
|
+
active,
|
|
148
|
+
runToken: active ? (record?.runToken ?? null) : null,
|
|
149
|
+
expiresAt: active ? (record?.expiresAt ?? null) : null,
|
|
150
|
+
diag: active && record?.diag === true,
|
|
151
|
+
profile: active && record?.profile === true,
|
|
152
|
+
cpuprof: active ? (record?.cpuprof ?? 0) : 0,
|
|
153
|
+
read,
|
|
154
|
+
record,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// auto / manual measure anyway; a persisted run token still applies until it expires.
|
|
158
|
+
const token = live(record, nowMs) ? (record?.runToken ?? null) : null;
|
|
159
|
+
return {
|
|
160
|
+
mode,
|
|
161
|
+
active: true,
|
|
162
|
+
runToken: token,
|
|
163
|
+
expiresAt: token ? (record?.expiresAt ?? null) : null,
|
|
164
|
+
diag: token !== null && record?.diag === true,
|
|
165
|
+
profile: token !== null && record?.profile === true,
|
|
166
|
+
cpuprof: token !== null ? (record?.cpuprof ?? 0) : 0,
|
|
167
|
+
read,
|
|
168
|
+
record,
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const ttlMs = (value: string | undefined) => {
|
|
173
|
+
const parsed = Number(value);
|
|
174
|
+
const seconds =
|
|
175
|
+
value === undefined || value === '' || !Number.isFinite(parsed) || parsed <= 0
|
|
176
|
+
? DEFAULT_TTL_S
|
|
177
|
+
: Math.min(MAX_TTL_S, parsed);
|
|
178
|
+
return seconds * 1000;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const truthy = (value: string) => value === '1' || value === 'true';
|
|
182
|
+
|
|
183
|
+
// `cpuprof=1`/`true` samples at `cpuhz` (or 250 Hz); a number above 1 is the
|
|
184
|
+
// rate itself; `0` is off.
|
|
185
|
+
const cpuprofFromLink = (params: Record<string, string>) => {
|
|
186
|
+
const value = params.cpuprof;
|
|
187
|
+
if (value === undefined || value === '0' || value === 'false') {
|
|
188
|
+
return 0;
|
|
189
|
+
}
|
|
190
|
+
if (truthy(value)) {
|
|
191
|
+
return hzOf(Number(params.cpuhz)) || DEFAULT_CPU_HZ;
|
|
192
|
+
}
|
|
193
|
+
return hzOf(Number(value));
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// The record a settings link asks for, or null when it does not touch activation.
|
|
197
|
+
// `activate=1` (with `runToken`, `ttl`, `diag`, `profile`, `cpuprof`, `cpuhz`),
|
|
198
|
+
// `activate=0`, or a bare `runToken` (keeps the current activation, empty
|
|
199
|
+
// clears the token; `diag`, `profile` and `cpuprof` kept unless given).
|
|
200
|
+
export const recordFromLink = (
|
|
201
|
+
params: Record<string, string>,
|
|
202
|
+
current: ActivationRecord | null,
|
|
203
|
+
nowMs: number,
|
|
204
|
+
): ActivationRecord | null => {
|
|
205
|
+
if (params.activate !== undefined) {
|
|
206
|
+
if (truthy(params.activate)) {
|
|
207
|
+
return {
|
|
208
|
+
active: true,
|
|
209
|
+
runToken: params.runToken ? params.runToken : null,
|
|
210
|
+
expiresAt: nowMs + ttlMs(params.ttl),
|
|
211
|
+
diag: params.diag !== undefined && truthy(params.diag),
|
|
212
|
+
profile: params.profile !== undefined && truthy(params.profile),
|
|
213
|
+
cpuprof: cpuprofFromLink(params),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return { ...OFF };
|
|
217
|
+
}
|
|
218
|
+
if (params.runToken !== undefined) {
|
|
219
|
+
const kept = live(current, nowMs) ? current : null;
|
|
220
|
+
return {
|
|
221
|
+
active: kept?.active === true,
|
|
222
|
+
runToken: params.runToken === '' ? null : params.runToken,
|
|
223
|
+
expiresAt: nowMs + ttlMs(params.ttl),
|
|
224
|
+
diag: params.diag !== undefined ? truthy(params.diag) : kept?.diag === true,
|
|
225
|
+
profile:
|
|
226
|
+
params.profile !== undefined ? truthy(params.profile) : kept?.profile === true,
|
|
227
|
+
cpuprof:
|
|
228
|
+
params.cpuprof !== undefined ? cpuprofFromLink(params) : (kept?.cpuprof ?? 0),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
return null;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export const parseQuery = (
|
|
235
|
+
query: string | undefined,
|
|
236
|
+
): Record<string, string> => {
|
|
237
|
+
if (!query) {
|
|
238
|
+
return {};
|
|
239
|
+
}
|
|
240
|
+
const out: Record<string, string> = {};
|
|
241
|
+
for (const pair of query.split('&')) {
|
|
242
|
+
const [key = '', ...rest] = pair.split('=');
|
|
243
|
+
try {
|
|
244
|
+
out[decodeURIComponent(key)] = decodeURIComponent(rest.join('='));
|
|
245
|
+
} catch {
|
|
246
|
+
out[key] = rest.join('=');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return out;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
export const queryOf = (url: string) => parseQuery(url.split('?')[1]);
|
|
253
|
+
|
|
254
|
+
interface ActivationGlobal {
|
|
255
|
+
__PERFLAB_ACTIVATION__?: PolyfillActivation;
|
|
256
|
+
__PERFLAB_HOOK__?: { ensure: () => string };
|
|
257
|
+
expo?: { modules?: Record<string, unknown> };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const g = globalThis as unknown as ActivationGlobal;
|
|
261
|
+
|
|
262
|
+
const isNative = (value: unknown): value is ActivationNative =>
|
|
263
|
+
!!value &&
|
|
264
|
+
typeof (value as ActivationNative).activationRead === 'function' &&
|
|
265
|
+
typeof (value as ActivationNative).activationWrite === 'function';
|
|
266
|
+
|
|
267
|
+
// Found lazily: only links and a deferred startup read need it.
|
|
268
|
+
export const findActivationNative = (): ActivationNative | null => {
|
|
269
|
+
const direct = g.expo?.modules?.PerfLab;
|
|
270
|
+
if (isNative(direct)) {
|
|
271
|
+
return direct;
|
|
272
|
+
}
|
|
273
|
+
if (g.expo?.modules) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
// Android may install `expo.modules` only when expo-modules-core first loads.
|
|
277
|
+
try {
|
|
278
|
+
const core = require('expo-modules-core') as {
|
|
279
|
+
requireOptionalNativeModule?: (name: string) => unknown;
|
|
280
|
+
};
|
|
281
|
+
const found = core.requireOptionalNativeModule?.('PerfLab');
|
|
282
|
+
return isNative(found) ? found : null;
|
|
283
|
+
} catch {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
// One decision per process, taken by the boot module.
|
|
289
|
+
export const resolveDecision = (
|
|
290
|
+
nowMs: number = Date.now(),
|
|
291
|
+
findNative: () => ActivationNative | null = findActivationNative,
|
|
292
|
+
): Decision => {
|
|
293
|
+
const fromPolyfill = g.__PERFLAB_ACTIVATION__;
|
|
294
|
+
if (MODE !== 'dormant') {
|
|
295
|
+
// Not gated: the record only carries a persisted token, read when it is cheap.
|
|
296
|
+
if (fromPolyfill && fromPolyfill.read === 'polyfill') {
|
|
297
|
+
return decide(MODE, parseRecord(fromPolyfill.raw), fromPolyfill.t, 'polyfill');
|
|
298
|
+
}
|
|
299
|
+
let raw: string | null = null;
|
|
300
|
+
let read: ActivationRead = 'unavailable';
|
|
301
|
+
try {
|
|
302
|
+
const native = findNative();
|
|
303
|
+
if (native) {
|
|
304
|
+
raw = native.activationRead();
|
|
305
|
+
read = 'boot';
|
|
306
|
+
}
|
|
307
|
+
} catch {
|
|
308
|
+
read = 'unavailable';
|
|
309
|
+
}
|
|
310
|
+
return decide(MODE, parseRecord(raw), nowMs, read);
|
|
311
|
+
}
|
|
312
|
+
if (fromPolyfill && fromPolyfill.read === 'polyfill') {
|
|
313
|
+
return decide(MODE, parseRecord(fromPolyfill.raw), fromPolyfill.t, 'polyfill');
|
|
314
|
+
}
|
|
315
|
+
// The polyfill could not reach the native module: read now, still before the
|
|
316
|
+
// React renderer loads (it loads when the app's root component first renders).
|
|
317
|
+
let decision: Decision;
|
|
318
|
+
try {
|
|
319
|
+
const native = findNative();
|
|
320
|
+
decision = native
|
|
321
|
+
? decide(MODE, parseRecord(native.activationRead()), nowMs, 'boot')
|
|
322
|
+
: decide(MODE, null, nowMs, 'unavailable');
|
|
323
|
+
} catch {
|
|
324
|
+
decision = decide(MODE, null, nowMs, 'unavailable');
|
|
325
|
+
}
|
|
326
|
+
if (decision.active) {
|
|
327
|
+
g.__PERFLAB_HOOK__?.ensure();
|
|
328
|
+
}
|
|
329
|
+
// The polyfill could not choose the renderer; boot still runs before it loads.
|
|
330
|
+
if (fromPolyfill && fromPolyfill.read === 'deferred') {
|
|
331
|
+
fromPolyfill.profile = decision.profile;
|
|
332
|
+
}
|
|
333
|
+
return decision;
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// Links in this process change the record for the NEXT launch only.
|
|
337
|
+
export const createActivationStore = (
|
|
338
|
+
initial: ActivationRecord | null,
|
|
339
|
+
findNative: () => ActivationNative | null = findActivationNative,
|
|
340
|
+
) => {
|
|
341
|
+
let current = initial;
|
|
342
|
+
let writes = 0;
|
|
343
|
+
let lastError: string | null = null;
|
|
344
|
+
return {
|
|
345
|
+
current: () => current,
|
|
346
|
+
stats: () => ({ writes, lastError }),
|
|
347
|
+
// Returns the written record, or null when the link does not touch activation.
|
|
348
|
+
apply(params: Record<string, string>, nowMs: number = Date.now()) {
|
|
349
|
+
const next = recordFromLink(params, current, nowMs);
|
|
350
|
+
if (!next) {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
try {
|
|
354
|
+
const native = findNative();
|
|
355
|
+
if (!native) {
|
|
356
|
+
lastError = 'native module unavailable';
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
if (!native.activationWrite(JSON.stringify(next))) {
|
|
360
|
+
lastError = 'write failed';
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
current = next;
|
|
364
|
+
writes++;
|
|
365
|
+
lastError = null;
|
|
366
|
+
return next;
|
|
367
|
+
} catch (error) {
|
|
368
|
+
lastError = String(error);
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
export type ActivationStore = ReturnType<typeof createActivationStore>;
|
package/js/sdk/active.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// The measuring SDK. boot.ts requires this module only when the process
|
|
2
|
+
// measures: always in auto/manual builds, after an activation in dormant ones.
|
|
3
|
+
|
|
4
|
+
import { createActivationStore, type Decision } from './activation';
|
|
5
|
+
import {
|
|
6
|
+
addStartupSection,
|
|
7
|
+
applySettings,
|
|
8
|
+
attachHook,
|
|
9
|
+
exposeGlobal,
|
|
10
|
+
handlePerfLabDeepLink,
|
|
11
|
+
logStartup,
|
|
12
|
+
perfLog,
|
|
13
|
+
setActivation,
|
|
14
|
+
} from './core';
|
|
15
|
+
import { causeStats, installCauses } from './causes';
|
|
16
|
+
import { installInteractions, interactionStats } from './interactions';
|
|
17
|
+
import { installLinks, linkPatchStatus, redactUrl } from './links';
|
|
18
|
+
import { installNav } from './nav';
|
|
19
|
+
import { installNet } from './net-install';
|
|
20
|
+
import { installPush } from './push-install';
|
|
21
|
+
import { installScreens, noteAppLink, screenStats } from './screens';
|
|
22
|
+
|
|
23
|
+
const guarded = (label: string, run: () => void) => {
|
|
24
|
+
try {
|
|
25
|
+
run();
|
|
26
|
+
} catch (error) {
|
|
27
|
+
globalThis.console.log(
|
|
28
|
+
`PERF_LAB boot step failed ${label} ${String(error)}`,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const startActive = (decision: Decision) => {
|
|
34
|
+
setActivation(decision, createActivationStore(decision.record));
|
|
35
|
+
// First: it wraps the timers and the scheduler before the app's modules and the renderer read them.
|
|
36
|
+
guarded('causes', () => installCauses());
|
|
37
|
+
guarded('hook', attachHook);
|
|
38
|
+
guarded('global', exposeGlobal);
|
|
39
|
+
guarded('settings', () => applySettings({}));
|
|
40
|
+
guarded('links', () =>
|
|
41
|
+
installLinks(
|
|
42
|
+
(url, via) => {
|
|
43
|
+
perfLog(`link via=${via} ${redactUrl(url)}`);
|
|
44
|
+
handlePerfLabDeepLink(url);
|
|
45
|
+
},
|
|
46
|
+
noteAppLink,
|
|
47
|
+
),
|
|
48
|
+
);
|
|
49
|
+
guarded('interactions', installInteractions);
|
|
50
|
+
guarded('net', installNet);
|
|
51
|
+
guarded('nav', installNav);
|
|
52
|
+
guarded('screens', installScreens);
|
|
53
|
+
guarded('push', installPush);
|
|
54
|
+
addStartupSection('causes', () => ({ ...causeStats }));
|
|
55
|
+
addStartupSection('interactions', interactionStats);
|
|
56
|
+
addStartupSection('screens', screenStats);
|
|
57
|
+
addStartupSection('links', linkPatchStatus);
|
|
58
|
+
logStartup('boot');
|
|
59
|
+
// By now the renderer has injected into the hook and Expo has installed its
|
|
60
|
+
// native modules, so the second report shows both.
|
|
61
|
+
setTimeout(() => logStartup('boot+5s'), 5000);
|
|
62
|
+
};
|