@oneuptime/react-native-replay 13.0.4
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/OneUptimeReactNativeReplay.podspec +20 -0
- package/README.md +264 -0
- package/android/build.gradle +36 -0
- package/android/consumer-rules.pro +26 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/oneuptime/replay/OneUptimeReplayPackage.java +29 -0
- package/android/src/main/java/com/oneuptime/replay/OneUptimeReplayViewTreeModule.java +541 -0
- package/dist/index.cjs +4111 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.js +4088 -0
- package/dist/index.js.map +7 -0
- package/dist/types/ChunkBuffer.d.ts +47 -0
- package/dist/types/ChunkBuffer.d.ts.map +1 -0
- package/dist/types/Config.d.ts +59 -0
- package/dist/types/Config.d.ts.map +1 -0
- package/dist/types/Contract.d.ts +176 -0
- package/dist/types/Contract.d.ts.map +1 -0
- package/dist/types/Events.d.ts +14 -0
- package/dist/types/Events.d.ts.map +1 -0
- package/dist/types/MobileReplayRecorder.d.ts +176 -0
- package/dist/types/MobileReplayRecorder.d.ts.map +1 -0
- package/dist/types/NativeViewTree.d.ts +49 -0
- package/dist/types/NativeViewTree.d.ts.map +1 -0
- package/dist/types/Outbox.d.ts +29 -0
- package/dist/types/Outbox.d.ts.map +1 -0
- package/dist/types/ReplayProvider.d.ts +28 -0
- package/dist/types/ReplayProvider.d.ts.map +1 -0
- package/dist/types/Sanitize.d.ts +29 -0
- package/dist/types/Sanitize.d.ts.map +1 -0
- package/dist/types/Storage.d.ts +30 -0
- package/dist/types/Storage.d.ts.map +1 -0
- package/dist/types/TouchPrivacy.d.ts +15 -0
- package/dist/types/TouchPrivacy.d.ts.map +1 -0
- package/dist/types/Transport.d.ts +36 -0
- package/dist/types/Transport.d.ts.map +1 -0
- package/dist/types/ViewTreeSerializer.d.ts +29 -0
- package/dist/types/ViewTreeSerializer.d.ts.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.d.ts.map +1 -0
- package/ios/OneUptimeReplayViewTreeModule.m +17 -0
- package/ios/OneUptimeReplayViewTreeModule.swift +239 -0
- package/package.json +66 -0
- package/react-native.config.js +15 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SessionReplaySignalCounts } from "./Contract";
|
|
2
|
+
export declare const MAX_TRAIT_KEYS: number;
|
|
3
|
+
export declare const MAX_TRAIT_KEY_LENGTH: number;
|
|
4
|
+
export declare const MAX_TRAIT_VALUE_LENGTH: number;
|
|
5
|
+
export declare const MAX_TAG_KEYS: number;
|
|
6
|
+
export declare const MAX_TAG_KEY_LENGTH: number;
|
|
7
|
+
export declare const MAX_TAG_VALUE_LENGTH: number;
|
|
8
|
+
export declare const MAX_CUSTOM_EVENT_NAME_LENGTH: number;
|
|
9
|
+
export declare const MAX_CUSTOM_EVENT_PROPERTY_KEYS: number;
|
|
10
|
+
export declare const MAX_CAPTURE_REASON_LENGTH: number;
|
|
11
|
+
export declare const MAX_USER_REFERENCE_LENGTH: number;
|
|
12
|
+
export declare function truncate(value: string, length: number): string;
|
|
13
|
+
export declare function sanitizeIdentifier(value: unknown, maximumLength: number): string | null;
|
|
14
|
+
export declare function sanitizeStringMap(value: unknown, limits: {
|
|
15
|
+
keys: number;
|
|
16
|
+
keyLength: number;
|
|
17
|
+
valueLength: number;
|
|
18
|
+
}): Record<string, string>;
|
|
19
|
+
export declare function sanitizeTraits(value: unknown): Record<string, string>;
|
|
20
|
+
export declare function sanitizeTags(value: unknown): Record<string, string>;
|
|
21
|
+
export declare function sanitizeEventProperties(value: unknown): Record<string, string>;
|
|
22
|
+
export declare function maskTextValue(value: string): string;
|
|
23
|
+
export declare function maskStringMapValues(values: Record<string, string>): Record<string, string>;
|
|
24
|
+
export declare function sanitizeRoute(route: unknown): string;
|
|
25
|
+
export declare function toAppUrl(mobileAppIdentifier: string, route: string): string;
|
|
26
|
+
export declare function emptySignalCounts(): SessionReplaySignalCounts;
|
|
27
|
+
export declare function byteLength(value: string): number;
|
|
28
|
+
export declare function isPayloadWithinLimit(payload: string): boolean;
|
|
29
|
+
//# sourceMappingURL=Sanitize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Sanitize.d.ts","sourceRoot":"","sources":["../../src/Sanitize.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,yBAAyB,EAC1B,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,cAAc,EAAE,MAAW,CAAC;AACzC,eAAO,MAAM,oBAAoB,EAAE,MAAW,CAAC;AAC/C,eAAO,MAAM,sBAAsB,EAAE,MAAY,CAAC;AAClD,eAAO,MAAM,YAAY,EAAE,MAAW,CAAC;AACvC,eAAO,MAAM,kBAAkB,EAAE,MAAW,CAAC;AAC7C,eAAO,MAAM,oBAAoB,EAAE,MAAY,CAAC;AAChD,eAAO,MAAM,4BAA4B,EAAE,MAAW,CAAC;AACvD,eAAO,MAAM,8BAA8B,EAAE,MAAW,CAAC;AACzD,eAAO,MAAM,yBAAyB,EAAE,MAAW,CAAC;AACpD,eAAO,MAAM,yBAAyB,EAAE,MAAY,CAAC;AAkBrD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,OAAO,EACd,aAAa,EAAE,MAAM,GACpB,MAAM,GAAG,IAAI,CAef;AAeD,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,OAAO,EACd,MAAM,EAAE;IACN,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB,GACA,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA4BxB;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMrE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMnE;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,OAAO,GACb,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMxB;AAGD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQnD;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CASxB;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CA2CpD;AAED,wBAAgB,QAAQ,CAAC,mBAAmB,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED,wBAAgB,iBAAiB,IAAI,yBAAyB,CAW7D;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMhD;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE7D"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface ReplayStorage {
|
|
2
|
+
getItem(key: string): Promise<string | null>;
|
|
3
|
+
setItem(key: string, value: string): Promise<void>;
|
|
4
|
+
removeItem(key: string): Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
export declare const defaultReplayStorage: ReplayStorage;
|
|
7
|
+
export interface ReplaySessionIdentity {
|
|
8
|
+
sessionId: string;
|
|
9
|
+
tabId: string;
|
|
10
|
+
visitorId: string;
|
|
11
|
+
sessionStartUnixMs: number;
|
|
12
|
+
chunkIndex: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function generateReplayId(): string;
|
|
15
|
+
export default class ReplaySessionStore {
|
|
16
|
+
private readonly storage;
|
|
17
|
+
private readonly sessionKey;
|
|
18
|
+
private readonly visitorKey;
|
|
19
|
+
private identity;
|
|
20
|
+
constructor(storage: ReplayStorage, namespace: string);
|
|
21
|
+
resolve(nowUnixMs: number): Promise<ReplaySessionIdentity>;
|
|
22
|
+
takeChunkIndex(nowUnixMs: number): Promise<number>;
|
|
23
|
+
getNextChunkIndex(): number | null;
|
|
24
|
+
rotate(nowUnixMs: number): Promise<ReplaySessionIdentity>;
|
|
25
|
+
clear(): Promise<void>;
|
|
26
|
+
private safeGet;
|
|
27
|
+
private safeSet;
|
|
28
|
+
private safeRemove;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=Storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Storage.d.ts","sourceRoot":"","sources":["../../src/Storage.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAED,eAAO,MAAM,oBAAoB,EAAE,aAA4B,CAAC;AAEhE,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;CACpB;AASD,wBAAgB,gBAAgB,IAAI,MAAM,CAiBzC;AAkCD,MAAM,CAAC,OAAO,OAAO,kBAAkB;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IACxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAsC;gBAEnC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM;IAM/C,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IA4C1D,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAmBxD,iBAAiB,IAAI,MAAM,GAAG,IAAI;IAI5B,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAuBzD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAQrB,OAAO;YAQP,OAAO;YAQP,UAAU;CAOzB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NativeViewTreeNode } from "./NativeViewTree";
|
|
2
|
+
export interface OpaqueTouchRegion {
|
|
3
|
+
left: number;
|
|
4
|
+
top: number;
|
|
5
|
+
right: number;
|
|
6
|
+
bottom: number;
|
|
7
|
+
}
|
|
8
|
+
export interface TouchPrivacyMap {
|
|
9
|
+
regions: Array<OpaqueTouchRegion>;
|
|
10
|
+
suppressAll: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare const CONSERVATIVE_TOUCH_PRIVACY_MAP: TouchPrivacyMap;
|
|
13
|
+
export declare function deriveTouchPrivacyMap(root: NativeViewTreeNode): TouchPrivacyMap;
|
|
14
|
+
export declare function isTouchPrivate(privacyMap: TouchPrivacyMap, x: number, y: number): boolean;
|
|
15
|
+
//# sourceMappingURL=TouchPrivacy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TouchPrivacy.d.ts","sourceRoot":"","sources":["../../src/TouchPrivacy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAUtD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,KAAK,CAAC,iBAAiB,CAAC,CAAC;IAClC,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,8BAA8B,EAAE,eAG5C,CAAC;AAMF,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,kBAAkB,GACvB,eAAe,CAyEjB;AAED,wBAAgB,cAAc,CAC5B,UAAU,EAAE,eAAe,EAC3B,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,GACR,OAAO,CAYT"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { SessionReplayChunkEnvelope, SessionReplayDirective } from "./Contract";
|
|
2
|
+
import { ValidatedStartOptions } from "./Config";
|
|
3
|
+
import ReplayOutbox from "./Outbox";
|
|
4
|
+
export declare const CHUNK_POST_TIMEOUT_MS: number;
|
|
5
|
+
export interface ReplayTransportOptions {
|
|
6
|
+
startOptions: ValidatedStartOptions;
|
|
7
|
+
outbox: ReplayOutbox;
|
|
8
|
+
onDirective: (directive: SessionReplayDirective, reason: string | null) => void;
|
|
9
|
+
onDiagnostic: (code: string, details?: Record<string, unknown>) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface EncodedReplayFrame {
|
|
12
|
+
envelope: SessionReplayChunkEnvelope;
|
|
13
|
+
body: Uint8Array;
|
|
14
|
+
}
|
|
15
|
+
export declare function encodeReplayFrame(originalEnvelope: SessionReplayChunkEnvelope, payload: string): EncodedReplayFrame;
|
|
16
|
+
export default class ReplayTransport {
|
|
17
|
+
private readonly options;
|
|
18
|
+
private readonly fetch;
|
|
19
|
+
private drainPromise;
|
|
20
|
+
private retryTimer;
|
|
21
|
+
private stopped;
|
|
22
|
+
private flushFailures;
|
|
23
|
+
private retryAfterDrainMs;
|
|
24
|
+
private activeAbortController;
|
|
25
|
+
private abortCurrentRequest;
|
|
26
|
+
constructor(options: ReplayTransportOptions);
|
|
27
|
+
enqueue(envelope: SessionReplayChunkEnvelope, payload: string): Promise<void>;
|
|
28
|
+
restore(): Promise<void>;
|
|
29
|
+
drain(): Promise<void>;
|
|
30
|
+
destroy(): void;
|
|
31
|
+
getFlushFailures(): number;
|
|
32
|
+
private drainLoop;
|
|
33
|
+
private post;
|
|
34
|
+
private scheduleRetry;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=Transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Transport.d.ts","sourceRoot":"","sources":["../../src/Transport.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,0BAA0B,EAE1B,sBAAsB,EACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EAIL,qBAAqB,EACtB,MAAM,UAAU,CAAC;AAClB,OAAO,YAIN,MAAM,UAAU,CAAC;AAKlB,eAAO,MAAM,qBAAqB,EAAE,MAAe,CAAC;AAGpD,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,qBAAqB,CAAC;IACpC,MAAM,EAAE,YAAY,CAAC;IACrB,WAAW,EAAE,CACX,SAAS,EAAE,sBAAsB,EACjC,MAAM,EAAE,MAAM,GAAG,IAAI,KAClB,IAAI,CAAC;IACV,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CACzE;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,0BAA0B,CAAC;IACrC,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,wBAAgB,iBAAiB,CAC/B,gBAAgB,EAAE,0BAA0B,EAC5C,OAAO,EAAE,MAAM,GACd,kBAAkB,CAepB;AA0ED,MAAM,CAAC,OAAO,OAAO,eAAe;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,UAAU,CAA8C;IAChE,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,iBAAiB,CAAuB;IAChD,OAAO,CAAC,qBAAqB,CAAgC;IAC7D,OAAO,CAAC,mBAAmB,CAA6B;gBAErC,OAAO,EAAE,sBAAsB;IAKrC,OAAO,CAClB,QAAQ,EAAE,0BAA0B,EACpC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC;IAqBH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB5B,OAAO,IAAI,IAAI;IAUf,gBAAgB,IAAI,MAAM;YAInB,SAAS;YAyCT,IAAI;IAiJlB,OAAO,CAAC,aAAa;CAatB"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { RrwebEvent, SessionReplayFidelityNotice } from "./Contract";
|
|
2
|
+
import { NativeViewTreeNode } from "./NativeViewTree";
|
|
3
|
+
export declare const RRWEB_DOCUMENT_NODE_ID: number;
|
|
4
|
+
export declare const RRWEB_HTML_NODE_ID: number;
|
|
5
|
+
export declare const RRWEB_HEAD_NODE_ID: number;
|
|
6
|
+
export declare const RRWEB_BODY_NODE_ID: number;
|
|
7
|
+
export interface SerializedCapture {
|
|
8
|
+
events: Array<RrwebEvent>;
|
|
9
|
+
hasFullSnapshot: boolean;
|
|
10
|
+
fidelityNotices: Array<SessionReplayFidelityNotice>;
|
|
11
|
+
droppedNodes: number;
|
|
12
|
+
}
|
|
13
|
+
export default class ViewTreeSerializer {
|
|
14
|
+
private readonly nodeIds;
|
|
15
|
+
private nextNodeId;
|
|
16
|
+
private previous;
|
|
17
|
+
capture(input: NativeViewTreeNode, options: {
|
|
18
|
+
timestamp: number;
|
|
19
|
+
url: string;
|
|
20
|
+
viewportWidth: number;
|
|
21
|
+
viewportHeight: number;
|
|
22
|
+
forceFullSnapshot?: boolean;
|
|
23
|
+
}): SerializedCapture;
|
|
24
|
+
reset(): void;
|
|
25
|
+
private build;
|
|
26
|
+
private fullSnapshot;
|
|
27
|
+
private mutations;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=ViewTreeSerializer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ViewTreeSerializer.d.ts","sourceRoot":"","sources":["../../src/ViewTreeSerializer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EAGV,2BAA2B,EAC5B,MAAM,YAAY,CAAC;AACpB,OAAO,EAAkB,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,eAAO,MAAM,sBAAsB,EAAE,MAAU,CAAC;AAChD,eAAO,MAAM,kBAAkB,EAAE,MAAU,CAAC;AAC5C,eAAO,MAAM,kBAAkB,EAAE,MAAU,CAAC;AAC5C,eAAO,MAAM,kBAAkB,EAAE,MAAU,CAAC;AAmC5C,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,KAAK,CAAC,2BAA2B,CAAC,CAAC;IACpD,YAAY,EAAE,MAAM,CAAC;CACtB;AAsHD,MAAM,CAAC,OAAO,OAAO,kBAAkB;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkD;IAC1E,OAAO,CAAC,UAAU,CAA8B;IAChD,OAAO,CAAC,QAAQ,CAA4B;IAErC,OAAO,CACZ,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE;QACP,SAAS,EAAE,MAAM,CAAC;QAClB,GAAG,EAAE,MAAM,CAAC;QACZ,aAAa,EAAE,MAAM,CAAC;QACtB,cAAc,EAAE,MAAM,CAAC;QACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;KAC7B,GACA,iBAAiB;IAgCb,KAAK,IAAI,IAAI;IAMpB,OAAO,CAAC,KAAK;IAwJb,OAAO,CAAC,YAAY;IAqEpB,OAAO,CAAC,SAAS;CAwElB"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import MobileReplayRecorder from "./MobileReplayRecorder";
|
|
2
|
+
export { default as MobileReplayRecorder } from "./MobileReplayRecorder";
|
|
3
|
+
export type { MobileReplayDiagnosticEvent, MobileReplayDiagnostics, MobileReplayRecorderDependencies, MobileReplaySessionChangeListener, MobileReplayStatus, } from "./MobileReplayRecorder";
|
|
4
|
+
export type { MobileReplayStartOptions, ReplayFetch, ReplayFetchResponse, } from "./Config";
|
|
5
|
+
export { OneUptimeReplayProvider, ReplayMask, ReplayProvider, useOneUptimeReplay, } from "./ReplayProvider";
|
|
6
|
+
export type { OneUptimeReplayProviderProps, ReplayMaskProps, } from "./ReplayProvider";
|
|
7
|
+
export type { RecordedTouch, TouchPhase } from "./Events";
|
|
8
|
+
export { MOBILE_RECORDER_KIND, SESSION_REPLAY_MOBILE_RECORDER_CAPABILITIES, SessionReplayCaptureTrigger, SessionReplayConsentMode, SessionReplayFidelityNotice, SessionReplayMaskingMode, SessionReplayTriggerReason, } from "./Contract";
|
|
9
|
+
/** Process-wide convenience client used by ReplayProvider by default. */
|
|
10
|
+
export declare const OneUptimeReplay: MobileReplayRecorder;
|
|
11
|
+
export default OneUptimeReplay;
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,oBAAoB,MAAM,wBAAwB,CAAC;AAG1D,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACzE,YAAY,EACV,2BAA2B,EAC3B,uBAAuB,EACvB,gCAAgC,EAChC,iCAAiC,EACjC,kBAAkB,GACnB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,wBAAwB,EACxB,WAAW,EACX,mBAAmB,GACpB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,uBAAuB,EACvB,UAAU,EACV,cAAc,EACd,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,4BAA4B,EAC5B,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAO,EACL,oBAAoB,EACpB,2CAA2C,EAC3C,2BAA2B,EAC3B,wBAAwB,EACxB,2BAA2B,EAC3B,wBAAwB,EACxB,0BAA0B,GAC3B,MAAM,YAAY,CAAC;AAEpB,yEAAyE;AACzE,eAAO,MAAM,eAAe,EAAE,oBACD,CAAC;AAE9B,eAAe,eAAe,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
|
|
3
|
+
@interface RCT_EXTERN_MODULE(OneUptimeReplayViewTree, NSObject)
|
|
4
|
+
|
|
5
|
+
RCT_EXTERN_METHOD(captureViewTree:(nonnull NSNumber *)rootTag
|
|
6
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
7
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
8
|
+
|
|
9
|
+
RCT_EXTERN_METHOD(getAppMetadata:(RCTPromiseResolveBlock)resolve
|
|
10
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
11
|
+
|
|
12
|
+
RCT_EXTERN_METHOD(isTouchTargetPrivate:(nonnull NSNumber *)targetTag
|
|
13
|
+
replayRootTag:(nonnull NSNumber *)replayRootTag
|
|
14
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
15
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
16
|
+
|
|
17
|
+
@end
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import React
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
@objc(OneUptimeReplayViewTree)
|
|
6
|
+
final class OneUptimeReplayViewTreeModule: NSObject {
|
|
7
|
+
private final class TraversalState {
|
|
8
|
+
var visited = 0
|
|
9
|
+
var truncated = 0
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
private let maximumTreeDepth = 64
|
|
13
|
+
private let maximumTreeNodes = 5_000
|
|
14
|
+
@objc var bridge: RCTBridge!
|
|
15
|
+
|
|
16
|
+
@objc static func requiresMainQueueSetup() -> Bool {
|
|
17
|
+
return true
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@objc(captureViewTree:resolver:rejecter:)
|
|
21
|
+
func captureViewTree(
|
|
22
|
+
_ rootTag: NSNumber,
|
|
23
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
24
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
25
|
+
) {
|
|
26
|
+
DispatchQueue.main.async { [weak self] in
|
|
27
|
+
guard let self = self,
|
|
28
|
+
let root = self.bridge.uiManager.view(forReactTag: rootTag) else {
|
|
29
|
+
reject("E_ROOT_VIEW", "Replay root view was not found.", nil)
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let state = TraversalState()
|
|
34
|
+
guard var serialized = self.serialize(
|
|
35
|
+
root,
|
|
36
|
+
relativeTo: root,
|
|
37
|
+
depth: 0,
|
|
38
|
+
isRoot: true,
|
|
39
|
+
state: state
|
|
40
|
+
) else {
|
|
41
|
+
reject("E_EMPTY_ROOT", "Replay root view could not be serialized.", nil)
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
serialized["truncatedNodes"] = state.truncated
|
|
45
|
+
resolve(serialized)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@objc(getAppMetadata:rejecter:)
|
|
50
|
+
func getAppMetadata(
|
|
51
|
+
_ resolve: RCTPromiseResolveBlock,
|
|
52
|
+
rejecter reject: RCTPromiseRejectBlock
|
|
53
|
+
) {
|
|
54
|
+
let info = Bundle.main.infoDictionary ?? [:]
|
|
55
|
+
resolve([
|
|
56
|
+
"appName": info["CFBundleDisplayName"] as? String
|
|
57
|
+
?? info["CFBundleName"] as? String
|
|
58
|
+
?? "React Native",
|
|
59
|
+
"appVersion": info["CFBundleShortVersionString"] as? String
|
|
60
|
+
?? "unknown",
|
|
61
|
+
"osName": "ios",
|
|
62
|
+
"osVersion": UIDevice.current.systemVersion,
|
|
63
|
+
])
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@objc(isTouchTargetPrivate:replayRootTag:resolver:rejecter:)
|
|
67
|
+
func isTouchTargetPrivate(
|
|
68
|
+
_ targetTag: NSNumber,
|
|
69
|
+
replayRootTag: NSNumber,
|
|
70
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
71
|
+
rejecter _: @escaping RCTPromiseRejectBlock
|
|
72
|
+
) {
|
|
73
|
+
DispatchQueue.main.async { [weak self] in
|
|
74
|
+
guard let self = self,
|
|
75
|
+
let target = self.bridge.uiManager.view(forReactTag: targetTag),
|
|
76
|
+
let replayRoot = self.bridge.uiManager.view(forReactTag: replayRootTag) else {
|
|
77
|
+
resolve(true)
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
var current: UIView? = target
|
|
82
|
+
while let view = current {
|
|
83
|
+
if self.isReplayMask(view) || self.isOpaqueView(view) {
|
|
84
|
+
resolve(true)
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
if view === replayRoot {
|
|
88
|
+
resolve(false)
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
current = view.superview
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Stale/out-of-root event targets must never bypass privacy.
|
|
95
|
+
resolve(true)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private func serialize(
|
|
100
|
+
_ view: UIView,
|
|
101
|
+
relativeTo root: UIView,
|
|
102
|
+
depth: Int,
|
|
103
|
+
isRoot: Bool,
|
|
104
|
+
state: TraversalState
|
|
105
|
+
) -> [String: Any]? {
|
|
106
|
+
guard depth <= maximumTreeDepth, state.visited < maximumTreeNodes else {
|
|
107
|
+
state.truncated += 1
|
|
108
|
+
return nil
|
|
109
|
+
}
|
|
110
|
+
guard isRoot || (!view.isHidden && view.bounds.width > 0 && view.bounds.height > 0) else {
|
|
111
|
+
return nil
|
|
112
|
+
}
|
|
113
|
+
state.visited += 1
|
|
114
|
+
let frame = view.convert(view.bounds, to: root)
|
|
115
|
+
let masked = view.accessibilityIdentifier == "oneuptime-replay-mask"
|
|
116
|
+
let kind = classify(view, masked: masked)
|
|
117
|
+
let opaque = masked || ["image", "webview", "canvas"].contains(kind)
|
|
118
|
+
var node: [String: Any] = [
|
|
119
|
+
"nativeId": view.reactTag?.intValue ?? ObjectIdentifier(view).hashValue,
|
|
120
|
+
"kind": kind,
|
|
121
|
+
"x": frame.origin.x,
|
|
122
|
+
"y": frame.origin.y,
|
|
123
|
+
"width": frame.size.width,
|
|
124
|
+
"height": frame.size.height,
|
|
125
|
+
"masked": masked,
|
|
126
|
+
"opaque": opaque,
|
|
127
|
+
"opacity": view.alpha,
|
|
128
|
+
"zIndex": view.layer.zPosition,
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
if isRoot {
|
|
132
|
+
if let reactTouchRoot = findReactTouchRoot(from: view) {
|
|
133
|
+
let touchFrame = view.convert(view.bounds, to: reactTouchRoot)
|
|
134
|
+
node["touchOriginX"] = touchFrame.origin.x
|
|
135
|
+
node["touchOriginY"] = touchFrame.origin.y
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if let background = view.backgroundColor, let css = cssColor(background) {
|
|
140
|
+
node["backgroundColor"] = css
|
|
141
|
+
}
|
|
142
|
+
if let border = view.layer.borderColor, let css = cssColor(UIColor(cgColor: border)) {
|
|
143
|
+
node["borderColor"] = css
|
|
144
|
+
node["borderWidth"] = view.layer.borderWidth
|
|
145
|
+
}
|
|
146
|
+
if view.layer.cornerRadius > 0 {
|
|
147
|
+
node["borderRadius"] = view.layer.cornerRadius
|
|
148
|
+
}
|
|
149
|
+
node["children"] = opaque
|
|
150
|
+
? []
|
|
151
|
+
: view.subviews.compactMap {
|
|
152
|
+
self.serialize(
|
|
153
|
+
$0,
|
|
154
|
+
relativeTo: view,
|
|
155
|
+
depth: depth + 1,
|
|
156
|
+
isRoot: false,
|
|
157
|
+
state: state
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
return node
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private func isReplayMask(_ view: UIView) -> Bool {
|
|
164
|
+
return view.accessibilityIdentifier == "oneuptime-replay-mask"
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private func isOpaqueView(_ view: UIView) -> Bool {
|
|
168
|
+
return ["image", "webview", "canvas"].contains(
|
|
169
|
+
classify(view, masked: false)
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private func findReactTouchRoot(from view: UIView) -> UIView? {
|
|
174
|
+
var current: UIView? = view
|
|
175
|
+
while let candidate = current {
|
|
176
|
+
if let reactTag = candidate.reactTag?.intValue,
|
|
177
|
+
reactTag % 10 == 1 {
|
|
178
|
+
return candidate
|
|
179
|
+
}
|
|
180
|
+
if NSStringFromClass(type(of: candidate)).hasSuffix("RCTSurfaceView") {
|
|
181
|
+
return candidate
|
|
182
|
+
}
|
|
183
|
+
current = candidate.superview
|
|
184
|
+
}
|
|
185
|
+
return nil
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private func classify(_ view: UIView, masked: Bool) -> String {
|
|
189
|
+
if masked { return "masked" }
|
|
190
|
+
if view is UITextField || view is UITextView { return "input" }
|
|
191
|
+
if view is UILabel { return "text" }
|
|
192
|
+
let name = NSStringFromClass(type(of: view)).lowercased()
|
|
193
|
+
if view is UIImageView || name.contains("rctimageview")
|
|
194
|
+
|| name.contains("imageview") { return "image" }
|
|
195
|
+
if name.contains("webview") { return "webview" }
|
|
196
|
+
if isNamedOpaqueSurface(name) {
|
|
197
|
+
return "canvas"
|
|
198
|
+
}
|
|
199
|
+
if view is UIScrollView { return "scroll" }
|
|
200
|
+
if view is UIControl { return "button" }
|
|
201
|
+
if view.subviews.isEmpty && !isKnownStructuralView(name) {
|
|
202
|
+
// Unknown leaf views may draw arbitrary private pixels themselves.
|
|
203
|
+
return "canvas"
|
|
204
|
+
}
|
|
205
|
+
return "view"
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private func isNamedOpaqueSurface(_ name: String) -> Bool {
|
|
209
|
+
return [
|
|
210
|
+
"skia", "metal", "opengl", "canvas", "svg", "mapview",
|
|
211
|
+
"camera", "video", "player", "pdf", "signature", "drawing",
|
|
212
|
+
"paint", "chart",
|
|
213
|
+
].contains { name.contains($0) }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private func isKnownStructuralView(_ name: String) -> Bool {
|
|
217
|
+
return [
|
|
218
|
+
"uiview", "rctview", "rctviewcomponentview",
|
|
219
|
+
"rctrootcomponentview", "rctrootcontentview", "rctsurfaceview",
|
|
220
|
+
].contains { name == $0 || name.hasSuffix(".\($0)") }
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private func cssColor(_ color: UIColor) -> String? {
|
|
224
|
+
var red: CGFloat = 0
|
|
225
|
+
var green: CGFloat = 0
|
|
226
|
+
var blue: CGFloat = 0
|
|
227
|
+
var alpha: CGFloat = 0
|
|
228
|
+
guard color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) else {
|
|
229
|
+
return nil
|
|
230
|
+
}
|
|
231
|
+
return String(
|
|
232
|
+
format: "#%02x%02x%02x%02x",
|
|
233
|
+
Int(red * 255),
|
|
234
|
+
Int(green * 255),
|
|
235
|
+
Int(blue * 255),
|
|
236
|
+
Int(alpha * 255)
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oneuptime/react-native-replay",
|
|
3
|
+
"version": "13.0.4",
|
|
4
|
+
"description": "Privacy-first OneUptime session replay recorder for React Native applications.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/OneUptime/oneuptime",
|
|
8
|
+
"directory": "App/FeatureSet/MobileRecorder"
|
|
9
|
+
},
|
|
10
|
+
"license": "Apache-2.0",
|
|
11
|
+
"author": "OneUptime <hello@oneuptime.com> (https://oneuptime.com/)",
|
|
12
|
+
"main": "dist/index.cjs",
|
|
13
|
+
"module": "dist/index.js",
|
|
14
|
+
"react-native": "dist/index.js",
|
|
15
|
+
"types": "dist/types/index.d.ts",
|
|
16
|
+
"files": [
|
|
17
|
+
"android",
|
|
18
|
+
"ios",
|
|
19
|
+
"dist",
|
|
20
|
+
"OneUptimeReactNativeReplay.podspec",
|
|
21
|
+
"react-native.config.js",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/types/index.d.ts",
|
|
27
|
+
"react-native": "./dist/index.js",
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"require": "./dist/index.cjs"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "node esbuild.config.js && tsc -p tsconfig.build.json --emitDeclarationOnly",
|
|
34
|
+
"compile": "tsc -p tsconfig.json --noEmit",
|
|
35
|
+
"prepack": "npm run build",
|
|
36
|
+
"prepublishOnly": "npm run build",
|
|
37
|
+
"test": "node --max-old-space-size=4096 node_modules/.bin/jest --runInBand --config jest.config.js",
|
|
38
|
+
"test:coverage": "npm test -- --coverage",
|
|
39
|
+
"audit": "npm audit --audit-level=low",
|
|
40
|
+
"clear-modules": "rm -rf node_modules package-lock.json && npm install"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"fflate": "^0.8.2"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@react-native-async-storage/async-storage": ">=1.20.0",
|
|
47
|
+
"react": ">=18.2.0",
|
|
48
|
+
"react-native": ">=0.73.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
52
|
+
"@types/jest": "^29.5.14",
|
|
53
|
+
"@types/react": "^19.1.10",
|
|
54
|
+
"@types/react-test-renderer": "^19.1.0",
|
|
55
|
+
"esbuild": "^0.28.1",
|
|
56
|
+
"jest": "^29.7.0",
|
|
57
|
+
"react": "19.1.0",
|
|
58
|
+
"react-native": "0.81.5",
|
|
59
|
+
"react-test-renderer": "19.1.0",
|
|
60
|
+
"ts-jest": "^29.4.1",
|
|
61
|
+
"typescript": "^5.9.2"
|
|
62
|
+
},
|
|
63
|
+
"engines": {
|
|
64
|
+
"node": ">=20"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
dependency: {
|
|
3
|
+
platforms: {
|
|
4
|
+
android: {
|
|
5
|
+
sourceDir: "./android",
|
|
6
|
+
packageImportPath:
|
|
7
|
+
"import com.oneuptime.replay.OneUptimeReplayPackage;",
|
|
8
|
+
packageInstance: "new OneUptimeReplayPackage()",
|
|
9
|
+
},
|
|
10
|
+
ios: {
|
|
11
|
+
podspecPath: "./OneUptimeReactNativeReplay.podspec",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
};
|