@amplytools/react-native-amply-sdk 0.4.0 → 0.5.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.
Files changed (26) hide show
  1. package/AmplyReactNative.podspec +1 -1
  2. package/android/build.gradle +1 -1
  3. package/android/src/main/java/tools/amply/sdk/reactnative/AmplyModule.kt +73 -0
  4. package/android/src/main/java/tools/amply/sdk/reactnative/core/AmplyClient.kt +36 -0
  5. package/android/src/main/java/tools/amply/sdk/reactnative/core/DefaultAmplyClient.kt +264 -0
  6. package/android/src/main/java/tools/amply/sdk/reactnative/core/GateDecision.kt +23 -0
  7. package/android/src/main/java/tools/amply/sdk/reactnative/model/CampaignPresentPayload.kt +18 -0
  8. package/android/src/main/java/tools/amply/sdk/reactnative/model/DeepLinkPayload.kt +15 -0
  9. package/android/src/newarch/java/tools/amply/sdk/reactnative/NativeAmplyModuleSpec.java +16 -0
  10. package/android/src/newarch/jni/AmplyReactNative-generated.cpp +19 -0
  11. package/dist/src/__tests__/campaignResolved.test.d.ts +2 -0
  12. package/dist/src/__tests__/campaignResolved.test.d.ts.map +1 -0
  13. package/dist/src/__tests__/campaignResolved.test.js +196 -0
  14. package/dist/src/index.d.ts +81 -2
  15. package/dist/src/index.d.ts.map +1 -1
  16. package/dist/src/index.js +120 -0
  17. package/dist/src/nativeSpecs/NativeAmplyModule.d.ts +82 -0
  18. package/dist/src/nativeSpecs/NativeAmplyModule.d.ts.map +1 -1
  19. package/dist/tsconfig.tsbuildinfo +1 -1
  20. package/ios/Sources/AmplyReactNative/AmplyModule.mm +349 -0
  21. package/ios/Sources/AmplyReactNative/AmplyReactNative/AmplyReactNative-generated.mm +26 -0
  22. package/ios/Sources/AmplyReactNative/AmplyReactNative/AmplyReactNative.h +10 -0
  23. package/package.json +1 -1
  24. package/src/__tests__/campaignResolved.test.ts +276 -0
  25. package/src/index.ts +198 -0
  26. package/src/nativeSpecs/NativeAmplyModule.ts +82 -0
@@ -44,14 +44,69 @@ static NSString* AmplyLogLevelToString(AmplyLogLevel level) {
44
44
  return @"none";
45
45
  }
46
46
 
47
+ // Maps a JS-reported result string to the KMP CampaignResult enum. Strict semantics:
48
+ // anything other than a deliberate user "Dismissed" or explicit "Completed" fails open
49
+ // to Unavailable (proceed) — never strand the continuation on a malformed value.
50
+ static ASDKCampaignResult* AmplyCampaignResultFromString(NSString *result) {
51
+ if ([result isEqualToString:@"Completed"]) return ASDKCampaignResult.completed;
52
+ if ([result isEqualToString:@"Dismissed"]) return ASDKCampaignResult.dismissed;
53
+ if ([result isEqualToString:@"Unavailable"]) return ASDKCampaignResult.unavailable;
54
+ RCTLogWarn(@"[AmplyReactNative] Unknown campaign result '%@'; treating as Unavailable (fail-open)", result);
55
+ return ASDKCampaignResult.unavailable;
56
+ }
57
+
58
+ // Gate-side protocol names confirmed against the published KMP 0.5.0-SNAPSHOT Obj-C header
59
+ // (AmplySDK.xcframework): ASDKCampaignPresenter (present(params:info:resolution:) + dismiss())
60
+ // and ASDKCampaignResolution (resolve(result:)).
61
+ @class AmplyGatePresenter;
62
+
63
+ // The module owns the GLOBAL routing table and the JS emitter; each registered gate gets its
64
+ // OWN AmplyGatePresenter instance (below) that owns the per-gate "current presentation" slot.
47
65
  @interface Amply : NativeAmplyModuleSpecBase <NativeAmplyModuleSpec, ASDKDeepLinkListener, ASDKSystemEventsListener, ASDKLogListener>
48
66
  @property (nonatomic, strong) ASDKAmply *amplyInstance;
49
67
  @property (nonatomic, assign) BOOL deepLinkListenerRegistered;
68
+ @property (nonatomic, assign) BOOL gateRegistered;
50
69
  @property (nonatomic, assign) BOOL systemEventsListenerRegistered;
51
70
  @property (nonatomic, assign) BOOL lifecycleObserversRegistered;
52
71
  @property (nonatomic, assign) BOOL logListenerRegistered;
53
72
  @property (nonatomic, assign) AmplyLogLevel currentLogLevel;
54
73
  @property (nonatomic, strong) NSMutableArray<void (^)(ASDKAmply *)> *pendingPropertyOps;
74
+ // Global routing table: mediationId -> the KMP CampaignResolution token awaiting a JS reply.
75
+ // Shared across every registered gate because resolveCampaign(mediationId) routes by exact id —
76
+ // that lookup is correct regardless of which gate minted the id. Guarded by @synchronized(self).
77
+ @property (nonatomic, strong) NSMutableDictionary<NSString *, id<ASDKCampaignResolution>> *pendingCompletions;
78
+ // Retains the per-gate presenter instances; the KMP gate registry does not keep them alive.
79
+ @property (nonatomic, strong) NSMutableArray<AmplyGatePresenter *> *gatePresenters;
80
+
81
+ // Internal hooks used by AmplyGatePresenter (one instance per registerGate).
82
+ - (id<ASDKCampaignResolution>)takePendingCompletionForMediationId:(NSString *)mediationId;
83
+ - (void)parkPendingCompletion:(id<ASDKCampaignResolution>)resolution forMediationId:(NSString *)mediationId;
84
+ - (void)emitGatePresent:(NSDictionary *)payload;
85
+ @end
86
+
87
+ /**
88
+ * One ASDKCampaignPresenter per registerGate registration (per url-pattern).
89
+ *
90
+ * Concurrency model: KMP presents at most one gate per url-pattern at a time (the gate is modal —
91
+ * you cannot show two rewarded ads, or two presentations of the same pattern, at once). KMP's
92
+ * -dismiss is parameterless and is dispatched to the SPECIFIC presenter instance registered for
93
+ * that pattern. So the "which presentation does my dismiss() target" state must live PER-PRESENTER,
94
+ * here in `activeMediationId` — NOT in one global slot on the module. With one global slot, an
95
+ * overlap of two different-pattern gates (A present, B present, A dismiss) routes A's dismiss to
96
+ * B's token. Per-instance state makes A's dismiss abandon only A's own live presentation.
97
+ *
98
+ * Concurrent presentations of the SAME url-pattern are not supported: a second -present overwrites
99
+ * `activeMediationId`, so a later -dismiss targets the latest. That is acceptable and intended
100
+ * given the modal contract above. The module's pendingCompletions stays global (lookup by exact id
101
+ * is always correct), so a JS resolveCampaign for the overwritten id still settles its own token.
102
+ */
103
+ @interface AmplyGatePresenter : NSObject <ASDKCampaignPresenter>
104
+ @property (nonatomic, weak) Amply *module;
105
+ @property (nonatomic, copy) NSString *baseUrl;
106
+ // The mediationId this presenter most recently -present'ed and has not yet resolved/abandoned.
107
+ // -dismiss targets exactly this id, so it can only ever release THIS gate's live presentation.
108
+ // Guarded by @synchronized(self); only ever holds a live id (cleared when its token is taken).
109
+ @property (nonatomic, copy) NSString *activeMediationId;
55
110
  @end
56
111
 
57
112
  @implementation Amply
@@ -209,6 +264,204 @@ RCT_EXPORT_MODULE()
209
264
  }
210
265
  }
211
266
 
267
+ // Maps the KMP GateDecision enum/sealed value to the JS-facing decision map:
268
+ // { outcome: "proceed", reason: "completed" | "failOpen" } | { outcome: "cancelled" }
269
+ // The raw CampaignResult is never surfaced — the SDK collapses it into GateDecision.
270
+ static NSDictionary *AmplyGateDecisionToMap(ASDKGateDecision *decision) {
271
+ // Proceed carries a `reason` (Completed / FailOpen); Cancelled carries nothing.
272
+ if ([decision isKindOfClass:[ASDKGateDecisionProceed class]]) {
273
+ ASDKGateDecisionProceed *proceed = (ASDKGateDecisionProceed *)decision;
274
+ NSString *reason = @"failOpen";
275
+ if (proceed.reason == ASDKProceedReason.completed) {
276
+ reason = @"completed";
277
+ }
278
+ return @{@"outcome": @"proceed", @"reason": reason};
279
+ }
280
+ // Cancelled — or any unexpected shape — collapses to its terminal outcome. A
281
+ // Cancelled value is the only non-proceed terminal in the frozen API.
282
+ if ([decision isKindOfClass:[ASDKGateDecisionCancelled class]]) {
283
+ return @{@"outcome": @"cancelled"};
284
+ }
285
+ // Defensive: an unknown decision subtype fails open rather than stranding the caller.
286
+ RCTLogWarn(@"[AmplyReactNative] Unknown GateDecision subtype; failing open");
287
+ return @{@"outcome": @"proceed", @"reason": @"failOpen"};
288
+ }
289
+
290
+ /**
291
+ * Gated form of track. Backed by the K/N-generated async selector for the KMP
292
+ * `suspend fun trackGated(event:props:): GateDecision`. The resolve block is settled
293
+ * exactly once with the collapsed decision map; __block + a settled flag guards the
294
+ * (defensive) double-settle. This NEVER rejects: not-initialized, a thrown exception,
295
+ * or a K/N error all fail open to { outcome: "proceed", reason: "failOpen" }.
296
+ */
297
+ - (void)trackGated:(NSString *)event
298
+ properties:(NSDictionary *)properties
299
+ resolve:(RCTPromiseResolveBlock)resolve
300
+ reject:(RCTPromiseRejectBlock)reject
301
+ {
302
+ if (!event || event.length == 0) {
303
+ // Even an invalid event fails open rather than rejecting — the JS contract is
304
+ // "trackGated never rejects".
305
+ RCTLogWarn(@"[AmplyReactNative] trackGated with empty event; failing open");
306
+ if (resolve) { resolve(@{@"outcome": @"proceed", @"reason": @"failOpen"}); }
307
+ return;
308
+ }
309
+
310
+ NSDictionary *props = [properties isKindOfClass:[NSDictionary class]] ? properties : @{};
311
+
312
+ // Contract: resolve EXACTLY ONCE, ON MAIN. Every settle path — fail-open before init, the
313
+ // K/N success completion, the K/N error/cancellation completion, and the @catch — funnels
314
+ // through here. We always hop to the main queue (which serializes the once-guard, so the
315
+ // flag needs no further synchronization) and resolve at most once.
316
+ __block BOOL settled = NO;
317
+ void (^settle)(NSDictionary *) = ^(NSDictionary *decision) {
318
+ dispatch_async(dispatch_get_main_queue(), ^{
319
+ if (settled) { return; }
320
+ settled = YES;
321
+ if (resolve) {
322
+ resolve(decision);
323
+ }
324
+ });
325
+ };
326
+ NSDictionary *failOpen = @{@"outcome": @"proceed", @"reason": @"failOpen"};
327
+
328
+ if (!self.amplyInstance) {
329
+ // Fail open: not initialized -> proceed immediately (KMP gate state-machine row 1).
330
+ RCTLogWarn(@"[AmplyReactNative] trackGated before init; failing open");
331
+ settle(failOpen);
332
+ return;
333
+ }
334
+
335
+ @try {
336
+ // Confirmed against the published KMP 0.5.0-SNAPSHOT header: the ASDKAmply facade's
337
+ // `suspend fun trackGated(event:properties:)` projects to
338
+ // `-trackGatedEvent:properties:completionHandler:` with a (ASDKGateDecision*, NSError*) handler.
339
+ [self.amplyInstance trackGatedEvent:event
340
+ properties:props
341
+ completionHandler:^(ASDKGateDecision *decision, NSError *error) {
342
+ if (error) {
343
+ // A coroutine error/cancellation fails open — never strand the caller. settle() routes
344
+ // this onto main with the same once-guard as the success path (resolve once, on main).
345
+ RCTLogWarn(@"[AmplyReactNative] trackGated coroutine error; failing open: %@", error.localizedDescription);
346
+ settle(failOpen);
347
+ return;
348
+ }
349
+ // settle() already hops to main to match the deeplink/present delivery contract.
350
+ settle(AmplyGateDecisionToMap(decision));
351
+ }];
352
+ RCTLogInfo(@"[AmplyReactNative] trackGated dispatched: %@", event);
353
+ } @catch (NSException *exception) {
354
+ RCTLogError(@"[AmplyReactNative] trackGated threw; failing open: %@", exception.reason);
355
+ settle(failOpen);
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Atomically removes and returns the token parked under `mediationId` from the GLOBAL routing
361
+ * table. Returning the token to exactly one caller is what makes resolve and dismiss mutually-
362
+ * exclusive and at-most-once: whoever takes the token first owns the outcome; every later path
363
+ * gets nil and becomes a no-op. This is the single point that prevents the pending-resolution
364
+ * leak on timeout/cancel/dismiss. (The per-gate "current presentation" slot lives on each
365
+ * AmplyGatePresenter, not here — the presenter clears its own slot around -dismiss.)
366
+ */
367
+ - (id<ASDKCampaignResolution>)takePendingCompletionForMediationId:(NSString *)mediationId
368
+ {
369
+ if (!mediationId) { return nil; }
370
+ @synchronized (self) {
371
+ if (!self.pendingCompletions) { return nil; }
372
+ id<ASDKCampaignResolution> token = self.pendingCompletions[mediationId];
373
+ if (token) {
374
+ [self.pendingCompletions removeObjectForKey:mediationId];
375
+ }
376
+ return token;
377
+ }
378
+ }
379
+
380
+ /** Parks a resolution token in the global routing table under `mediationId`. */
381
+ - (void)parkPendingCompletion:(id<ASDKCampaignResolution>)resolution forMediationId:(NSString *)mediationId
382
+ {
383
+ @synchronized (self) {
384
+ if (!self.pendingCompletions) {
385
+ self.pendingCompletions = [NSMutableDictionary new];
386
+ }
387
+ self.pendingCompletions[mediationId] = resolution;
388
+ }
389
+ }
390
+
391
+ /** Surfaces a gate present dispatch to JS. Emitter delivery is async; hop to main to match deeplink. */
392
+ - (void)emitGatePresent:(NSDictionary *)payload
393
+ {
394
+ dispatch_async(dispatch_get_main_queue(), ^{
395
+ [self emitOnCampaignPresent:payload];
396
+ });
397
+ }
398
+
399
+ - (void)resolveCampaign:(NSString *)mediationId
400
+ result:(NSString *)result
401
+ {
402
+ id<ASDKCampaignResolution> completion = [self takePendingCompletionForMediationId:mediationId];
403
+ if (!completion) {
404
+ // Late/duplicate/unknown reply — the SDK token is the source of truth and is
405
+ // already settled (or never existed). Ignore.
406
+ RCTLogInfo(@"[AmplyReactNative] resolveCampaign for unknown/settled mediationId=%@; ignored", mediationId);
407
+ return;
408
+ }
409
+ ASDKCampaignResult *mapped = AmplyCampaignResultFromString(result);
410
+ RCTLogInfo(@"[AmplyReactNative] resolveCampaign mediationId=%@ result=%@", mediationId, result);
411
+ // Confirmed against the published header: CampaignResolution.resolve(result:) projects to
412
+ // -resolveResult:.
413
+ [completion resolveResult:mapped];
414
+ }
415
+
416
+ /**
417
+ * Register the bridge as the gate presenter for `baseUrl`. JS is the single capability
418
+ * registry and routes by baseUrl on its side; the native gate carries the abort policy
419
+ * and timeout.
420
+ *
421
+ * FLAG (native-compile gate): the frozen KMP 0.5.0 API is
422
+ * `registerGate(baseUrl, presenter, onAbort, timeoutMs)`.
423
+ * The selector below is written to the documented K/N convention
424
+ * (`-registerGateBaseUrl:presenter:onAbort:timeoutMs:`) and the ASDKAbortPolicy
425
+ * enum-case names (.cancel / .proceed) are assumed. BOTH the selector and the
426
+ * AbortPolicy projection MUST be confirmed against the published header before publish.
427
+ *
428
+ * Each registration gets its OWN AmplyGatePresenter instance owning its OWN "current
429
+ * presentation" slot, so a dismiss() delivered to this gate's presenter can only abandon THIS
430
+ * gate's live presentation — never one belonging to a different url-pattern's gate.
431
+ */
432
+ - (void)registerGate:(NSString *)baseUrl
433
+ onAbort:(NSString *)onAbort
434
+ timeoutMs:(double)timeoutMs
435
+ {
436
+ if (!self.amplyInstance) {
437
+ RCTLogWarn(@"[AmplyReactNative] Cannot register gate - Amply not initialized");
438
+ return;
439
+ }
440
+ if (!self.pendingCompletions) {
441
+ self.pendingCompletions = [NSMutableDictionary new];
442
+ }
443
+ if (!self.gatePresenters) {
444
+ self.gatePresenters = [NSMutableArray new];
445
+ }
446
+
447
+ ASDKAbortPolicy *policy = [onAbort isEqualToString:@"proceed"]
448
+ ? ASDKAbortPolicy.proceed
449
+ : ASDKAbortPolicy.cancel;
450
+
451
+ AmplyGatePresenter *presenter = [[AmplyGatePresenter alloc] init];
452
+ presenter.module = self;
453
+ presenter.baseUrl = baseUrl;
454
+ // Retain the presenter — the KMP gate registry does not keep it alive.
455
+ [self.gatePresenters addObject:presenter];
456
+
457
+ RCTLogInfo(@"[AmplyReactNative] Registering gate baseUrl=%@ onAbort=%@ timeoutMs=%f", baseUrl, onAbort, timeoutMs);
458
+ [self.amplyInstance registerGateBaseUrl:baseUrl
459
+ presenter:presenter
460
+ onAbort:policy
461
+ timeoutMs:(int64_t)timeoutMs];
462
+ self.gateRegistered = YES;
463
+ }
464
+
212
465
  - (void)getRecentEvents:(double)limit
213
466
  resolve:(RCTPromiseResolveBlock)resolve
214
467
  reject:(RCTPromiseRejectBlock)reject
@@ -675,6 +928,22 @@ static ASDKDataSetType *AmplyDataSetTypeFromDictionary(NSDictionary *type, NSStr
675
928
  - (void)dealloc
676
929
  {
677
930
  [[NSNotificationCenter defaultCenter] removeObserver:self];
931
+
932
+ // Fail open any in-flight gate presentations so no caller is left hanging. Each per-gate
933
+ // presenter clears its own current-presentation slot when its token is taken below; on dealloc
934
+ // the presenters themselves are released with the module.
935
+ NSArray<id<ASDKCampaignResolution>> *inFlight = nil;
936
+ @synchronized (self) {
937
+ inFlight = self.pendingCompletions.allValues;
938
+ [self.pendingCompletions removeAllObjects];
939
+ }
940
+ for (id<ASDKCampaignResolution> completion in inFlight) {
941
+ @try {
942
+ [completion resolveResult:ASDKCampaignResult.unavailable];
943
+ } @catch (NSException *exception) {
944
+ // Best-effort cleanup during teardown.
945
+ }
946
+ }
678
947
  }
679
948
 
680
949
  #pragma mark - ASDKSystemEventsListener
@@ -732,3 +1001,83 @@ static ASDKDataSetType *AmplyDataSetTypeFromDictionary(NSDictionary *type, NSStr
732
1001
  }
733
1002
 
734
1003
  @end
1004
+
1005
+ #pragma mark - AmplyGatePresenter (one instance per registerGate / url-pattern)
1006
+
1007
+ @implementation AmplyGatePresenter
1008
+
1009
+ /**
1010
+ * Called by the KMP SDK when this gate dispatches its (blocking) presentation. We mint a
1011
+ * mediationId, park the resolution token in the module's GLOBAL routing table, record it as
1012
+ * THIS presenter's current presentation, and surface the dispatch to JS. JS later replies via
1013
+ * resolveCampaign:result: (which routes by exact id, so it is unaffected by overlap).
1014
+ *
1015
+ * Confirmed against the published header: the presenter signature is
1016
+ * `present(params:info:resolution:)` -> `-presentParams:info:resolution:`,
1017
+ * with params projected as NSDictionary<NSString *, NSString *>.
1018
+ */
1019
+ - (void)presentParams:(NSDictionary<NSString *, NSString *> *)params
1020
+ info:(NSDictionary<NSString *, id> *)info
1021
+ resolution:(id<ASDKCampaignResolution>)resolution
1022
+ {
1023
+ Amply *module = self.module;
1024
+ if (!module) {
1025
+ // Module gone (teardown race) — drop the token so the SDK's own abort machinery proceeds.
1026
+ RCTLogWarn(@"[AmplyReactNative] Gate[%@] present after module teardown; dropping", self.baseUrl);
1027
+ return;
1028
+ }
1029
+
1030
+ NSString *mediationId = [[NSUUID UUID] UUIDString];
1031
+ [module parkPendingCompletion:resolution forMediationId:mediationId];
1032
+ // Record THIS gate's live presentation so -dismiss releases exactly this token on abandon. A
1033
+ // second present() on this same presenter overwrites it (concurrent same-pattern not supported;
1034
+ // latest wins) — the prior id still lives in the global table for its own JS reply.
1035
+ @synchronized (self) {
1036
+ self.activeMediationId = mediationId;
1037
+ }
1038
+
1039
+ // `info` carries the action url under @"url"; surface it for JS-side baseUrl routing.
1040
+ NSString *url = [info[@"url"] isKindOfClass:[NSString class]] ? info[@"url"] : @"";
1041
+
1042
+ RCTLogInfo(@"[AmplyReactNative] Gate[%@] dispatching present mediationId=%@ url=%@", self.baseUrl, mediationId, url);
1043
+
1044
+ NSDictionary *payload = @{
1045
+ @"url": url,
1046
+ @"params": params ?: @{},
1047
+ @"info": info ?: @{},
1048
+ @"mediationId": mediationId
1049
+ };
1050
+
1051
+ [module emitGatePresent:payload];
1052
+ }
1053
+
1054
+ /**
1055
+ * Tear down any UI this presenter put on screen WITHOUT producing a result. Called by the SDK
1056
+ * when THIS gate's caller coroutine is cancelled/times out and the resolution token is abandoned.
1057
+ *
1058
+ * Presentation lives entirely in JS here (no native gate UI), so there is nothing to tear down —
1059
+ * BUT the parked token MUST be released or the global pendingCompletions leaks on every
1060
+ * timeout/cancel. We take THIS presenter's current presentation's token (at-most-once via
1061
+ * -takePendingCompletionForMediationId:) and simply drop it: the SDK's own abort machinery owns
1062
+ * the outcome here, so we must NOT also resolve it. A subsequent JS resolveCampaign: for the same
1063
+ * id finds nothing and is a harmless no-op. Crucially, because the active id is per-presenter, an
1064
+ * overlapping different-pattern gate's presentation can never be the one we abandon here.
1065
+ */
1066
+ - (void)dismiss
1067
+ {
1068
+ NSString *activeId = nil;
1069
+ @synchronized (self) {
1070
+ activeId = self.activeMediationId;
1071
+ self.activeMediationId = nil;
1072
+ }
1073
+ if (!activeId) {
1074
+ RCTLogInfo(@"[AmplyReactNative] Gate[%@] dismiss() with no active presentation", self.baseUrl);
1075
+ return;
1076
+ }
1077
+ Amply *module = self.module;
1078
+ id<ASDKCampaignResolution> abandoned = [module takePendingCompletionForMediationId:activeId];
1079
+ RCTLogInfo(@"[AmplyReactNative] Gate[%@] dismiss() releasing mediationId=%@ (token %@)",
1080
+ self.baseUrl, activeId, abandoned ? @"dropped" : @"already settled");
1081
+ }
1082
+
1083
+ @end
@@ -23,6 +23,10 @@
23
23
  {
24
24
  _eventEmitterCallback("onDeepLink", value);
25
25
  }
26
+ - (void)emitOnCampaignPresent:(NSDictionary *)value
27
+ {
28
+ _eventEmitterCallback("onCampaignPresent", value);
29
+ }
26
30
 
27
31
  - (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallbackWrapper
28
32
  {
@@ -56,6 +60,18 @@ namespace facebook::react {
56
60
  return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, PromiseKind, "track", @selector(track:resolve:reject:), args, count);
57
61
  }
58
62
 
63
+ static facebook::jsi::Value __hostFunction_NativeAmplyModuleSpecJSI_trackGated(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
64
+ return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, PromiseKind, "trackGated", @selector(trackGated:properties:resolve:reject:), args, count);
65
+ }
66
+
67
+ static facebook::jsi::Value __hostFunction_NativeAmplyModuleSpecJSI_resolveCampaign(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
68
+ return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "resolveCampaign", @selector(resolveCampaign:result:), args, count);
69
+ }
70
+
71
+ static facebook::jsi::Value __hostFunction_NativeAmplyModuleSpecJSI_registerGate(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
72
+ return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "registerGate", @selector(registerGate:onAbort:timeoutMs:), args, count);
73
+ }
74
+
59
75
  static facebook::jsi::Value __hostFunction_NativeAmplyModuleSpecJSI_getRecentEvents(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
60
76
  return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, PromiseKind, "getRecentEvents", @selector(getRecentEvents:resolve:reject:), args, count);
61
77
  }
@@ -116,6 +132,15 @@ namespace facebook::react {
116
132
  methodMap_["track"] = MethodMetadata {1, __hostFunction_NativeAmplyModuleSpecJSI_track};
117
133
  setMethodArgConversionSelector(@"track", 0, @"JS_NativeAmplyModule_TrackEventPayload:");
118
134
 
135
+ methodMap_["trackGated"] = MethodMetadata {2, __hostFunction_NativeAmplyModuleSpecJSI_trackGated};
136
+
137
+
138
+ methodMap_["resolveCampaign"] = MethodMetadata {2, __hostFunction_NativeAmplyModuleSpecJSI_resolveCampaign};
139
+
140
+
141
+ methodMap_["registerGate"] = MethodMetadata {3, __hostFunction_NativeAmplyModuleSpecJSI_registerGate};
142
+
143
+
119
144
  methodMap_["getRecentEvents"] = MethodMetadata {1, __hostFunction_NativeAmplyModuleSpecJSI_getRecentEvents};
120
145
 
121
146
 
@@ -153,6 +178,7 @@ namespace facebook::react {
153
178
 
154
179
  eventEmitterMap_["onSystemEvent"] = std::make_shared<AsyncEventEmitter<id>>();
155
180
  eventEmitterMap_["onDeepLink"] = std::make_shared<AsyncEventEmitter<id>>();
181
+ eventEmitterMap_["onCampaignPresent"] = std::make_shared<AsyncEventEmitter<id>>();
156
182
  setEventEmitterCallback([&](const std::string &name, id value) {
157
183
  static_cast<AsyncEventEmitter<id> &>(*eventEmitterMap_[name]).emit(value);
158
184
  });
@@ -78,6 +78,15 @@ namespace JS {
78
78
  - (void)track:(JS::NativeAmplyModule::TrackEventPayload &)payload
79
79
  resolve:(RCTPromiseResolveBlock)resolve
80
80
  reject:(RCTPromiseRejectBlock)reject;
81
+ - (void)trackGated:(NSString *)event
82
+ properties:(NSDictionary *)properties
83
+ resolve:(RCTPromiseResolveBlock)resolve
84
+ reject:(RCTPromiseRejectBlock)reject;
85
+ - (void)resolveCampaign:(NSString *)mediationId
86
+ result:(NSString *)result;
87
+ - (void)registerGate:(NSString *)baseUrl
88
+ onAbort:(NSString *)onAbort
89
+ timeoutMs:(double)timeoutMs;
81
90
  - (void)getRecentEvents:(double)limit
82
91
  resolve:(RCTPromiseResolveBlock)resolve
83
92
  reject:(RCTPromiseRejectBlock)reject;
@@ -107,6 +116,7 @@ facebook::react::EventEmitterCallback _eventEmitterCallback;
107
116
 
108
117
  - (void)emitOnSystemEvent:(NSDictionary *)value;
109
118
  - (void)emitOnDeepLink:(NSDictionary *)value;
119
+ - (void)emitOnCampaignPresent:(NSDictionary *)value;
110
120
  @end
111
121
 
112
122
  namespace facebook::react {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amplytools/react-native-amply-sdk",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "React Native SDK for Amply: Mobile SDK to orchestrate in-app experiences and campaigns remotely, without app releases",
5
5
  "license": "MIT",
6
6
  "repository": {