@npaw/forge-react-native 9.6.1

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 (74) hide show
  1. package/LICENSE +10 -0
  2. package/NPAWForgeReactNative.podspec +44 -0
  3. package/README.md +51 -0
  4. package/android/build.gradle +77 -0
  5. package/android/src/main/AndroidManifest.xml +14 -0
  6. package/android/src/main/java/com/npaw/forge/reactnative/ForgeDtoMapper.java +295 -0
  7. package/android/src/main/java/com/npaw/forge/reactnative/ForgeInstanceRegistry.java +61 -0
  8. package/android/src/main/java/com/npaw/forge/reactnative/NPAWForgeModule.java +735 -0
  9. package/android/src/main/java/com/npaw/forge/reactnative/NPAWForgePackage.java +53 -0
  10. package/android/src/test/java/com/npaw/forge/reactnative/ForgeDtoMapperTest.java +133 -0
  11. package/ios/ForgeApple/ForgeKernelFFI.xcframework/Info.plist +82 -0
  12. package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64/Headers/ForgeKernelFFI.h +17 -0
  13. package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64/Headers/forge_kernelFFI.h +873 -0
  14. package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64/Headers/module.modulemap +7 -0
  15. package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64/libForgeKernelFFI.a +0 -0
  16. package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64_x86_64-simulator/Headers/ForgeKernelFFI.h +17 -0
  17. package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64_x86_64-simulator/Headers/forge_kernelFFI.h +873 -0
  18. package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64_x86_64-simulator/Headers/module.modulemap +7 -0
  19. package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64_x86_64-simulator/libForgeKernelFFI.a +0 -0
  20. package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64/Headers/ForgeKernelFFI.h +17 -0
  21. package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64/Headers/forge_kernelFFI.h +873 -0
  22. package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64/Headers/module.modulemap +7 -0
  23. package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64/libForgeKernelFFI.a +0 -0
  24. package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64-simulator/Headers/ForgeKernelFFI.h +17 -0
  25. package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64-simulator/Headers/forge_kernelFFI.h +873 -0
  26. package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64-simulator/Headers/module.modulemap +7 -0
  27. package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64-simulator/libForgeKernelFFI.a +0 -0
  28. package/ios/ForgeApple/Sources/ForgeKernelUniFFI/forge_kernel.swift +3931 -0
  29. package/ios/ForgeApple/Sources/ForgeKernelUniFFI/forge_kernelFFI.h +873 -0
  30. package/ios/ForgeApple/Sources/ForgeKernelUniFFI/forge_kernelFFI.modulemap +7 -0
  31. package/ios/ForgeApple/Sources/NPAWForgeAVPlayerAdapter/AVPlayerAccessLogFacts.swift +152 -0
  32. package/ios/ForgeApple/Sources/NPAWForgeAVPlayerAdapter/AVPlayerAdapter.swift +877 -0
  33. package/ios/ForgeApple/Sources/NPAWForgeCapture/AttributionCapture.swift +158 -0
  34. package/ios/ForgeApple/Sources/NPAWForgeCapture/CapturePlanGate.swift +189 -0
  35. package/ios/ForgeApple/Sources/NPAWForgeCapture/CrashCapture.swift +120 -0
  36. package/ios/ForgeApple/Sources/NPAWForgeCapture/ForgeAutomaticCapture.swift +639 -0
  37. package/ios/ForgeApple/Sources/NPAWForgeCapture/SectionsCapture.swift +1176 -0
  38. package/ios/ForgeApple/Sources/NPAWForgeCapture/SectionsEngine.swift +674 -0
  39. package/ios/ForgeApple/Sources/NPAWForgeCapture/SectionsMarkers.swift +105 -0
  40. package/ios/ForgeApple/Sources/NPAWForgeCapture/SectionsSwiftUI.swift +284 -0
  41. package/ios/ForgeApple/Sources/NPAWForgeCapture/SwiftUICapture.swift +44 -0
  42. package/ios/ForgeApple/Sources/NPAWForgeCore/AdAdapter.swift +199 -0
  43. package/ios/ForgeApple/Sources/NPAWForgeCore/AdSemantics.swift +136 -0
  44. package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeApple.swift +596 -0
  45. package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeCapturePlan.swift +297 -0
  46. package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeClock.swift +29 -0
  47. package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeLogging.swift +109 -0
  48. package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeOutcome.swift +89 -0
  49. package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeRuntime.swift +291 -0
  50. package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeStorage.swift +70 -0
  51. package/ios/ForgeApple/Sources/NPAWForgeCore/ManualAdReporter.swift +180 -0
  52. package/ios/ForgeApple/Sources/NPAWForgeCore/NPAWForge.swift +453 -0
  53. package/ios/ForgeApple/Sources/NPAWForgeCore/NativeUniFFIRuntime.swift +594 -0
  54. package/ios/ForgeApple/Sources/NPAWForgeCore/NetworkRequestRecord.swift +191 -0
  55. package/ios/ForgeApple/Sources/NPAWForgeCore/ObjectiveCWrappers.swift +191 -0
  56. package/ios/ForgeApple/Sources/NPAWForgeCore/PlayerAdapter.swift +299 -0
  57. package/ios/ForgeApple/Sources/NPAWForgeCore/PlayerSemantics.swift +266 -0
  58. package/ios/ForgeApple/Sources/NPAWForgeCore/PrivacySanitizer.swift +59 -0
  59. package/ios/ForgeApple/Sources/NPAWForgeCore/VideoAdapter.swift +478 -0
  60. package/ios/ForgeApple/Sources/NPAWForgeCore/VideoOptions.swift +431 -0
  61. package/ios/ForgeApple/Sources/NPAWForgeNetworkCapture/ForgeNetworkCapture.swift +116 -0
  62. package/ios/ForgeApple/Sources/NPAWForgeNetworkCaptureObjC/NPAWForgeNetworkSwizzler.m +293 -0
  63. package/ios/ForgeApple/Sources/NPAWForgeNetworkCaptureObjC/include/NPAWForgeNetworkSwizzler.h +33 -0
  64. package/ios/ForgeModuleImpl.swift +1131 -0
  65. package/ios/NPAWForgeTurboModule.mm +399 -0
  66. package/package.json +33 -0
  67. package/src/ForgeAdReporter.ts +101 -0
  68. package/src/ForgeVideo.ts +169 -0
  69. package/src/NPAWForge.ts +177 -0
  70. package/src/capture.ts +192 -0
  71. package/src/events.ts +40 -0
  72. package/src/index.ts +37 -0
  73. package/src/specs/NativeNPAWForge.ts +168 -0
  74. package/src/types.ts +114 -0
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Copyright (C) NPAW - All Rights Reserved
3
+ *
4
+ * This source code is protected under international copyright law. All rights
5
+ * reserved and protected by the copyright holders.
6
+ * This file is confidential and only available to authorized individuals with the
7
+ * permission of the copyright holders. If you encounter this file and do not have
8
+ * permission, please contact the copyright holders and delete this file.
9
+ *
10
+ *
11
+ * @author: Victor Soto <victor.soto@nicepeopleatwork.com>
12
+ * @description: Per-(instance, identifier) video facade; mirrors the native VideoAdapter manual fire* surface and creates ad reporters.
13
+ */
14
+
15
+ import NativeNPAWForge from './specs/NativeNPAWForge';
16
+ import { ForgeAdReporter } from './ForgeAdReporter';
17
+ import type { ForgeOutcome, ForgeParams } from './types';
18
+
19
+ function asOutcome(value: Promise<unknown>): Promise<ForgeOutcome> {
20
+ return value as Promise<ForgeOutcome>;
21
+ }
22
+
23
+ export class ForgeVideo {
24
+ constructor(
25
+ readonly instanceId: string,
26
+ readonly identifier: string
27
+ ) {}
28
+
29
+ fireInit(params: ForgeParams = {}): Promise<ForgeOutcome> {
30
+ return asOutcome(NativeNPAWForge.videoFireInit(this.instanceId, this.identifier, params));
31
+ }
32
+
33
+ fireStart(params: ForgeParams = {}, options: { forceInit?: boolean } = {}): Promise<ForgeOutcome> {
34
+ return asOutcome(
35
+ NativeNPAWForge.videoFireStart(this.instanceId, this.identifier, params, options.forceInit ?? false)
36
+ );
37
+ }
38
+
39
+ fireJoin(params: ForgeParams = {}): Promise<ForgeOutcome> {
40
+ return asOutcome(NativeNPAWForge.videoFireJoin(this.instanceId, this.identifier, params));
41
+ }
42
+
43
+ firePause(params: ForgeParams = {}): Promise<ForgeOutcome> {
44
+ return asOutcome(NativeNPAWForge.videoFirePause(this.instanceId, this.identifier, params));
45
+ }
46
+
47
+ fireResume(params: ForgeParams = {}): Promise<ForgeOutcome> {
48
+ return asOutcome(NativeNPAWForge.videoFireResume(this.instanceId, this.identifier, params));
49
+ }
50
+
51
+ fireSeekBegin(params: ForgeParams = {}, options: { convertFromBuffer?: boolean } = {}): Promise<ForgeOutcome> {
52
+ return asOutcome(
53
+ NativeNPAWForge.videoFireSeekBegin(
54
+ this.instanceId,
55
+ this.identifier,
56
+ params,
57
+ options.convertFromBuffer ?? false
58
+ )
59
+ );
60
+ }
61
+
62
+ fireSeekEnd(params: ForgeParams = {}): Promise<ForgeOutcome> {
63
+ return asOutcome(NativeNPAWForge.videoFireSeekEnd(this.instanceId, this.identifier, params));
64
+ }
65
+
66
+ fireBufferBegin(params: ForgeParams = {}, options: { convertFromSeek?: boolean } = {}): Promise<ForgeOutcome> {
67
+ return asOutcome(
68
+ NativeNPAWForge.videoFireBufferBegin(
69
+ this.instanceId,
70
+ this.identifier,
71
+ params,
72
+ options.convertFromSeek ?? false
73
+ )
74
+ );
75
+ }
76
+
77
+ fireBufferEnd(params: ForgeParams = {}): Promise<ForgeOutcome> {
78
+ return asOutcome(NativeNPAWForge.videoFireBufferEnd(this.instanceId, this.identifier, params));
79
+ }
80
+
81
+ fireStop(params: ForgeParams = {}): Promise<ForgeOutcome> {
82
+ return asOutcome(NativeNPAWForge.videoFireStop(this.instanceId, this.identifier, params));
83
+ }
84
+
85
+ fireError(errorCode?: string, errorMessage?: string, errorMetadata?: string): Promise<ForgeOutcome> {
86
+ return asOutcome(
87
+ NativeNPAWForge.videoFireError(
88
+ this.instanceId,
89
+ this.identifier,
90
+ errorCode ?? '',
91
+ errorMessage ?? '',
92
+ errorMetadata ?? ''
93
+ )
94
+ );
95
+ }
96
+
97
+ fireFatalError(errorCode?: string, errorMessage?: string, errorMetadata?: string): Promise<ForgeOutcome> {
98
+ return asOutcome(
99
+ NativeNPAWForge.videoFireFatalError(
100
+ this.instanceId,
101
+ this.identifier,
102
+ errorCode ?? '',
103
+ errorMessage ?? '',
104
+ errorMetadata ?? ''
105
+ )
106
+ );
107
+ }
108
+
109
+ fireDestroy(): Promise<ForgeOutcome> {
110
+ return asOutcome(NativeNPAWForge.videoFireDestroy(this.instanceId, this.identifier));
111
+ }
112
+
113
+ fireEvent(
114
+ eventName: string,
115
+ dimensions: Record<string, string> = {},
116
+ values: Record<string, number> = {},
117
+ topLevelDimensions: Record<string, string> = {},
118
+ options: { hasEndDatetime?: boolean } = {}
119
+ ): Promise<ForgeOutcome> {
120
+ return asOutcome(
121
+ NativeNPAWForge.videoFireEvent(
122
+ this.instanceId,
123
+ this.identifier,
124
+ eventName,
125
+ dimensions,
126
+ values,
127
+ topLevelDimensions,
128
+ options.hasEndDatetime ?? false
129
+ )
130
+ );
131
+ }
132
+
133
+ fireEventEnd(
134
+ eventName: string,
135
+ dimensions: Record<string, string> = {},
136
+ values: Record<string, number> = {},
137
+ topLevelDimensions: Record<string, string> = {}
138
+ ): Promise<ForgeOutcome> {
139
+ return asOutcome(
140
+ NativeNPAWForge.videoFireEventEnd(
141
+ this.instanceId,
142
+ this.identifier,
143
+ eventName,
144
+ dimensions,
145
+ values,
146
+ topLevelDimensions
147
+ )
148
+ );
149
+ }
150
+
151
+ /** Manual metadata refresh (the native facade owns the snapshot semantics). */
152
+ fireMetadataUpdate(): Promise<ForgeOutcome> {
153
+ return asOutcome(NativeNPAWForge.videoFireMetadataUpdate(this.instanceId, this.identifier));
154
+ }
155
+
156
+ async createAdReporter(adapterVersion?: string): Promise<ForgeAdReporter> {
157
+ const reporterId = await NativeNPAWForge.createAdReporter(
158
+ this.instanceId,
159
+ this.identifier,
160
+ adapterVersion ?? ''
161
+ );
162
+ return new ForgeAdReporter(reporterId);
163
+ }
164
+
165
+ /** Destroys the kernel video view and every native resource behind it. */
166
+ destroy(): Promise<ForgeOutcome> {
167
+ return asOutcome(NativeNPAWForge.videoDestroy(this.instanceId, this.identifier));
168
+ }
169
+ }
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Copyright (C) NPAW - All Rights Reserved
3
+ *
4
+ * This source code is protected under international copyright law. All rights
5
+ * reserved and protected by the copyright holders.
6
+ * This file is confidential and only available to authorized individuals with the
7
+ * permission of the copyright holders. If you encounter this file and do not have
8
+ * permission, please contact the copyright holders and delete this file.
9
+ *
10
+ *
11
+ * @author: Victor Soto <victor.soto@nicepeopleatwork.com>
12
+ * @description: Main @npaw/forge-react-native facade class - sessions, identity, raw/business events, network records, diagnostics, video handles, kernel event feed.
13
+ */
14
+
15
+ import NativeNPAWForge from './specs/NativeNPAWForge';
16
+ import { ForgeVideo } from './ForgeVideo';
17
+ import { subscribeInstanceEvents } from './events';
18
+ import type {
19
+ ForgeConfig,
20
+ ForgeEventSubscription,
21
+ ForgeNetworkRequestRecord,
22
+ ForgeOutcome,
23
+ ForgeParams,
24
+ ForgeRecordedEvent,
25
+ ForgeVideoOptions,
26
+ } from './types';
27
+
28
+ export const DEFAULT_VIDEO_IDENTIFIER = 'default';
29
+
30
+ function asOutcome(value: Promise<unknown>): Promise<ForgeOutcome> {
31
+ return value as Promise<ForgeOutcome>;
32
+ }
33
+
34
+ export class NPAWForge {
35
+ private constructor(readonly instanceId: string) {}
36
+
37
+ /** Builds a Forge instance; resolves once the native runtime is live. */
38
+ static async create(accountCode: string, config: ForgeConfig = {}): Promise<NPAWForge> {
39
+ const instanceId = await NativeNPAWForge.create(accountCode, config);
40
+ return new NPAWForge(instanceId);
41
+ }
42
+
43
+ // ---- lifecycle ----
44
+
45
+ destroy(): Promise<ForgeOutcome> {
46
+ return asOutcome(NativeNPAWForge.destroy(this.instanceId));
47
+ }
48
+
49
+ setLogLevel(level: string): Promise<ForgeOutcome> {
50
+ return asOutcome(NativeNPAWForge.setLogLevel(this.instanceId, level));
51
+ }
52
+
53
+ // ---- app session ----
54
+
55
+ startSession(metadata: ForgeParams = {}): Promise<ForgeOutcome> {
56
+ return asOutcome(NativeNPAWForge.startSession(this.instanceId, metadata));
57
+ }
58
+
59
+ endSession(metadata: ForgeParams = {}): Promise<ForgeOutcome> {
60
+ return asOutcome(NativeNPAWForge.endSession(this.instanceId, metadata));
61
+ }
62
+
63
+ trackSessionNavigation(metadata: ForgeParams): Promise<ForgeOutcome> {
64
+ return asOutcome(NativeNPAWForge.trackSessionNavigation(this.instanceId, metadata));
65
+ }
66
+
67
+ trackSessionEvent(name: string, metadata: ForgeParams = {}): Promise<ForgeOutcome> {
68
+ return asOutcome(NativeNPAWForge.trackSessionEvent(this.instanceId, name, metadata));
69
+ }
70
+
71
+ trackSessionEventStop(name: string, metadata: ForgeParams = {}): Promise<ForgeOutcome> {
72
+ return asOutcome(NativeNPAWForge.trackSessionEventStop(this.instanceId, name, metadata));
73
+ }
74
+
75
+ trackSessionError(errorCode: string, errorMessage: string, metadata: ForgeParams = {}): Promise<ForgeOutcome> {
76
+ return asOutcome(NativeNPAWForge.trackSessionError(this.instanceId, errorCode, errorMessage, metadata));
77
+ }
78
+
79
+ // ---- identity ----
80
+
81
+ loginSuccess(userId: string, options: ForgeParams = {}): Promise<ForgeOutcome> {
82
+ return asOutcome(NativeNPAWForge.loginSuccess(this.instanceId, userId, options));
83
+ }
84
+
85
+ loginFailed(options: ForgeParams = {}): Promise<ForgeOutcome> {
86
+ return asOutcome(NativeNPAWForge.loginFailed(this.instanceId, options));
87
+ }
88
+
89
+ logout(options: ForgeParams = {}): Promise<ForgeOutcome> {
90
+ return asOutcome(NativeNPAWForge.logout(this.instanceId, options));
91
+ }
92
+
93
+ profileCreated(profileId: string, options: ForgeParams = {}): Promise<ForgeOutcome> {
94
+ return asOutcome(NativeNPAWForge.profileCreated(this.instanceId, profileId, options));
95
+ }
96
+
97
+ profileSelected(profileId: string, options: ForgeParams = {}): Promise<ForgeOutcome> {
98
+ return asOutcome(NativeNPAWForge.profileSelected(this.instanceId, profileId, options));
99
+ }
100
+
101
+ profileDeleted(profileId: string): Promise<ForgeOutcome> {
102
+ return asOutcome(NativeNPAWForge.profileDeleted(this.instanceId, profileId));
103
+ }
104
+
105
+ // ---- raw / business ----
106
+
107
+ trackRawEvent(name: string, params: ForgeParams = {}): Promise<ForgeOutcome> {
108
+ return asOutcome(NativeNPAWForge.trackRawEvent(this.instanceId, name, params));
109
+ }
110
+
111
+ trackSearch(query: string, resultCount?: number): Promise<ForgeOutcome> {
112
+ return asOutcome(NativeNPAWForge.trackSearch(this.instanceId, query, resultCount ?? -1));
113
+ }
114
+
115
+ trackSearchResultClick(query: string, options: ForgeParams = {}): Promise<ForgeOutcome> {
116
+ return asOutcome(NativeNPAWForge.trackSearchResultClick(this.instanceId, query, options));
117
+ }
118
+
119
+ trackBusinessEvent(name: string, attributes: ForgeParams = {}): Promise<ForgeOutcome> {
120
+ return asOutcome(NativeNPAWForge.trackBusinessEvent(this.instanceId, name, attributes));
121
+ }
122
+
123
+ trackAttribution(options: ForgeParams = {}): Promise<ForgeOutcome> {
124
+ return asOutcome(NativeNPAWForge.trackAttribution(this.instanceId, options));
125
+ }
126
+
127
+ // ---- network ----
128
+
129
+ trackNetworkRequest(record: ForgeNetworkRequestRecord): Promise<ForgeOutcome> {
130
+ return asOutcome(NativeNPAWForge.trackNetworkRequest(this.instanceId, record));
131
+ }
132
+
133
+ /**
134
+ * Installs automatic network capture (Android: RN OkHttp factory
135
+ * instrumentation, call before the app's first fetch; iOS: URLSession
136
+ * swizzling). The kernel capture plan still gates what propagates.
137
+ */
138
+ installNetworkCapture(): Promise<ForgeOutcome> {
139
+ return asOutcome(NativeNPAWForge.installNetworkCapture(this.instanceId, {}));
140
+ }
141
+
142
+ // ---- diagnostics / events ----
143
+
144
+ async snapshotEvents(): Promise<ForgeRecordedEvent[]> {
145
+ const result = (await NativeNPAWForge.snapshotEvents(this.instanceId)) as {
146
+ items: ForgeRecordedEvent[];
147
+ };
148
+ return result.items;
149
+ }
150
+
151
+ /** Live feed of every event the kernel records for this instance. */
152
+ onEvent(callback: (event: ForgeRecordedEvent) => void): ForgeEventSubscription {
153
+ return subscribeInstanceEvents(this.instanceId, callback);
154
+ }
155
+
156
+ // ---- video ----
157
+
158
+ /**
159
+ * Creates (or reuses, per native same-identifier replacement semantics) a
160
+ * video handle. Options flow through the native VideoOptions metadata
161
+ * pipeline (web-parity key naming).
162
+ */
163
+ async createVideo(
164
+ options: ForgeVideoOptions = {},
165
+ identifier: string = DEFAULT_VIDEO_IDENTIFIER
166
+ ): Promise<ForgeVideo> {
167
+ await NativeNPAWForge.createVideo(this.instanceId, identifier, options);
168
+ return new ForgeVideo(this.instanceId, identifier);
169
+ }
170
+
171
+ /** Returns a handle to an existing video without creating it natively. */
172
+ video(identifier: string = DEFAULT_VIDEO_IDENTIFIER): ForgeVideo {
173
+ return new ForgeVideo(this.instanceId, identifier);
174
+ }
175
+ }
176
+
177
+ export default NPAWForge;
package/src/capture.ts ADDED
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Copyright (C) NPAW - All Rights Reserved
3
+ *
4
+ * This source code is protected under international copyright law. All rights
5
+ * reserved and protected by the copyright holders.
6
+ * This file is confidential and only available to authorized individuals with the
7
+ * permission of the copyright holders. If you encounter this file and do not have
8
+ * permission, please contact the copyright holders and delete this file.
9
+ *
10
+ *
11
+ * @author: Victor Soto <victor.soto@nicepeopleatwork.com>
12
+ * @description: RN-side capture helpers - JS error/unhandled-rejection observer (native crashes stay native) and screen-view tracking with Web-parity payload keys, plus a react-navigation onStateChange helper.
13
+ */
14
+
15
+ import type { NPAWForge } from './NPAWForge';
16
+ import type { ForgeEventSubscription } from './types';
17
+
18
+ type GlobalErrorHandler = (error: unknown, isFatal?: boolean) => void;
19
+
20
+ type ErrorUtilsLike = {
21
+ getGlobalHandler: () => GlobalErrorHandler;
22
+ setGlobalHandler: (handler: GlobalErrorHandler) => void;
23
+ };
24
+
25
+ type RejectionTrackingModule = {
26
+ enable: (options: {
27
+ allRejections: boolean;
28
+ onUnhandled: (id: number, rejection: unknown) => void;
29
+ onHandled?: (id: number) => void;
30
+ }) => void;
31
+ disable: () => void;
32
+ };
33
+
34
+ export type ErrorCaptureOptions = {
35
+ /** Also observe unhandled promise rejections. Default true. */
36
+ trackUnhandledRejections?: boolean;
37
+ };
38
+
39
+ const STACK_LIMIT_CHARS = 8 * 1024; // kernel caps at 8 KiB; pre-trim the JS copy
40
+
41
+ function describeError(error: unknown): { code: string; message: string; stack?: string } {
42
+ if (error instanceof Error) {
43
+ return {
44
+ code: error.name || 'Error',
45
+ message: error.message || String(error),
46
+ stack: error.stack ? error.stack.slice(0, STACK_LIMIT_CHARS) : undefined,
47
+ };
48
+ }
49
+ return { code: 'Error', message: String(error) };
50
+ }
51
+
52
+ /**
53
+ * W1: JS error visibility. Native crashes are already captured by the native
54
+ * SDKs; JS exceptions and unhandled rejections are invisible natively, so
55
+ * this observer forwards them through the typed session-error path. The
56
+ * previous global handler keeps running (RedBox/LogBox in dev, crash
57
+ * semantics in release are unchanged).
58
+ */
59
+ export function installErrorCapture(
60
+ forge: NPAWForge,
61
+ options: ErrorCaptureOptions = {}
62
+ ): ForgeEventSubscription {
63
+ const globalObject = globalThis as {
64
+ ErrorUtils?: ErrorUtilsLike;
65
+ HermesInternal?: unknown;
66
+ };
67
+ const errorUtils = globalObject.ErrorUtils;
68
+ let removed = false;
69
+ let previousHandler: GlobalErrorHandler | undefined;
70
+
71
+ if (errorUtils) {
72
+ previousHandler = errorUtils.getGlobalHandler();
73
+ errorUtils.setGlobalHandler((error, isFatal) => {
74
+ if (!removed) {
75
+ const described = describeError(error);
76
+ void forge
77
+ .trackSessionError(described.code, described.message, {
78
+ source: 'react_native_capture',
79
+ fatal: isFatal ? 'true' : 'false',
80
+ ...(described.stack ? { stack: described.stack } : {}),
81
+ })
82
+ .catch(() => {});
83
+ }
84
+ previousHandler?.(error, isFatal);
85
+ });
86
+ }
87
+
88
+ let rejectionTrackingEnabled = false;
89
+ if (options.trackUnhandledRejections !== false) {
90
+ try {
91
+ // The promise polyfill RN installs ships rejection tracking here.
92
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
93
+ const tracking: RejectionTrackingModule = require('promise/setimmediate/rejection-tracking');
94
+ tracking.enable({
95
+ allRejections: true,
96
+ onUnhandled: (_id, rejection) => {
97
+ if (removed) {
98
+ return;
99
+ }
100
+ const described = describeError(rejection);
101
+ void forge
102
+ .trackSessionError('UnhandledPromiseRejection', described.message, {
103
+ source: 'react_native_capture',
104
+ fatal: 'false',
105
+ ...(described.stack ? { stack: described.stack } : {}),
106
+ })
107
+ .catch(() => {});
108
+ },
109
+ });
110
+ rejectionTrackingEnabled = true;
111
+ } catch {
112
+ rejectionTrackingEnabled = false;
113
+ }
114
+ }
115
+
116
+ return {
117
+ remove: () => {
118
+ removed = true;
119
+ if (errorUtils && previousHandler) {
120
+ errorUtils.setGlobalHandler(previousHandler);
121
+ }
122
+ // Rejection tracking stays enabled (it delegates to the default
123
+ // logger); the removed flag stops Forge forwarding.
124
+ void rejectionTrackingEnabled;
125
+ },
126
+ };
127
+ }
128
+
129
+ export type ScreenViewOptions = {
130
+ /** Parameterized route shape, e.g. '/movie/:id'. Defaults to the name. */
131
+ routeTemplate?: string;
132
+ };
133
+
134
+ const lastScreenByInstance = new Map<string, { route: string; page: string }>();
135
+
136
+ /**
137
+ * W2: manual screen views. Mirrors the Web navigation capture pair: a typed
138
+ * trackSessionNavigation plus the raw_screen_view raw event with the same
139
+ * payload keys (route/page/previousRoute/previousPage/routeTemplate/
140
+ * launchSource), so RN screens land in the same dashboards.
141
+ */
142
+ export async function trackScreenView(
143
+ forge: NPAWForge,
144
+ screenName: string,
145
+ options: ScreenViewOptions = {}
146
+ ): Promise<void> {
147
+ const route = screenName.startsWith('/') ? screenName : `/${screenName}`;
148
+ const previous = lastScreenByInstance.get(forge.instanceId);
149
+ const launchSource = previous ? 'normal' : 'launch';
150
+ lastScreenByInstance.set(forge.instanceId, { route, page: screenName });
151
+
152
+ await forge.trackSessionNavigation({
153
+ route,
154
+ page: screenName,
155
+ source: 'react_native_capture',
156
+ });
157
+ await forge.trackRawEvent('raw_screen_view', {
158
+ route,
159
+ page: screenName,
160
+ ...(previous ? { previousRoute: previous.route, previousPage: previous.page } : {}),
161
+ routeTemplate: options.routeTemplate ?? route,
162
+ launchSource,
163
+ });
164
+ }
165
+
166
+ type NavigationStateLike = {
167
+ routes?: Array<{ name?: string }>;
168
+ index?: number;
169
+ };
170
+
171
+ /**
172
+ * react-navigation glue: pass the returned function to
173
+ * NavigationContainer's onStateChange (and call it once with the initial
174
+ * state from onReady).
175
+ */
176
+ export function createNavigationObserver(
177
+ forge: NPAWForge
178
+ ): (state: NavigationStateLike | undefined) => void {
179
+ let lastName: string | undefined;
180
+ return state => {
181
+ const route =
182
+ state && Array.isArray(state.routes) && typeof state.index === 'number'
183
+ ? state.routes[state.index]
184
+ : undefined;
185
+ const name = route?.name;
186
+ if (!name || name === lastName) {
187
+ return;
188
+ }
189
+ lastName = name;
190
+ void trackScreenView(forge, name).catch(() => {});
191
+ };
192
+ }
package/src/events.ts ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Copyright (C) NPAW - All Rights Reserved
3
+ *
4
+ * This source code is protected under international copyright law. All rights
5
+ * reserved and protected by the copyright holders.
6
+ * This file is confidential and only available to authorized individuals with the
7
+ * permission of the copyright holders. If you encounter this file and do not have
8
+ * permission, please contact the copyright holders and delete this file.
9
+ *
10
+ *
11
+ * @author: Victor Soto <victor.soto@nicepeopleatwork.com>
12
+ * @description: Kernel event feed wiring - single NativeEventEmitter channel shared by every Forge instance, filtered per instance id.
13
+ */
14
+
15
+ import { NativeEventEmitter, NativeModules } from 'react-native';
16
+ import NativeNPAWForge from './specs/NativeNPAWForge';
17
+ import type { ForgeEventSubscription, ForgeRecordedEvent } from './types';
18
+
19
+ export const FORGE_EVENT_CHANNEL = 'NPAWForgeEvent';
20
+
21
+ const emitter = new NativeEventEmitter(
22
+ // The spec module implements addListener/removeListeners; iOS needs the
23
+ // module object itself for the classic emitter contract.
24
+ NativeModules.NPAWForgeRN ?? (NativeNPAWForge as never)
25
+ );
26
+
27
+ export function subscribeInstanceEvents(
28
+ instanceId: string,
29
+ callback: (event: ForgeRecordedEvent) => void
30
+ ): ForgeEventSubscription {
31
+ const subscription = emitter.addListener(
32
+ FORGE_EVENT_CHANNEL,
33
+ (event: ForgeRecordedEvent) => {
34
+ if (event.instanceId === instanceId) {
35
+ callback(event);
36
+ }
37
+ }
38
+ );
39
+ return { remove: () => subscription.remove() };
40
+ }
package/src/index.ts ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Copyright (C) NPAW - All Rights Reserved
3
+ *
4
+ * This source code is protected under international copyright law. All rights
5
+ * reserved and protected by the copyright holders.
6
+ * This file is confidential and only available to authorized individuals with the
7
+ * permission of the copyright holders. If you encounter this file and do not have
8
+ * permission, please contact the copyright holders and delete this file.
9
+ *
10
+ *
11
+ * @author: Victor Soto <victor.soto@nicepeopleatwork.com>
12
+ * @description: Public exports for @npaw/forge-react-native.
13
+ */
14
+
15
+ export { NPAWForge, DEFAULT_VIDEO_IDENTIFIER } from './NPAWForge';
16
+ export { ForgeVideo } from './ForgeVideo';
17
+ export { ForgeAdReporter } from './ForgeAdReporter';
18
+ export { FORGE_EVENT_CHANNEL } from './events';
19
+ export {
20
+ installErrorCapture,
21
+ trackScreenView,
22
+ createNavigationObserver,
23
+ } from './capture';
24
+ export type { ErrorCaptureOptions, ScreenViewOptions } from './capture';
25
+ export type {
26
+ ForgeConfig,
27
+ ForgeEventSubscription,
28
+ ForgeNetworkOutcome,
29
+ ForgeNetworkRequestRecord,
30
+ ForgeOutcome,
31
+ ForgeOutcomeCode,
32
+ ForgeParams,
33
+ ForgeRecordedEvent,
34
+ ForgeVideoOptions,
35
+ } from './types';
36
+
37
+ export { NPAWForge as default } from './NPAWForge';