@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.
Files changed (43) hide show
  1. package/OneUptimeReactNativeReplay.podspec +20 -0
  2. package/README.md +264 -0
  3. package/android/build.gradle +36 -0
  4. package/android/consumer-rules.pro +26 -0
  5. package/android/src/main/AndroidManifest.xml +1 -0
  6. package/android/src/main/java/com/oneuptime/replay/OneUptimeReplayPackage.java +29 -0
  7. package/android/src/main/java/com/oneuptime/replay/OneUptimeReplayViewTreeModule.java +541 -0
  8. package/dist/index.cjs +4111 -0
  9. package/dist/index.cjs.map +7 -0
  10. package/dist/index.js +4088 -0
  11. package/dist/index.js.map +7 -0
  12. package/dist/types/ChunkBuffer.d.ts +47 -0
  13. package/dist/types/ChunkBuffer.d.ts.map +1 -0
  14. package/dist/types/Config.d.ts +59 -0
  15. package/dist/types/Config.d.ts.map +1 -0
  16. package/dist/types/Contract.d.ts +176 -0
  17. package/dist/types/Contract.d.ts.map +1 -0
  18. package/dist/types/Events.d.ts +14 -0
  19. package/dist/types/Events.d.ts.map +1 -0
  20. package/dist/types/MobileReplayRecorder.d.ts +176 -0
  21. package/dist/types/MobileReplayRecorder.d.ts.map +1 -0
  22. package/dist/types/NativeViewTree.d.ts +49 -0
  23. package/dist/types/NativeViewTree.d.ts.map +1 -0
  24. package/dist/types/Outbox.d.ts +29 -0
  25. package/dist/types/Outbox.d.ts.map +1 -0
  26. package/dist/types/ReplayProvider.d.ts +28 -0
  27. package/dist/types/ReplayProvider.d.ts.map +1 -0
  28. package/dist/types/Sanitize.d.ts +29 -0
  29. package/dist/types/Sanitize.d.ts.map +1 -0
  30. package/dist/types/Storage.d.ts +30 -0
  31. package/dist/types/Storage.d.ts.map +1 -0
  32. package/dist/types/TouchPrivacy.d.ts +15 -0
  33. package/dist/types/TouchPrivacy.d.ts.map +1 -0
  34. package/dist/types/Transport.d.ts +36 -0
  35. package/dist/types/Transport.d.ts.map +1 -0
  36. package/dist/types/ViewTreeSerializer.d.ts +29 -0
  37. package/dist/types/ViewTreeSerializer.d.ts.map +1 -0
  38. package/dist/types/index.d.ts +12 -0
  39. package/dist/types/index.d.ts.map +1 -0
  40. package/ios/OneUptimeReplayViewTreeModule.m +17 -0
  41. package/ios/OneUptimeReplayViewTreeModule.swift +239 -0
  42. package/package.json +66 -0
  43. 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
+ };