@npaw/forge-react-native 10.0.4 → 10.2.0
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/android/build.gradle +1 -1
- package/android/src/main/java/com/npaw/forge/reactnative/ForgeDtoMapper.java +14 -0
- package/android/src/main/java/com/npaw/forge/reactnative/NPAWForgeModule.java +82 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/Info.plist +9 -9
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64/Headers/forge_kernelFFI.h +71 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64/libForgeKernelFFI.a +0 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64_x86_64-simulator/Headers/forge_kernelFFI.h +71 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64_x86_64-simulator/libForgeKernelFFI.a +0 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64/Headers/forge_kernelFFI.h +71 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64/libForgeKernelFFI.a +0 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64-simulator/Headers/forge_kernelFFI.h +71 -0
- package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64-simulator/libForgeKernelFFI.a +0 -0
- package/ios/ForgeApple/Sources/ForgeKernelUniFFI/forge_kernel.swift +250 -0
- package/ios/ForgeApple/Sources/ForgeKernelUniFFI/forge_kernelFFI.h +71 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeApple.swift +147 -6
- package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeCommands.swift +113 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeRuntime.swift +72 -2
- package/ios/ForgeApple/Sources/NPAWForgeCore/NPAWForge.swift +46 -2
- package/ios/ForgeApple/Sources/NPAWForgeCore/NativeUniFFIRuntime.swift +53 -0
- package/ios/ForgeApple/Sources/NPAWForgeCore/ObjectiveCWrappers.swift +14 -0
- package/ios/ForgeModuleImpl.swift +66 -0
- package/ios/NPAWForgeTurboModule.mm +16 -1
- package/package.json +1 -1
- package/src/NPAWForge.ts +131 -1
- package/src/events.ts +17 -0
- package/src/index.ts +6 -2
- package/src/specs/NativeNPAWForge.ts +9 -0
- package/src/types.ts +21 -0
|
@@ -36,11 +36,17 @@ public final class ForgeModuleImpl: NSObject {
|
|
|
36
36
|
/// Set by the TurboModule shell; receives the channel body for every line
|
|
37
37
|
/// of the kernel's rendered decision log.
|
|
38
38
|
@objc public var logSink: (([String: Any]) -> Void)?
|
|
39
|
+
/// Set by the TurboModule shell; receives the channel body for every
|
|
40
|
+
/// subscribed remote command.
|
|
41
|
+
@objc public var commandSink: (([String: Any]) -> Void)?
|
|
39
42
|
|
|
40
43
|
private let lock = NSLock()
|
|
41
44
|
private var instances: [String: NPAWForge] = [:]
|
|
42
45
|
private var subscriptions: [String: ForgeEventSubscription] = [:]
|
|
43
46
|
private var logSubscriptions: [String: ForgeEventSubscription] = [:]
|
|
47
|
+
/// Remote command subscriptions per instance, one native listener per
|
|
48
|
+
/// subscribed name; explicit, so they do not follow `listenerCount`.
|
|
49
|
+
private var commandSubscriptions: [String: [String: ForgeEventSubscription]] = [:]
|
|
44
50
|
private var adReporters: [Int: ManualAdReporter] = [:]
|
|
45
51
|
private var nextInstanceId = 1
|
|
46
52
|
private var nextReporterId = 1
|
|
@@ -106,6 +112,7 @@ public final class ForgeModuleImpl: NSObject {
|
|
|
106
112
|
let forge = instances.removeValue(forKey: instanceId)
|
|
107
113
|
let subscription = subscriptions.removeValue(forKey: instanceId)
|
|
108
114
|
let logSubscription = logSubscriptions.removeValue(forKey: instanceId)
|
|
115
|
+
let commandSubscription = commandSubscriptions.removeValue(forKey: instanceId)
|
|
109
116
|
lock.unlock()
|
|
110
117
|
|
|
111
118
|
guard let forge else {
|
|
@@ -114,9 +121,66 @@ public final class ForgeModuleImpl: NSObject {
|
|
|
114
121
|
}
|
|
115
122
|
subscription?.cancel()
|
|
116
123
|
logSubscription?.cancel()
|
|
124
|
+
commandSubscription?.values.forEach { $0.cancel() }
|
|
117
125
|
resolve(Self.outcomeToDictionary(forge.destroy()))
|
|
118
126
|
}
|
|
119
127
|
|
|
128
|
+
// MARK: - Remote commands
|
|
129
|
+
|
|
130
|
+
@objc public func setCommandSubscriptions(
|
|
131
|
+
_ instanceId: String,
|
|
132
|
+
names: [String],
|
|
133
|
+
resolve: @escaping ForgeResolveBlock,
|
|
134
|
+
reject: @escaping ForgeRejectBlock
|
|
135
|
+
) {
|
|
136
|
+
lock.lock()
|
|
137
|
+
let forge = instances[instanceId]
|
|
138
|
+
var current = commandSubscriptions[instanceId] ?? [:]
|
|
139
|
+
lock.unlock()
|
|
140
|
+
guard let forge else {
|
|
141
|
+
reject(Self.errUnknownInstance, "No NPAWForge instance '\(instanceId)'")
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
let wanted = Set(names.filter { !$0.isEmpty })
|
|
145
|
+
var cancelled: [ForgeEventSubscription] = []
|
|
146
|
+
for name in Array(current.keys) where !wanted.contains(name) {
|
|
147
|
+
if let subscription = current.removeValue(forKey: name) {
|
|
148
|
+
cancelled.append(subscription)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
for name in wanted where current[name] == nil {
|
|
152
|
+
// One native listener per name; the JS registry fans out to its
|
|
153
|
+
// own listeners and answers by id through answerCommand.
|
|
154
|
+
current[name] = forge.onCommand(name) { [weak self] command, _ in
|
|
155
|
+
guard let self, let sink = self.commandSink else { return }
|
|
156
|
+
sink([
|
|
157
|
+
"instanceId": instanceId,
|
|
158
|
+
"id": command.id,
|
|
159
|
+
"name": command.name,
|
|
160
|
+
"args": command.args,
|
|
161
|
+
])
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
lock.lock()
|
|
165
|
+
commandSubscriptions[instanceId] = current
|
|
166
|
+
lock.unlock()
|
|
167
|
+
cancelled.forEach { $0.cancel() }
|
|
168
|
+
resolve(Self.outcomeToDictionary(.accepted))
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@objc public func answerCommand(
|
|
172
|
+
_ instanceId: String,
|
|
173
|
+
commandId: String,
|
|
174
|
+
handled: Bool,
|
|
175
|
+
details: [String: Any]?,
|
|
176
|
+
resolve: @escaping ForgeResolveBlock,
|
|
177
|
+
reject: @escaping ForgeRejectBlock
|
|
178
|
+
) {
|
|
179
|
+
withInstance(instanceId, resolve: resolve, reject: reject) {
|
|
180
|
+
$0.answerCommand(id: commandId, handled: handled, details: Self.stringMap(details))
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
120
184
|
@objc public func setLogLevel(
|
|
121
185
|
_ instanceId: String,
|
|
122
186
|
level: String,
|
|
@@ -959,9 +1023,11 @@ public final class ForgeModuleImpl: NSObject {
|
|
|
959
1023
|
@objc public func invalidate() {
|
|
960
1024
|
lock.lock()
|
|
961
1025
|
let cancelled = Array(subscriptions.values) + Array(logSubscriptions.values)
|
|
1026
|
+
+ commandSubscriptions.values.flatMap { Array($0.values) }
|
|
962
1027
|
let live = Array(instances.values)
|
|
963
1028
|
subscriptions.removeAll()
|
|
964
1029
|
logSubscriptions.removeAll()
|
|
1030
|
+
commandSubscriptions.removeAll()
|
|
965
1031
|
instances.removeAll()
|
|
966
1032
|
adReporters.removeAll()
|
|
967
1033
|
listenerCount = 0
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
static NSString *const kForgeEventChannel = @"NPAWForgeEvent";
|
|
28
28
|
static NSString *const kForgeLogChannel = @"NPAWForgeLog";
|
|
29
|
+
static NSString *const kForgeCommandChannel = @"NPAWForgeCommand";
|
|
29
30
|
|
|
30
31
|
@interface NPAWForgeReactNative : RCTEventEmitter
|
|
31
32
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
@@ -52,13 +53,16 @@ RCT_EXPORT_MODULE(NPAWForgeRN)
|
|
|
52
53
|
ForgeModuleImpl.shared.logSink = ^(NSDictionary<NSString *, id> *body) {
|
|
53
54
|
[weakSelf sendEventWithName:kForgeLogChannel body:body];
|
|
54
55
|
};
|
|
56
|
+
ForgeModuleImpl.shared.commandSink = ^(NSDictionary<NSString *, id> *body) {
|
|
57
|
+
[weakSelf sendEventWithName:kForgeCommandChannel body:body];
|
|
58
|
+
};
|
|
55
59
|
}
|
|
56
60
|
return self;
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
- (NSArray<NSString *> *)supportedEvents
|
|
60
64
|
{
|
|
61
|
-
return @[ kForgeEventChannel, kForgeLogChannel ];
|
|
65
|
+
return @[ kForgeEventChannel, kForgeLogChannel, kForgeCommandChannel ];
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
- (void)startObserving
|
|
@@ -75,6 +79,7 @@ RCT_EXPORT_MODULE(NPAWForgeRN)
|
|
|
75
79
|
{
|
|
76
80
|
ForgeModuleImpl.shared.eventSink = nil;
|
|
77
81
|
ForgeModuleImpl.shared.logSink = nil;
|
|
82
|
+
ForgeModuleImpl.shared.commandSink = nil;
|
|
78
83
|
[ForgeModuleImpl.shared invalidate];
|
|
79
84
|
[super invalidate];
|
|
80
85
|
}
|
|
@@ -105,6 +110,16 @@ RCT_EXPORT_METHOD(setLogLevel : (NSString *)instanceId level : (NSString *)level
|
|
|
105
110
|
[ForgeModuleImpl.shared setLogLevel:instanceId level:level resolve:resolve reject:ForgeMakeReject(reject)];
|
|
106
111
|
}
|
|
107
112
|
|
|
113
|
+
RCT_EXPORT_METHOD(setCommandSubscriptions : (NSString *)instanceId names : (NSArray<NSString *> *)names resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject)
|
|
114
|
+
{
|
|
115
|
+
[ForgeModuleImpl.shared setCommandSubscriptions:instanceId names:names ?: @[] resolve:resolve reject:ForgeMakeReject(reject)];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
RCT_EXPORT_METHOD(answerCommand : (NSString *)instanceId commandId : (NSString *)commandId handled : (BOOL)handled details : (NSDictionary *)details resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject)
|
|
119
|
+
{
|
|
120
|
+
[ForgeModuleImpl.shared answerCommand:instanceId commandId:commandId handled:handled details:details resolve:resolve reject:ForgeMakeReject(reject)];
|
|
121
|
+
}
|
|
122
|
+
|
|
108
123
|
RCT_EXPORT_METHOD(startSession : (NSString *)instanceId metadata : (NSDictionary *)metadata resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject)
|
|
109
124
|
{
|
|
110
125
|
[ForgeModuleImpl.shared startSession:instanceId metadata:metadata resolve:resolve reject:ForgeMakeReject(reject)];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@npaw/forge-react-native",
|
|
3
|
-
"version": "10.0
|
|
3
|
+
"version": "10.2.0",
|
|
4
4
|
"description": "NPAW Forge analytics for React Native: New Architecture TurboModule facade over the native Forge Android/Apple SDKs.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Victor Soto <victor.soto@nicepeopleatwork.com>",
|
package/src/NPAWForge.ts
CHANGED
|
@@ -14,8 +14,12 @@
|
|
|
14
14
|
|
|
15
15
|
import NativeNPAWForge from './specs/NativeNPAWForge';
|
|
16
16
|
import { ForgeVideo } from './ForgeVideo';
|
|
17
|
-
import { subscribeInstanceEvents, subscribeInstanceLogs } from './events';
|
|
17
|
+
import { subscribeInstanceCommands, subscribeInstanceEvents, subscribeInstanceLogs } from './events';
|
|
18
18
|
import type {
|
|
19
|
+
ForgeCommand,
|
|
20
|
+
ForgeCommandAnswer,
|
|
21
|
+
ForgeCommandEvent,
|
|
22
|
+
ForgeCommandListener,
|
|
19
23
|
ForgeConfig,
|
|
20
24
|
ForgeEventSubscription,
|
|
21
25
|
ForgeNetworkRequestRecord,
|
|
@@ -32,7 +36,18 @@ function asOutcome(value: Promise<unknown>): Promise<ForgeOutcome> {
|
|
|
32
36
|
return value as Promise<ForgeOutcome>;
|
|
33
37
|
}
|
|
34
38
|
|
|
39
|
+
/** Distinct command names one host may subscribe to (the kernel cap). */
|
|
40
|
+
export const MAX_COMMAND_SUBSCRIPTIONS = 64;
|
|
41
|
+
|
|
42
|
+
const COMMAND_NAME_PATTERN = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
43
|
+
|
|
44
|
+
const NOOP_SUBSCRIPTION: ForgeEventSubscription = { remove: () => undefined };
|
|
45
|
+
|
|
35
46
|
export class NPAWForge {
|
|
47
|
+
private readonly commandListeners = new Map<string, Set<ForgeCommandListener>>();
|
|
48
|
+
private commandChannel: ForgeEventSubscription | undefined;
|
|
49
|
+
private commandsClosed = false;
|
|
50
|
+
|
|
36
51
|
private constructor(readonly instanceId: string) {}
|
|
37
52
|
|
|
38
53
|
/** Builds a Forge instance; resolves once the native runtime is live. */
|
|
@@ -44,6 +59,12 @@ export class NPAWForge {
|
|
|
44
59
|
// ---- lifecycle ----
|
|
45
60
|
|
|
46
61
|
destroy(): Promise<ForgeOutcome> {
|
|
62
|
+
// Listeners go first: the native subscriptions die with the instance and
|
|
63
|
+
// a late answer must not reach a destroyed runtime.
|
|
64
|
+
this.commandsClosed = true;
|
|
65
|
+
this.commandListeners.clear();
|
|
66
|
+
this.commandChannel?.remove();
|
|
67
|
+
this.commandChannel = undefined;
|
|
47
68
|
return asOutcome(NativeNPAWForge.destroy(this.instanceId));
|
|
48
69
|
}
|
|
49
70
|
|
|
@@ -176,6 +197,115 @@ export class NPAWForge {
|
|
|
176
197
|
return subscribeInstanceEvents(this.instanceId, callback);
|
|
177
198
|
}
|
|
178
199
|
|
|
200
|
+
// ---- remote commands ----
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Listens for a remote command by name. The backend delivers commands in
|
|
204
|
+
* the responses of Forge's own analytics requests, so they arrive only
|
|
205
|
+
* while events flow; a command nobody subscribed to is answered
|
|
206
|
+
* `handled=false` by the kernel and never re-delivered, so register early.
|
|
207
|
+
* Every listener of the name runs; call `answer(handled, details?)` at most
|
|
208
|
+
* once per listener, the first answer per command id is reported as a
|
|
209
|
+
* `command-answer` event. Blank or malformed names and the 65th distinct
|
|
210
|
+
* name are refused with a console warning.
|
|
211
|
+
*/
|
|
212
|
+
onCommand(name: string, listener: ForgeCommandListener): ForgeEventSubscription {
|
|
213
|
+
const normalized = typeof name === 'string' ? name.trim() : '';
|
|
214
|
+
if (!COMMAND_NAME_PATTERN.test(normalized)) {
|
|
215
|
+
console.warn('[NPAWForge] ignored command listener: names are 1-64 characters of [A-Za-z0-9_.-]');
|
|
216
|
+
return NOOP_SUBSCRIPTION;
|
|
217
|
+
}
|
|
218
|
+
if (this.commandsClosed) {
|
|
219
|
+
return NOOP_SUBSCRIPTION;
|
|
220
|
+
}
|
|
221
|
+
let listeners = this.commandListeners.get(normalized);
|
|
222
|
+
if (!listeners) {
|
|
223
|
+
if (this.commandListeners.size >= MAX_COMMAND_SUBSCRIPTIONS) {
|
|
224
|
+
console.warn(
|
|
225
|
+
`[NPAWForge] ignored command listener: at most ${MAX_COMMAND_SUBSCRIPTIONS} distinct command names can be subscribed`
|
|
226
|
+
);
|
|
227
|
+
return NOOP_SUBSCRIPTION;
|
|
228
|
+
}
|
|
229
|
+
listeners = new Set<ForgeCommandListener>();
|
|
230
|
+
this.commandListeners.set(normalized, listeners);
|
|
231
|
+
}
|
|
232
|
+
listeners.add(listener);
|
|
233
|
+
if (!this.commandChannel) {
|
|
234
|
+
this.commandChannel = subscribeInstanceCommands(this.instanceId, (event) => this.dispatchCommand(event));
|
|
235
|
+
}
|
|
236
|
+
void this.syncCommandSubscriptions();
|
|
237
|
+
let active = true;
|
|
238
|
+
return {
|
|
239
|
+
remove: () => {
|
|
240
|
+
if (!active) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
active = false;
|
|
244
|
+
const current = this.commandListeners.get(normalized);
|
|
245
|
+
if (!current) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
current.delete(listener);
|
|
249
|
+
if (current.size === 0) {
|
|
250
|
+
this.commandListeners.delete(normalized);
|
|
251
|
+
}
|
|
252
|
+
if (this.commandListeners.size === 0) {
|
|
253
|
+
this.commandChannel?.remove();
|
|
254
|
+
this.commandChannel = undefined;
|
|
255
|
+
}
|
|
256
|
+
if (!this.commandsClosed) {
|
|
257
|
+
void this.syncCommandSubscriptions();
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** The remote command names currently subscribed, sorted. */
|
|
264
|
+
commandSubscriptions(): string[] {
|
|
265
|
+
return Array.from(this.commandListeners.keys()).sort();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Answers a delivered remote command by id, for hosts that correlate
|
|
270
|
+
* answers themselves. Prefer the `answer` handed to the listener. The
|
|
271
|
+
* kernel keeps the first answer per command id.
|
|
272
|
+
*/
|
|
273
|
+
answerCommand(commandId: string, handled: boolean, details: Record<string, string> = {}): Promise<ForgeOutcome> {
|
|
274
|
+
if (this.commandsClosed) {
|
|
275
|
+
// The runtime is gone (or going): a late answer must not reach native.
|
|
276
|
+
return Promise.resolve({ code: 'ignored_invalid_state', isAccepted: false } as ForgeOutcome);
|
|
277
|
+
}
|
|
278
|
+
return asOutcome(NativeNPAWForge.answerCommand(this.instanceId, commandId, Boolean(handled), details));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private syncCommandSubscriptions(): Promise<ForgeOutcome> {
|
|
282
|
+
return asOutcome(NativeNPAWForge.setCommandSubscriptions(this.instanceId, this.commandSubscriptions()));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private dispatchCommand(event: ForgeCommandEvent): void {
|
|
286
|
+
const listeners = this.commandListeners.get(event.name);
|
|
287
|
+
if (!listeners || listeners.size === 0) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const args = Array.isArray(event.args) ? event.args.map((arg) => String(arg)) : [];
|
|
291
|
+
for (const listener of Array.from(listeners)) {
|
|
292
|
+
let answered = false;
|
|
293
|
+
const answer: ForgeCommandAnswer = (handled, details) => {
|
|
294
|
+
if (answered) {
|
|
295
|
+
return Promise.resolve({ code: 'ignored_duplicate', isAccepted: false } as ForgeOutcome);
|
|
296
|
+
}
|
|
297
|
+
answered = true;
|
|
298
|
+
return this.answerCommand(event.id, handled, details ?? {});
|
|
299
|
+
};
|
|
300
|
+
const command: ForgeCommand = { id: event.id, name: event.name, args: [...args] };
|
|
301
|
+
try {
|
|
302
|
+
listener(command, answer);
|
|
303
|
+
} catch (error) {
|
|
304
|
+
console.warn('[NPAWForge] command listener failed', error);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
179
309
|
/**
|
|
180
310
|
* Live feed of the kernel's rendered decision log for this instance - the
|
|
181
311
|
* same lines `logLevel: 'trace'` writes to the platform console, without
|
package/src/events.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { NativeEventEmitter, NativeModules } from 'react-native';
|
|
16
16
|
import NativeNPAWForge from './specs/NativeNPAWForge';
|
|
17
17
|
import type {
|
|
18
|
+
ForgeCommandEvent,
|
|
18
19
|
ForgeEventSubscription,
|
|
19
20
|
ForgeRecordedEvent,
|
|
20
21
|
ForgeRenderedLog,
|
|
@@ -22,6 +23,7 @@ import type {
|
|
|
22
23
|
|
|
23
24
|
export const FORGE_EVENT_CHANNEL = 'NPAWForgeEvent';
|
|
24
25
|
export const FORGE_LOG_CHANNEL = 'NPAWForgeLog';
|
|
26
|
+
export const FORGE_COMMAND_CHANNEL = 'NPAWForgeCommand';
|
|
25
27
|
|
|
26
28
|
const emitter = new NativeEventEmitter(
|
|
27
29
|
// The spec module implements addListener/removeListeners; iOS needs the
|
|
@@ -58,3 +60,18 @@ export function subscribeInstanceLogs(
|
|
|
58
60
|
);
|
|
59
61
|
return { remove: () => subscription.remove() };
|
|
60
62
|
}
|
|
63
|
+
|
|
64
|
+
export function subscribeInstanceCommands(
|
|
65
|
+
instanceId: string,
|
|
66
|
+
callback: (command: ForgeCommandEvent) => void
|
|
67
|
+
): ForgeEventSubscription {
|
|
68
|
+
const subscription = emitter.addListener(
|
|
69
|
+
FORGE_COMMAND_CHANNEL,
|
|
70
|
+
(command: ForgeCommandEvent) => {
|
|
71
|
+
if (command.instanceId === instanceId) {
|
|
72
|
+
callback(command);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
return { remove: () => subscription.remove() };
|
|
77
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
* @description: Public exports for @npaw/forge-react-native.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
export { NPAWForge, DEFAULT_VIDEO_IDENTIFIER } from './NPAWForge';
|
|
15
|
+
export { NPAWForge, DEFAULT_VIDEO_IDENTIFIER, MAX_COMMAND_SUBSCRIPTIONS } from './NPAWForge';
|
|
16
16
|
export { ForgeVideo } from './ForgeVideo';
|
|
17
17
|
export { ForgeAdReporter } from './ForgeAdReporter';
|
|
18
|
-
export { FORGE_EVENT_CHANNEL, FORGE_LOG_CHANNEL } from './events';
|
|
18
|
+
export { FORGE_COMMAND_CHANNEL, FORGE_EVENT_CHANNEL, FORGE_LOG_CHANNEL } from './events';
|
|
19
19
|
export {
|
|
20
20
|
installErrorCapture,
|
|
21
21
|
trackScreenView,
|
|
@@ -31,6 +31,10 @@ export type {
|
|
|
31
31
|
ForgeViewToken,
|
|
32
32
|
} from './sections';
|
|
33
33
|
export type {
|
|
34
|
+
ForgeCommand,
|
|
35
|
+
ForgeCommandAnswer,
|
|
36
|
+
ForgeCommandEvent,
|
|
37
|
+
ForgeCommandListener,
|
|
34
38
|
ForgeConfig,
|
|
35
39
|
ForgeEventSubscription,
|
|
36
40
|
ForgeLogLevelName,
|
|
@@ -163,6 +163,15 @@ export interface Spec extends TurboModule {
|
|
|
163
163
|
adFireBreakStop(reporterId: number, params: UnsafeObject): Promise<UnsafeObject>;
|
|
164
164
|
adFireDestroy(reporterId: number): Promise<UnsafeObject>;
|
|
165
165
|
|
|
166
|
+
// ---- remote commands ----
|
|
167
|
+
setCommandSubscriptions(instanceId: string, names: string[]): Promise<UnsafeObject>;
|
|
168
|
+
answerCommand(
|
|
169
|
+
instanceId: string,
|
|
170
|
+
commandId: string,
|
|
171
|
+
handled: boolean,
|
|
172
|
+
details: UnsafeObject
|
|
173
|
+
): Promise<UnsafeObject>;
|
|
174
|
+
|
|
166
175
|
// ---- event feed (classic emitter contract) ----
|
|
167
176
|
addListener(eventName: string): void;
|
|
168
177
|
removeListeners(count: number): void;
|
package/src/types.ts
CHANGED
|
@@ -148,3 +148,24 @@ export type ForgeNetworkRequestRecord = {
|
|
|
148
148
|
};
|
|
149
149
|
|
|
150
150
|
export type ForgeEventSubscription = { remove: () => void };
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* A remote command the backend addressed to this client (delivered in the
|
|
154
|
+
* `npaw-command` header of an NQS response). `args` are positional strings in
|
|
155
|
+
* header order; `id` is opaque and echoed as `commandId` on the answer.
|
|
156
|
+
*/
|
|
157
|
+
export type ForgeCommand = { id: string; name: string; args: string[] };
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Answers a delivered command. Call it at most once per listener: the first
|
|
161
|
+
* call reports `handled` plus optional flat string `details` as a
|
|
162
|
+
* `command-answer` analytics event, later calls resolve `ignored_duplicate`
|
|
163
|
+
* locally. Only the first answer per command id across all listeners is
|
|
164
|
+
* emitted by the kernel.
|
|
165
|
+
*/
|
|
166
|
+
export type ForgeCommandAnswer = (handled: boolean, details?: Record<string, string>) => Promise<ForgeOutcome>;
|
|
167
|
+
|
|
168
|
+
export type ForgeCommandListener = (command: ForgeCommand, answer: ForgeCommandAnswer) => void;
|
|
169
|
+
|
|
170
|
+
/** Channel body of `NPAWForgeCommand`: the command plus its instance id. */
|
|
171
|
+
export type ForgeCommandEvent = ForgeCommand & { instanceId: string };
|