@keepsnappy/react-native 0.1.0-alpha.15 → 0.1.0-alpha.17
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 +12 -0
- package/js/babel-transformer.js +31 -6
- package/js/component-contract.cjs +14 -0
- package/js/component-stamps.js +207 -0
- package/js/sdk/bytes.ts +20 -0
- package/js/sdk/core.ts +92 -22
- package/js/sdk/device-model.ts +10 -0
- package/js/sdk/fiber.ts +352 -28
- package/js/sdk/interactions.ts +38 -24
- package/js/sdk/place.ts +25 -0
- package/js/sdk/profile-export.ts +15 -0
- package/js/sdk/profile.ts +272 -18
- package/js/sdk/push-install.ts +2 -1
- package/js/sdk/push.ts +70 -46
- package/js/sdk/sampler.ts +1 -1
- package/js/sdk/screens.ts +91 -6
- package/js/sdk/stream.ts +6 -0
- package/metro.js +11 -1
- package/package.json +1 -1
package/js/sdk/core.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
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
|
+
import { setPlaceLog } from './place';
|
|
5
|
+
|
|
4
6
|
import {
|
|
5
7
|
type ActivationStore,
|
|
6
8
|
type Decision,
|
|
@@ -12,6 +14,8 @@ import {
|
|
|
12
14
|
} from './activation';
|
|
13
15
|
import {
|
|
14
16
|
componentName,
|
|
17
|
+
clientKeyOf,
|
|
18
|
+
OUTSIDE_APP,
|
|
15
19
|
type Fiber,
|
|
16
20
|
type FiberRoot,
|
|
17
21
|
isComponentTag,
|
|
@@ -66,6 +70,7 @@ import {
|
|
|
66
70
|
type ProfiledFiber,
|
|
67
71
|
type ProfiledRoot,
|
|
68
72
|
recordFiber,
|
|
73
|
+
recordRolledUp,
|
|
69
74
|
selfMs,
|
|
70
75
|
summarizeWindowProfile,
|
|
71
76
|
totalMs,
|
|
@@ -364,6 +369,8 @@ export const perfLog = (message: string, sink: LogSink = settings.log) => {
|
|
|
364
369
|
}
|
|
365
370
|
}
|
|
366
371
|
};
|
|
372
|
+
setPlaceLog(perfLog);
|
|
373
|
+
|
|
367
374
|
|
|
368
375
|
// Requests started per window (fetch/XHR hooks in net-install.ts feed it).
|
|
369
376
|
export const net = createNetCounter({ now, onStart: noteRequestSent });
|
|
@@ -466,8 +473,10 @@ interface OpenSampleState {
|
|
|
466
473
|
// - so does a window that starts within OPEN_SAMPLE_TOLERANCE_MS of the close sample (restarted key);
|
|
467
474
|
// - a window detected long after its t0 (a screen window opened by a press, seen after the new screen rendered) gets
|
|
468
475
|
// none: its bound is the press's open sample;
|
|
469
|
-
// - else the sampler is asked to sample on its own thread.
|
|
470
|
-
|
|
476
|
+
// - else the sampler is asked to sample on its own thread. A press always is, however late its touch reached JS: with
|
|
477
|
+
// no window before it (after a GC command, the background or the flush) the sample before the touch can be seconds
|
|
478
|
+
// old, so its own sample, a negative lead past t0, is the closer bound.
|
|
479
|
+
const openSampleFor = (id: number, t0: number, press: boolean): OpenSampleState => {
|
|
471
480
|
const none = { openRecord: null, openPending: null };
|
|
472
481
|
if (samplerState.mode === 'off') {
|
|
473
482
|
return none;
|
|
@@ -480,7 +489,7 @@ const openSampleFor = (id: number, t0: number): OpenSampleState => {
|
|
|
480
489
|
) {
|
|
481
490
|
return { openRecord: lastClose.record, openPending: null };
|
|
482
491
|
}
|
|
483
|
-
if (now() - samplerState.offsetMs - t0Native > OPEN_SAMPLE_TOLERANCE_MS) {
|
|
492
|
+
if (!press && now() - samplerState.offsetMs - t0Native > OPEN_SAMPLE_TOLERANCE_MS) {
|
|
484
493
|
return none;
|
|
485
494
|
}
|
|
486
495
|
const reason = `window-start:${id}`;
|
|
@@ -611,6 +620,8 @@ const diffStats = (
|
|
|
611
620
|
export type WindowKind = 'marker' | 'press' | 'screen' | 'background';
|
|
612
621
|
|
|
613
622
|
interface MarkerWindow {
|
|
623
|
+
legacyName: string | null;
|
|
624
|
+
beforeEnd: (() => void) | null;
|
|
614
625
|
id: number;
|
|
615
626
|
key: string;
|
|
616
627
|
name: string;
|
|
@@ -839,9 +850,12 @@ export const lastCommitAt = (key: string) =>
|
|
|
839
850
|
export const isWindowOpen = (key?: string) =>
|
|
840
851
|
key ? openWindows.has(key) : openWindows.size > 0;
|
|
841
852
|
|
|
842
|
-
const traceName = (name: string) => `keepsnappy:${name}
|
|
853
|
+
const traceName = (name: string) => `keepsnappy:${name}`.slice(0, 120);
|
|
854
|
+
const fileNamePart = (name: string) => name.replace(/[^\w.:+-]/g, '_').slice(0, 64);
|
|
843
855
|
|
|
844
856
|
interface StartOptions {
|
|
857
|
+
legacyName?: string | null;
|
|
858
|
+
beforeEnd?: () => void;
|
|
845
859
|
key?: string;
|
|
846
860
|
kind?: WindowKind;
|
|
847
861
|
// Backdates t0 (e.g. to the hardware touch time). performance.now() timebase.
|
|
@@ -879,6 +893,8 @@ const backgroundWindow = (source: string, t: number): MarkerWindow => {
|
|
|
879
893
|
key: name,
|
|
880
894
|
name,
|
|
881
895
|
kind: 'background',
|
|
896
|
+
legacyName: null,
|
|
897
|
+
beforeEnd: null,
|
|
882
898
|
trace: traceName(name),
|
|
883
899
|
t0: t,
|
|
884
900
|
t1: null,
|
|
@@ -924,7 +940,14 @@ const routeToBackground = (record: CommitRecord) => {
|
|
|
924
940
|
|
|
925
941
|
// Segments written and not yet listed for upload (listed at the next flush).
|
|
926
942
|
const pendingStreamFiles: WrittenFile[] = [];
|
|
927
|
-
//
|
|
943
|
+
// What the run lost since the last flush, which reports it. Commit-stream segments that failed to write are missing data
|
|
944
|
+
// (the backend splits work by cause from them). Kept React exports dropped at the run cap or not written, and stream fibers
|
|
945
|
+
// past a segment's cap, leave every measured value complete: counted notes only.
|
|
946
|
+
const runLosses = { stream: 0, exportsCapped: 0, exportsUnwritten: 0, streamFibersDropped: 0 };
|
|
947
|
+
export const setStreamFiberCapForTest = (fibers: number) => commitStream.setMaxFibers(fibers);
|
|
948
|
+
// A segment is cut at this many commits even between flushes, so a long stretch outside a run stays bounded. In a run
|
|
949
|
+
// the cut waits while a window is open (no file I/O inside a measured window, REQ-MEAS-23): every window ends at the
|
|
950
|
+
// flush, which cuts it, and a segment past its fiber cap keeps only the commits' facts.
|
|
928
951
|
const MAX_SEGMENT_COMMITS = 5000;
|
|
929
952
|
|
|
930
953
|
const cutStream = () => {
|
|
@@ -940,7 +963,10 @@ const cutStream = () => {
|
|
|
940
963
|
}
|
|
941
964
|
if (path) {
|
|
942
965
|
pendingStreamFiles.push({ name: segment.name, windows: [], bytes: segment.json.length });
|
|
966
|
+
} else if (inRun()) {
|
|
967
|
+
runLosses.stream++;
|
|
943
968
|
}
|
|
969
|
+
if (inRun()) runLosses.streamFibersDropped += segment.fibersDropped;
|
|
944
970
|
perfLog(`stream ${JSON.stringify({ name: segment.name, commits: segment.commits, bytes: segment.json.length, fibersDropped: segment.fibersDropped, written: path !== null })}`);
|
|
945
971
|
};
|
|
946
972
|
|
|
@@ -984,12 +1010,14 @@ const start = (name: string, options: StartOptions = {}) => {
|
|
|
984
1010
|
const statsStart = readHermesStats();
|
|
985
1011
|
const id = nextWindowId++;
|
|
986
1012
|
const startT = options.at ?? now();
|
|
987
|
-
const openSample = openSampleFor(id, startT);
|
|
1013
|
+
const openSample = openSampleFor(id, startT, options.kind === 'press');
|
|
988
1014
|
const window: MarkerWindow = {
|
|
989
1015
|
id,
|
|
990
1016
|
key,
|
|
991
1017
|
name,
|
|
992
1018
|
kind: options.kind ?? 'marker',
|
|
1019
|
+
legacyName: options.legacyName ?? null,
|
|
1020
|
+
beforeEnd: options.beforeEnd ?? null,
|
|
993
1021
|
trace: traceName(name),
|
|
994
1022
|
t0: startT,
|
|
995
1023
|
t1: null,
|
|
@@ -1043,6 +1071,7 @@ const end = (key: string, reason = 'end', at?: number) => {
|
|
|
1043
1071
|
if (!window) {
|
|
1044
1072
|
return;
|
|
1045
1073
|
}
|
|
1074
|
+
window.beforeEnd?.();
|
|
1046
1075
|
window.t1 = at ?? now();
|
|
1047
1076
|
window.endReason = reason;
|
|
1048
1077
|
openWindows.delete(key);
|
|
@@ -1086,8 +1115,9 @@ const endAll = (reason: string) => {
|
|
|
1086
1115
|
}
|
|
1087
1116
|
};
|
|
1088
1117
|
|
|
1089
|
-
const rename = (key: string, name: string) => {
|
|
1118
|
+
const rename = (key: string, name: string, legacyName?: string | null) => {
|
|
1090
1119
|
const window = openWindows.get(key);
|
|
1120
|
+
if (window && legacyName !== undefined) window.legacyName = legacyName;
|
|
1091
1121
|
if (window && window.name !== name) {
|
|
1092
1122
|
perfLog(`PERF_MARK ${window.name} renamed=${name} id=${window.id}`);
|
|
1093
1123
|
window.name = name;
|
|
@@ -1135,30 +1165,43 @@ const walkCommit = (
|
|
|
1135
1165
|
causes: CommitCauses,
|
|
1136
1166
|
) => {
|
|
1137
1167
|
const stack: Fiber[] = [root.current];
|
|
1168
|
+
const owners: string[] = [OUTSIDE_APP];
|
|
1169
|
+
const parents: (string | null)[] = [null];
|
|
1138
1170
|
const renders: Record<string, number> = {};
|
|
1139
1171
|
let mounts = 0;
|
|
1140
1172
|
let visited = 0;
|
|
1141
1173
|
const ancestors = exported ? createAncestorPaths() : null;
|
|
1142
1174
|
while (stack.length > 0) {
|
|
1143
1175
|
const fiber = stack.pop() as Fiber;
|
|
1176
|
+
const inherited = owners.pop() as string;
|
|
1177
|
+
const inheritedParent = parents.pop() as string | null;
|
|
1178
|
+
let owner = inherited, parentOfOwner = inheritedParent;
|
|
1144
1179
|
visited++;
|
|
1145
1180
|
const mounted = fiber.alternate === null;
|
|
1146
1181
|
if (mounted) {
|
|
1147
1182
|
noteMounted(fiber);
|
|
1148
1183
|
}
|
|
1149
1184
|
noteHostFiber(fiber, causes);
|
|
1150
|
-
|
|
1185
|
+
const component = isComponentTag(fiber.tag);
|
|
1186
|
+
const own = component ? clientKeyOf(fiber) : null;
|
|
1187
|
+
if (own !== null) {
|
|
1188
|
+
owner = own;
|
|
1189
|
+
parentOfOwner = inherited === OUTSIDE_APP ? null : inherited;
|
|
1190
|
+
}
|
|
1191
|
+
if (component && (fiber.flags & PERFORMED_WORK) !== 0) {
|
|
1151
1192
|
const name = componentName(fiber);
|
|
1152
1193
|
renders[name] = (renders[name] ?? 0) + 1;
|
|
1153
1194
|
if (mounted) {
|
|
1154
1195
|
mounts++;
|
|
1155
1196
|
}
|
|
1156
|
-
if (profile || exported) {
|
|
1197
|
+
if ((profile && own !== null) || exported) {
|
|
1157
1198
|
const cause = updateCause(fiber as ProfiledFiber);
|
|
1158
|
-
if (profile) {
|
|
1159
|
-
recordFiber(profile,
|
|
1199
|
+
if (profile && own !== null) {
|
|
1200
|
+
recordFiber(profile, own, fiber as ProfiledFiber, cause, fiberId(fiber), parentOfOwner);
|
|
1160
1201
|
}
|
|
1161
1202
|
if (exported) {
|
|
1203
|
+
// Profiled exports assign the rendered id before fresh ancestor ids.
|
|
1204
|
+
if (profile && own === null) fiberId(fiber);
|
|
1162
1205
|
exported.fibers.push(
|
|
1163
1206
|
exportFiber(
|
|
1164
1207
|
fiber,
|
|
@@ -1167,10 +1210,14 @@ const walkCommit = (
|
|
|
1167
1210
|
totalMs(fiber as ProfiledFiber),
|
|
1168
1211
|
selfMs(fiber as ProfiledFiber),
|
|
1169
1212
|
(ancestors as (f: Fiber) => number[])(fiber),
|
|
1213
|
+
owner,
|
|
1170
1214
|
),
|
|
1171
1215
|
);
|
|
1172
1216
|
}
|
|
1173
1217
|
}
|
|
1218
|
+
if (profile && own === null) {
|
|
1219
|
+
recordRolledUp(profile, inherited, inheritedParent, name, fiber as ProfiledFiber);
|
|
1220
|
+
}
|
|
1174
1221
|
}
|
|
1175
1222
|
// Children not cloned in this render are the previous commit's fibers and
|
|
1176
1223
|
// carry stale flags, so a bailed-out subtree is skipped entirely.
|
|
@@ -1180,6 +1227,8 @@ const walkCommit = (
|
|
|
1180
1227
|
}
|
|
1181
1228
|
for (let child = fiber.child; child !== null; child = child.sibling) {
|
|
1182
1229
|
stack.push(child);
|
|
1230
|
+
owners.push(owner);
|
|
1231
|
+
parents.push(parentOfOwner);
|
|
1183
1232
|
}
|
|
1184
1233
|
}
|
|
1185
1234
|
return { renders, mounts, visited };
|
|
@@ -1217,7 +1266,7 @@ const onCommitFiberRoot = (rendererId: number, root: FiberRoot) => {
|
|
|
1217
1266
|
// The figure `profile.renderMs` reads; null when the renderer does not time commits.
|
|
1218
1267
|
renderMs = isProfilingFiber(profiled.current) ? totalMs(profiled.current) : null;
|
|
1219
1268
|
if (settings.profile) {
|
|
1220
|
-
profile = createCommitProfile(profiled, seq);
|
|
1269
|
+
profile = createCommitProfile(profiled, seq, t0);
|
|
1221
1270
|
}
|
|
1222
1271
|
// Only commits a window can take are exported (open now, or backfilled
|
|
1223
1272
|
// into a window opened later with a backdated start).
|
|
@@ -1264,7 +1313,7 @@ const onCommitFiberRoot = (rendererId: number, root: FiberRoot) => {
|
|
|
1264
1313
|
// Work no open window judges is the background of its source: pushes, and loops of earlier actions or of the launch.
|
|
1265
1314
|
record.background = record.judged ? null : backgroundSource(causes);
|
|
1266
1315
|
commitStream.noteCommit({ seq, t: t0, renderMs, causes, fibers: exported?.fibers ?? null, updaters: profile?.updaters ?? null });
|
|
1267
|
-
if (commitStream.size() >= MAX_SEGMENT_COMMITS) {
|
|
1316
|
+
if (commitStream.size() >= MAX_SEGMENT_COMMITS && !(inRun() && openWindows.size > 0)) {
|
|
1268
1317
|
cutStream();
|
|
1269
1318
|
}
|
|
1270
1319
|
recordCommit(record);
|
|
@@ -1477,7 +1526,7 @@ const netForWindow = (window: MarkerWindow): NetWindow | null =>
|
|
|
1477
1526
|
window.net ? net.windowClose(window.id) : null;
|
|
1478
1527
|
|
|
1479
1528
|
const reactExportName = (window: MarkerWindow) =>
|
|
1480
|
-
`react-${PROCESS_ID}-${window.id}-${window.name
|
|
1529
|
+
`react-${PROCESS_ID}-${window.id}-${fileNamePart(window.name)}.json`;
|
|
1481
1530
|
|
|
1482
1531
|
interface PendingExport {
|
|
1483
1532
|
name: string;
|
|
@@ -1488,7 +1537,12 @@ interface PendingExport {
|
|
|
1488
1537
|
}
|
|
1489
1538
|
|
|
1490
1539
|
// React exports held until flush (inside a run), capped like a window's.
|
|
1491
|
-
|
|
1540
|
+
let maxPendingExportFibers = 400000;
|
|
1541
|
+
export const setPendingExportCapForTest = (fibers: number) => {
|
|
1542
|
+
const previous = maxPendingExportFibers;
|
|
1543
|
+
maxPendingExportFibers = fibers;
|
|
1544
|
+
return previous;
|
|
1545
|
+
};
|
|
1492
1546
|
const pendingExports: PendingExport[] = [];
|
|
1493
1547
|
let pendingExportFibers = 0;
|
|
1494
1548
|
|
|
@@ -1509,7 +1563,8 @@ const writeReactExport = (window: MarkerWindow) => {
|
|
|
1509
1563
|
truncated: window.exportTruncated,
|
|
1510
1564
|
};
|
|
1511
1565
|
if (inRun()) {
|
|
1512
|
-
if (pendingExportFibers + window.exportFibers >
|
|
1566
|
+
if (pendingExportFibers + window.exportFibers > maxPendingExportFibers) {
|
|
1567
|
+
if (settings.diag || budgetBreaks.has(window.id)) runLosses.exportsCapped++;
|
|
1513
1568
|
return { ...base, deferred: true, dropped: true };
|
|
1514
1569
|
}
|
|
1515
1570
|
pendingExports.push({
|
|
@@ -1586,6 +1641,7 @@ const writeDeferredFiles = (): ArtifactEntry[] => {
|
|
|
1586
1641
|
} catch (error) {
|
|
1587
1642
|
result = { error: String(error) };
|
|
1588
1643
|
}
|
|
1644
|
+
if (!result.path && (settings.diag || budgetBreaks.has(pending.window))) runLosses.exportsUnwritten++;
|
|
1589
1645
|
perfLog(
|
|
1590
1646
|
`rexport ${JSON.stringify({ name: pending.name, commits: pending.commits.length, fibers: pending.fibers, ...result })}`,
|
|
1591
1647
|
);
|
|
@@ -1720,7 +1776,7 @@ const finalizeWindow = (window: MarkerWindow) => {
|
|
|
1720
1776
|
// Sampler first: its records carry the display rate the frame budget needs.
|
|
1721
1777
|
const sampler = samplerForWindow(window);
|
|
1722
1778
|
const profile = window.profile
|
|
1723
|
-
? summarizeWindowProfile(window.profile, { budget: frameBudget })
|
|
1779
|
+
? summarizeWindowProfile(window.profile, { budget: frameBudget, t0: window.t0 })
|
|
1724
1780
|
: null;
|
|
1725
1781
|
const perf = settings.po ? summarizeEntries(window) : null;
|
|
1726
1782
|
noteBudgetBreak(
|
|
@@ -1734,6 +1790,7 @@ const finalizeWindow = (window: MarkerWindow) => {
|
|
|
1734
1790
|
windowModel: WINDOW_MODEL,
|
|
1735
1791
|
id: window.id,
|
|
1736
1792
|
name: window.name,
|
|
1793
|
+
...(window.legacyName !== null && window.legacyName !== window.name ? { legacyName: window.legacyName } : {}),
|
|
1737
1794
|
kind: window.kind,
|
|
1738
1795
|
t0: window.t0,
|
|
1739
1796
|
t1: window.t1,
|
|
@@ -1799,7 +1856,7 @@ const writeWindowRecord = (window: MarkerWindow, summary: Record<string, unknown
|
|
|
1799
1856
|
windowListener?.(`${Date.now()} KEEPSNAPPY window ${json}`);
|
|
1800
1857
|
if (settings.file) {
|
|
1801
1858
|
const path = getNative()?.writeText(
|
|
1802
|
-
`window-${window.id}-${window.name
|
|
1859
|
+
`window-${window.id}-${fileNamePart(window.name)}.json`,
|
|
1803
1860
|
json,
|
|
1804
1861
|
);
|
|
1805
1862
|
perfLog(`window file id=${window.id} path=${path ?? 'unavailable'}`);
|
|
@@ -1839,13 +1896,14 @@ const externalSummary = (window: MarkerWindow, timed: boolean) => {
|
|
|
1839
1896
|
|
|
1840
1897
|
const finalizeBackground = (window: MarkerWindow) => {
|
|
1841
1898
|
const profile = window.profile
|
|
1842
|
-
? summarizeWindowProfile(window.profile, { budget: frameBudget })
|
|
1899
|
+
? summarizeWindowProfile(window.profile, { budget: frameBudget, t0: window.t0 })
|
|
1843
1900
|
: null;
|
|
1844
1901
|
writeWindowRecord(window, {
|
|
1845
1902
|
v: 1,
|
|
1846
1903
|
windowModel: WINDOW_MODEL,
|
|
1847
1904
|
id: window.id,
|
|
1848
1905
|
name: window.name,
|
|
1906
|
+
...(window.legacyName !== null && window.legacyName !== window.name ? { legacyName: window.legacyName } : {}),
|
|
1849
1907
|
kind: window.kind,
|
|
1850
1908
|
t0: window.t0,
|
|
1851
1909
|
t1: window.t1,
|
|
@@ -1903,11 +1961,13 @@ const leakJsHeap = (mb: number) => {
|
|
|
1903
1961
|
leakState.retainedMB += mb;
|
|
1904
1962
|
};
|
|
1905
1963
|
|
|
1906
|
-
export const isFaultTarget = (
|
|
1907
|
-
|
|
1964
|
+
export const isFaultTarget = (press: { name: string; legacyName: string; target: { testID: string | null; accessibilityLabel: string | null } }) => {
|
|
1965
|
+
const names = [press.name, press.legacyName, press.target.testID, press.target.accessibilityLabel];
|
|
1966
|
+
return settings.faultTarget
|
|
1908
1967
|
.split(',')
|
|
1909
1968
|
.map(part => part.trim())
|
|
1910
|
-
.some(part => part !== '' &&
|
|
1969
|
+
.some(part => part !== '' && names.some(name => name?.includes(part)));
|
|
1970
|
+
};
|
|
1911
1971
|
|
|
1912
1972
|
export const runOpenFaults = (where: string) => {
|
|
1913
1973
|
const faults = settings.faults;
|
|
@@ -2086,6 +2146,16 @@ const flushOnce = async (ack: string | undefined) => {
|
|
|
2086
2146
|
} catch (error) {
|
|
2087
2147
|
perfLog(`artifacts failed ${String(error)}`);
|
|
2088
2148
|
}
|
|
2149
|
+
if (runToken !== null) {
|
|
2150
|
+
if (runLosses.stream > 0) {
|
|
2151
|
+
if (status === 'ok') status = 'error';
|
|
2152
|
+
detail += ` lost stream=${runLosses.stream}`;
|
|
2153
|
+
}
|
|
2154
|
+
if (runLosses.exportsCapped > 0) detail += ` exports capped=${runLosses.exportsCapped}`;
|
|
2155
|
+
if (runLosses.exportsUnwritten > 0) detail += ` exports unwritten=${runLosses.exportsUnwritten}`;
|
|
2156
|
+
if (runLosses.streamFibersDropped > 0) detail += ` stream fibersDropped=${runLosses.streamFibersDropped}`;
|
|
2157
|
+
}
|
|
2158
|
+
Object.assign(runLosses, { stream: 0, exportsCapped: 0, exportsUnwritten: 0, streamFibersDropped: 0 });
|
|
2089
2159
|
try {
|
|
2090
2160
|
await sendEarlierArtifacts(deadline);
|
|
2091
2161
|
} catch (error) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// The model a device reports to the API: what the OS calls the hardware, never the marketing name. expo-device's `modelId` is an
|
|
2
|
+
// iPhone's machine id ("iPhone17,1") and null on Android, where `modelName` is already Build.MODEL ("SM-S911B"); `modelName` on iOS
|
|
3
|
+
// is the marketing name, so the id comes first. A simulator keeps its name: there `modelId` is the host's `uname` machine ("arm64")
|
|
4
|
+
// and `modelName` is "Simulator iOS" (an emulator's Build.MODEL carries the word the API reads the device kind from).
|
|
5
|
+
export const deviceModelOf = (device: { isDevice?: boolean | null; modelId?: string | null; modelName?: string | null }): string | null => {
|
|
6
|
+
const modelId = /^(arm64|x86_64|i386)$/i.test(device.modelId ?? '') && device.isDevice === false ? null : device.modelId;
|
|
7
|
+
if (device.isDevice !== false) return modelId || device.modelName || null;
|
|
8
|
+
const model = device.modelName || modelId;
|
|
9
|
+
return model ? /(simulator|emulator|sdk_gphone|android sdk built for)/i.test(model) ? model : `${model} Simulator` : null;
|
|
10
|
+
};
|