react-native-insider 8.1.0 → 9.0.0-nh
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/README.md +89 -660
- package/RNInsider.podspec +33 -12
- package/android/build.gradle +16 -7
- package/android/src/main/java/com/useinsider/react/RNInsiderAppFrameViewManager.java +253 -0
- package/android/src/main/java/com/useinsider/react/RNInsiderModule.java +238 -178
- package/android/src/main/java/com/useinsider/react/RNInsiderPackage.java +5 -1
- package/android/src/main/java/com/useinsider/react/RNUtils.java +111 -0
- package/babel.config.js +4 -1
- package/index.d.ts +196 -161
- package/index.js +60 -11
- package/ios/RNInsider/RNInsider.m +245 -341
- package/ios/RNInsider/RNInsiderAppFrameView.h +33 -0
- package/ios/RNInsider/RNInsiderAppFrameView.mm +315 -0
- package/ios/RNInsider/RNNotificationHandler.h +4 -1
- package/ios/RNInsider/RNNotificationHandler.m +16 -18
- package/ios/RNInsider/RNUtils.h +2 -0
- package/ios/RNInsider/RNUtils.m +79 -0
- package/package.json +43 -12
- package/src/EventHandler.js +17 -0
- package/src/InsiderAppFrame.d.ts +45 -0
- package/src/InsiderAppFrame.js +265 -0
- package/src/InsiderAppFrameLogic.d.ts +35 -0
- package/src/InsiderAppFrameLogic.js +192 -0
- package/src/InsiderAppFrameStatus.d.ts +22 -0
- package/src/InsiderAppFrameStatus.js +35 -0
- package/src/InsiderAppFramesError.d.ts +25 -0
- package/src/InsiderAppFramesError.js +77 -0
- package/src/InsiderEvent.js +1 -0
- package/src/InsiderEvents.d.ts +93 -0
- package/src/InsiderEvents.js +124 -0
- package/src/InsiderIdentifier.js +1 -1
- package/src/InsiderUser.d.ts +3 -2
- package/src/InsiderUser.js +27 -0
- package/src/Util.js +2 -1
- package/src/specs/InsiderAppFrameNativeComponent.ts +54 -0
- package/.orbit-output/config/MASTER.md +0 -129
- package/.orbit-output/config/architecture/API_DOCS.md +0 -342
- package/.orbit-output/config/architecture/ARCHITECTURE.md +0 -209
- package/.orbit-output/config/architecture/CODE-CATALOG.md +0 -172
- package/.orbit-output/config/architecture/project.yaml +0 -138
- package/.orbit-output/config/automation/AUTOMATION-SETUP.md +0 -50
- package/.orbit-output/config/enforcement/ENFORCEMENT-RULES.md +0 -41
- package/.orbit-output/config/enforcement/validate.sh +0 -49
- package/.orbit-output/skills/custom/academy-doc-generator.md +0 -598
- package/android/.settings/org.eclipse.buildship.core.prefs +0 -13
- package/android/src/test/java/com/useinsider/react/RNInsiderModuleTest.java +0 -169
- package/docs/app-cards/app-cards_new.md +0 -569
- package/ios/RNInsiderTests/RNInsiderTriggerPushProcessTests.m +0 -91
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Fabric-only. Guarded here as well as in the .mm, because the .mm imports this header before
|
|
2
|
+
// its own guard and a legacy-architecture build would otherwise pull in RCTViewComponentView.h.
|
|
3
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
4
|
+
|
|
5
|
+
#import <React/RCTViewComponentView.h>
|
|
6
|
+
#import <UIKit/UIKit.h>
|
|
7
|
+
|
|
8
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
9
|
+
|
|
10
|
+
@interface RNInsiderAppFrameView : RCTViewComponentView
|
|
11
|
+
@end
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The error-code name that crosses the bridge for a delegate error.
|
|
15
|
+
*
|
|
16
|
+
* Answers `@"UNKNOWN"` for an error outside App Frames' own domain, and for every error when the
|
|
17
|
+
* linked InsiderMobile has no App Frames. The signature uses no InsiderMobile type so it compiles
|
|
18
|
+
* either way.
|
|
19
|
+
*/
|
|
20
|
+
FOUNDATION_EXPORT NSString *RNInsiderAppFramesErrorCodeNameForError(NSError *error);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The status name that crosses the bridge for a native status value.
|
|
24
|
+
*
|
|
25
|
+
* Answers `@"UNRECOGNIZED"` for a value outside the SDK's enum, and for every value when the
|
|
26
|
+
* linked InsiderMobile has no App Frames. The signature uses no InsiderMobile type so it compiles
|
|
27
|
+
* either way.
|
|
28
|
+
*/
|
|
29
|
+
FOUNDATION_EXPORT NSString *RNInsiderAppFramesStatusNameForRawStatus(NSInteger status);
|
|
30
|
+
|
|
31
|
+
NS_ASSUME_NONNULL_END
|
|
32
|
+
|
|
33
|
+
#endif // RCT_NEW_ARCH_ENABLED
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#import "RNInsiderAppFrameView.h"
|
|
2
|
+
|
|
3
|
+
// The codegen headers below exist only under the New Architecture. The legacy architecture is
|
|
4
|
+
// the default on React Native 0.74-0.75 and still selectable on 0.76-0.81 (removed in 0.82), all
|
|
5
|
+
// in the supported range, and this pod must still compile there.
|
|
6
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
#import <react/renderer/components/RNInsiderSpec/ComponentDescriptors.h>
|
|
10
|
+
#import <react/renderer/components/RNInsiderSpec/EventEmitters.h>
|
|
11
|
+
#import <react/renderer/components/RNInsiderSpec/Props.h>
|
|
12
|
+
#import <react/renderer/components/RNInsiderSpec/RCTComponentViewHelpers.h>
|
|
13
|
+
|
|
14
|
+
#import <React/RCTConversions.h>
|
|
15
|
+
|
|
16
|
+
// Diagnostics go through +[Insider sendError:desc:], like the rest of the bridge, rather than
|
|
17
|
+
// NSLog into a host app console the integrator cannot switch off.
|
|
18
|
+
#import <InsiderMobile/Insider.h>
|
|
19
|
+
|
|
20
|
+
// InsiderMobile releases before 16.0.0 have no App Frames headers. Codegen registers
|
|
21
|
+
// "InsiderAppFrameView" in every app regardless of the linked SDK, so InsiderAppFrameViewCls()
|
|
22
|
+
// must still resolve without them, or the app fails to link.
|
|
23
|
+
#if __has_include(<InsiderMobile/InsiderAppFramesView.h>)
|
|
24
|
+
#define INSIDER_APP_FRAMES_AVAILABLE 1
|
|
25
|
+
#import <InsiderMobile/InsiderAppFramesView.h>
|
|
26
|
+
#import <InsiderMobile/InsiderAppFramesError.h>
|
|
27
|
+
#else
|
|
28
|
+
#define INSIDER_APP_FRAMES_AVAILABLE 0
|
|
29
|
+
#endif
|
|
30
|
+
|
|
31
|
+
using namespace facebook::react;
|
|
32
|
+
|
|
33
|
+
#if INSIDER_APP_FRAMES_AVAILABLE
|
|
34
|
+
|
|
35
|
+
// Matches NO_DISMISS_CODE in src/InsiderAppFramesError.js.
|
|
36
|
+
static const NSInteger kInsiderAppFramesNoDismissCode = -1;
|
|
37
|
+
|
|
38
|
+
static NSString *RNInsiderAppFramesErrorCodeName(InsiderAppFramesErrorCode code) {
|
|
39
|
+
switch (code) {
|
|
40
|
+
case InsiderAppFramesErrorCodeResolutionFailed:
|
|
41
|
+
return @"RESOLUTION_FAILED";
|
|
42
|
+
case InsiderAppFramesErrorCodeResponseMalformed:
|
|
43
|
+
return @"RESPONSE_MALFORMED";
|
|
44
|
+
case InsiderAppFramesErrorCodeDownloadingFailed:
|
|
45
|
+
return @"DOWNLOADING_FAILED";
|
|
46
|
+
case InsiderAppFramesErrorCodePlacementUntrusted:
|
|
47
|
+
return @"PLACEMENT_UNTRUSTED";
|
|
48
|
+
case InsiderAppFramesErrorCodeContentDisplayFailed:
|
|
49
|
+
return @"CONTENT_DISPLAY_FAILED";
|
|
50
|
+
case InsiderAppFramesErrorCodeRenderingFailed:
|
|
51
|
+
return @"RENDERING_FAILED";
|
|
52
|
+
case InsiderAppFramesErrorCodeUnknown:
|
|
53
|
+
return @"UNKNOWN";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// No `default:`, and the parameter is the enum type rather than NSInteger, so a code added
|
|
57
|
+
// to the SDK trips -Wswitch instead of silently mapping to UNKNOWN.
|
|
58
|
+
return @"UNKNOWN";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// JS compares against its own UPPER_SNAKE enum (src/InsiderAppFrameStatus.js), so the SDK's
|
|
62
|
+
// lowerCamelCase `stringValue` would match nothing, silently.
|
|
63
|
+
static NSString *RNInsiderAppFramesStatusName(InsiderAppFramesViewStatus status) {
|
|
64
|
+
switch (status) {
|
|
65
|
+
case InsiderAppFramesViewStatusDetached:
|
|
66
|
+
return @"DETACHED";
|
|
67
|
+
case InsiderAppFramesViewStatusNoPlacement:
|
|
68
|
+
return @"NO_PLACEMENT";
|
|
69
|
+
case InsiderAppFramesViewStatusResolving:
|
|
70
|
+
return @"RESOLVING";
|
|
71
|
+
case InsiderAppFramesViewStatusDownloading:
|
|
72
|
+
return @"DOWNLOADING";
|
|
73
|
+
case InsiderAppFramesViewStatusRendering:
|
|
74
|
+
return @"RENDERING";
|
|
75
|
+
case InsiderAppFramesViewStatusUnavailable:
|
|
76
|
+
return @"UNAVAILABLE";
|
|
77
|
+
case InsiderAppFramesViewStatusReady:
|
|
78
|
+
return @"READY";
|
|
79
|
+
case InsiderAppFramesViewStatusDismissed:
|
|
80
|
+
return @"DISMISSED";
|
|
81
|
+
case InsiderAppFramesViewStatusErrorResolving:
|
|
82
|
+
return @"ERROR_RESOLVING";
|
|
83
|
+
case InsiderAppFramesViewStatusErrorDownloading:
|
|
84
|
+
return @"ERROR_DOWNLOADING";
|
|
85
|
+
case InsiderAppFramesViewStatusErrorRendering:
|
|
86
|
+
return @"ERROR_RENDERING";
|
|
87
|
+
case InsiderAppFramesViewStatusDisabled:
|
|
88
|
+
return @"DISABLED";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Reachable only for an out-of-enum runtime value. UNRECOGNIZED is a declared member of the
|
|
92
|
+
// JS status union; a real status such as DETACHED would tell JS something specific and wrong.
|
|
93
|
+
[Insider sendError:[NSException exceptionWithName:@"InsiderAppFrameUnrecognisedStatus"
|
|
94
|
+
reason:[NSString stringWithFormat:
|
|
95
|
+
@"Unrecognised InsiderAppFramesViewStatus: %ld",
|
|
96
|
+
(long)status]
|
|
97
|
+
userInfo:nil]
|
|
98
|
+
desc:[NSString stringWithFormat:@"%s:%d", __func__, __LINE__]];
|
|
99
|
+
return @"UNRECOGNIZED";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Test seam; the delegate callbacks call the static mapper directly.
|
|
103
|
+
NSString *RNInsiderAppFramesStatusNameForRawStatus(NSInteger status) {
|
|
104
|
+
return RNInsiderAppFramesStatusName((InsiderAppFramesViewStatus)status);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@interface RNInsiderAppFrameView () <InsiderAppFramesViewDelegate>
|
|
108
|
+
@end
|
|
109
|
+
|
|
110
|
+
static SharedViewProps RNInsiderAppFrameViewDefaultProps() {
|
|
111
|
+
// SharedViewProps, not Props::Shared: `_props` is shared_ptr<const ViewProps>, and a
|
|
112
|
+
// shared_ptr<const Props> will not implicitly convert down to it.
|
|
113
|
+
static const auto defaultProps = std::make_shared<const InsiderAppFrameViewProps>();
|
|
114
|
+
return defaultProps;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@implementation RNInsiderAppFrameView {
|
|
118
|
+
InsiderAppFramesView *_frameView;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
+ (ComponentDescriptorProvider)componentDescriptorProvider {
|
|
122
|
+
return concreteComponentDescriptorProvider<InsiderAppFrameViewComponentDescriptor>();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
- (instancetype)initWithFrame:(CGRect)frame {
|
|
126
|
+
if (self = [super initWithFrame:frame]) {
|
|
127
|
+
_props = RNInsiderAppFrameViewDefaultProps();
|
|
128
|
+
|
|
129
|
+
_frameView = [[InsiderAppFramesView alloc] initWithFrame:self.bounds];
|
|
130
|
+
_frameView.delegate = self;
|
|
131
|
+
self.contentView = _frameView;
|
|
132
|
+
}
|
|
133
|
+
return self;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps {
|
|
137
|
+
const auto &newProps = *std::static_pointer_cast<InsiderAppFrameViewProps const>(props);
|
|
138
|
+
const auto &previousProps = *std::static_pointer_cast<InsiderAppFrameViewProps const>(_props);
|
|
139
|
+
|
|
140
|
+
if (newProps.placementId != previousProps.placementId) {
|
|
141
|
+
NSString *placementId = RCTNSStringFromStringNilIfEmpty(newProps.placementId);
|
|
142
|
+
_frameView.placementId = placementId;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
[super updateProps:props oldProps:oldProps];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// A recycled view's next updateProps: diffs against `_props`, which RCTViewComponentView does
|
|
149
|
+
// not reset; without resetting it here, a view reused for the same placementId never applies it.
|
|
150
|
+
- (void)prepareForRecycle {
|
|
151
|
+
[super prepareForRecycle];
|
|
152
|
+
|
|
153
|
+
// Replace the SDK view rather than reset it. A recycled view is already off-window, so it sits
|
|
154
|
+
// in DETACHED, which keeps its reported height; native suppresses a repeat of that height, so
|
|
155
|
+
// the next occupant would never receive one and render into a zero-height box.
|
|
156
|
+
_frameView.delegate = nil;
|
|
157
|
+
[_frameView removeFromSuperview];
|
|
158
|
+
|
|
159
|
+
_frameView = [[InsiderAppFramesView alloc] initWithFrame:self.bounds];
|
|
160
|
+
_frameView.delegate = self;
|
|
161
|
+
self.contentView = _frameView;
|
|
162
|
+
|
|
163
|
+
_props = RNInsiderAppFrameViewDefaultProps();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
- (std::shared_ptr<const InsiderAppFrameViewEventEmitter>)frameEventEmitter {
|
|
167
|
+
if (!_eventEmitter) {
|
|
168
|
+
// Not expected: the emitter is set at mount, before the view reaches a window. Reported
|
|
169
|
+
// because JS mirrors status and error from these events, so a dropped one leaves it stale.
|
|
170
|
+
[Insider sendError:[NSException exceptionWithName:@"InsiderAppFrameDroppedEvent"
|
|
171
|
+
reason:@"Dropped an event: no event emitter attached yet."
|
|
172
|
+
userInfo:nil]
|
|
173
|
+
desc:[NSString stringWithFormat:@"%s:%d", __func__, __LINE__]];
|
|
174
|
+
return nullptr;
|
|
175
|
+
}
|
|
176
|
+
return std::static_pointer_cast<const InsiderAppFrameViewEventEmitter>(_eventEmitter);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
- (std::string)currentPlacementId {
|
|
180
|
+
return RCTStringFromNSString(_frameView.placementId ?: @"");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#pragma mark - InsiderAppFramesViewDelegate
|
|
184
|
+
|
|
185
|
+
- (void)appFramesView:(InsiderAppFramesView *)view
|
|
186
|
+
didChangeStatusTo:(InsiderAppFramesViewStatus)status
|
|
187
|
+
from:(InsiderAppFramesViewStatus)previousStatus {
|
|
188
|
+
auto emitter = [self frameEventEmitter];
|
|
189
|
+
if (emitter) {
|
|
190
|
+
emitter->onStatusChange({
|
|
191
|
+
.placementId = [self currentPlacementId],
|
|
192
|
+
.status = RCTStringFromNSString(RNInsiderAppFramesStatusName(status)),
|
|
193
|
+
.previousStatus = RCTStringFromNSString(RNInsiderAppFramesStatusName(previousStatus)),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// The cast below would reinterpret a foreign-domain code as a real App Frames code.
|
|
199
|
+
NSString *RNInsiderAppFramesErrorCodeNameForError(NSError *error) {
|
|
200
|
+
if (![error.domain isEqualToString:InsiderAppFramesErrorDomain]) {
|
|
201
|
+
return @"UNKNOWN";
|
|
202
|
+
}
|
|
203
|
+
return RNInsiderAppFramesErrorCodeName((InsiderAppFramesErrorCode)error.code);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
- (void)appFramesView:(InsiderAppFramesView *)view didFailLoadingWithError:(NSError *)error {
|
|
207
|
+
auto emitter = [self frameEventEmitter];
|
|
208
|
+
if (!emitter) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// `message` is the canonical per-code text, as on Android; the underlying error goes in `cause`.
|
|
213
|
+
NSError *underlying = error.userInfo[NSUnderlyingErrorKey];
|
|
214
|
+
NSString *message = error.localizedDescription ?: @"";
|
|
215
|
+
NSString *cause = underlying.localizedDescription ?: @"";
|
|
216
|
+
|
|
217
|
+
// Set when a template dismisses with a non-default code. The SDK exports no constant for this
|
|
218
|
+
// userInfo key.
|
|
219
|
+
NSNumber *dismissCode = error.userInfo[@"dismiss_code"];
|
|
220
|
+
|
|
221
|
+
NSString *codeName = RNInsiderAppFramesErrorCodeNameForError(error);
|
|
222
|
+
|
|
223
|
+
emitter->onLoadError({
|
|
224
|
+
.placementId = [self currentPlacementId],
|
|
225
|
+
.code = RCTStringFromNSString(codeName),
|
|
226
|
+
.message = RCTStringFromNSString(message),
|
|
227
|
+
.cause = RCTStringFromNSString(cause),
|
|
228
|
+
// The ternary's common type is NSInteger, which the Int32 braced initialiser rejects as
|
|
229
|
+
// narrowing (-Wc++11-narrowing).
|
|
230
|
+
.dismissCode = static_cast<int32_t>(
|
|
231
|
+
dismissCode != nil ? dismissCode.integerValue : kInsiderAppFramesNoDismissCode),
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// The height is applied in JS: Fabric assigns frames without Auto Layout, so the native view's
|
|
236
|
+
// intrinsicContentSize is never consulted.
|
|
237
|
+
- (void)appFramesView:(InsiderAppFramesView *)view didRequestHeightChangeTo:(CGFloat)optimalHeight {
|
|
238
|
+
auto emitter = [self frameEventEmitter];
|
|
239
|
+
if (emitter) {
|
|
240
|
+
emitter->onHeightChange({
|
|
241
|
+
.placementId = [self currentPlacementId],
|
|
242
|
+
.height = static_cast<double>(optimalHeight),
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
- (void)appFramesViewDidRequestDismiss:(InsiderAppFramesView *)view {
|
|
248
|
+
auto emitter = [self frameEventEmitter];
|
|
249
|
+
if (emitter) {
|
|
250
|
+
emitter->onDismissRequest({.placementId = [self currentPlacementId]});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
- (void)appFramesView:(InsiderAppFramesView *)view
|
|
255
|
+
didTriggerActionWithData:(NSDictionary<NSString *, id> *)actionData {
|
|
256
|
+
auto emitter = [self frameEventEmitter];
|
|
257
|
+
if (!emitter) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
NSString *json = nil;
|
|
262
|
+
if ([NSJSONSerialization isValidJSONObject:actionData]) {
|
|
263
|
+
NSData *data = [NSJSONSerialization dataWithJSONObject:actionData options:0 error:nil];
|
|
264
|
+
if (data) {
|
|
265
|
+
json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (!json) {
|
|
269
|
+
// The payload is lost either way, since the codegen field is non-nullable; the report is what
|
|
270
|
+
// makes the loss visible.
|
|
271
|
+
[Insider sendError:[NSException exceptionWithName:@"InsiderAppFrameUnserializableAction"
|
|
272
|
+
reason:@"Action data could not be serialized; forwarding {}."
|
|
273
|
+
userInfo:nil]
|
|
274
|
+
desc:[NSString stringWithFormat:@"%s:%d", __func__, __LINE__]];
|
|
275
|
+
json = @"{}";
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
emitter->onAction({
|
|
279
|
+
.placementId = [self currentPlacementId],
|
|
280
|
+
.data = RCTStringFromNSString(json),
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
@end
|
|
285
|
+
|
|
286
|
+
#else // !INSIDER_APP_FRAMES_AVAILABLE
|
|
287
|
+
|
|
288
|
+
// The only signal for this configuration: the inert stub registers cleanly and nothing at runtime
|
|
289
|
+
// reports it. `#pragma message` rather than `#warning`, so a -Werror build does not break over it.
|
|
290
|
+
#pragma message("[Insider] App Frames is unavailable in the linked InsiderMobile, so InsiderAppFrameView compiles as an inert stub: it registers, renders nothing and emits no events. Update InsiderMobile to 16.0.0 or later.")
|
|
291
|
+
|
|
292
|
+
@implementation RNInsiderAppFrameView
|
|
293
|
+
|
|
294
|
+
+ (ComponentDescriptorProvider)componentDescriptorProvider {
|
|
295
|
+
return concreteComponentDescriptorProvider<InsiderAppFrameViewComponentDescriptor>();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
@end
|
|
299
|
+
|
|
300
|
+
// The header declares these unconditionally, so they are defined here to avoid a link error.
|
|
301
|
+
NSString *RNInsiderAppFramesErrorCodeNameForError(__unused NSError *error) {
|
|
302
|
+
return @"UNKNOWN";
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
NSString *RNInsiderAppFramesStatusNameForRawStatus(__unused NSInteger status) {
|
|
306
|
+
return @"UNRECOGNIZED";
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
#endif // INSIDER_APP_FRAMES_AVAILABLE
|
|
310
|
+
|
|
311
|
+
Class<RCTComponentViewProtocol> InsiderAppFrameViewCls(void) {
|
|
312
|
+
return RNInsiderAppFrameView.class;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
#endif // RCT_NEW_ARCH_ENABLED
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
#endif
|
|
16
16
|
|
|
17
17
|
@interface RNNotificationHandler : RCTEventEmitter<RCTBridgeModule>
|
|
18
|
+
|
|
18
19
|
extern NSString * const NOTIFICATION_OPEN;
|
|
19
20
|
extern NSString * const INAPP_BUTTON_CLICK;
|
|
20
21
|
extern NSString * const TEMP_STORE_PURCHASE;
|
|
@@ -24,6 +25,8 @@ extern NSString * const INAPP_SEEN;
|
|
|
24
25
|
extern NSString * const FOREGROUND_PUSH;
|
|
25
26
|
extern NSString * const INSIDER_ID_LISTENER;
|
|
26
27
|
extern NSString * const SESSION_STARTED;
|
|
28
|
+
extern NSString * const INSIDER_EVENT_RECORDED;
|
|
29
|
+
|
|
30
|
+
- (void)sendDataToJS:(NSString *)eventName data:(NSDictionary *)data;
|
|
27
31
|
|
|
28
|
-
-(void)sendDataToJS:(NSString *)eventName data:(NSDictionary *)data;
|
|
29
32
|
@end
|
|
@@ -24,36 +24,34 @@ NSString * const INAPP_SEEN = @"INAPP_SEEN";
|
|
|
24
24
|
NSString * const FOREGROUND_PUSH = @"FOREGROUND_PUSH";
|
|
25
25
|
NSString * const INSIDER_ID_LISTENER = @"INSIDER_ID_LISTENER";
|
|
26
26
|
NSString * const SESSION_STARTED = @"SESSION_STARTED";
|
|
27
|
+
NSString * const INSIDER_EVENT_RECORDED = @"INSIDER_EVENT_RECORDED";
|
|
27
28
|
|
|
28
29
|
static RNNotificationHandler *notificationHandler;
|
|
29
30
|
|
|
30
|
-
+(id)allocWithZone:(struct _NSZone *)zone{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
+ (id)allocWithZone:(struct _NSZone *)zone {
|
|
32
|
+
static dispatch_once_t onceToken;
|
|
33
|
+
dispatch_once(&onceToken, ^{
|
|
34
|
+
notificationHandler = [super allocWithZone:zone];
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return notificationHandler;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
+ (BOOL)requiresMainQueueSetup {
|
|
41
|
+
return NO;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
RCT_EXPORT_MODULE();
|
|
43
45
|
|
|
44
|
-
-(NSArray<NSString *> *)supportedEvents{
|
|
45
|
-
return @[NOTIFICATION_OPEN, INAPP_BUTTON_CLICK, TEMP_STORE_PURCHASE, TEMP_STORE_ADDED_TO_CART, TEMP_STORE_CUSTOM_ACTION, INAPP_SEEN, FOREGROUND_PUSH, INSIDER_ID_LISTENER, SESSION_STARTED];
|
|
46
|
+
- (NSArray<NSString *> *)supportedEvents {
|
|
47
|
+
return @[NOTIFICATION_OPEN, INAPP_BUTTON_CLICK, TEMP_STORE_PURCHASE, TEMP_STORE_ADDED_TO_CART, TEMP_STORE_CUSTOM_ACTION, INAPP_SEEN, FOREGROUND_PUSH, INSIDER_ID_LISTENER, SESSION_STARTED, INSIDER_EVENT_RECORDED];
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
-(void)sendDataToJS:(NSString *)eventName data:(NSDictionary *)data {
|
|
50
|
+
- (void)sendDataToJS:(NSString *)eventName data:(NSDictionary *)data {
|
|
49
51
|
@try {
|
|
50
|
-
if (!notificationHandler){
|
|
51
|
-
[Insider sendError:nil desc:@"RNNotificationHandler.m - sendDataToJS. NotificationHandler is not initialized."];
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
52
|
[self sendEventWithName:eventName body:data];
|
|
55
53
|
} @catch (NSException *e) {
|
|
56
|
-
[Insider sendError:e desc:@"
|
|
54
|
+
[Insider sendError:e desc:[NSString stringWithFormat:@"%s:%d", __func__, __LINE__]];
|
|
57
55
|
}
|
|
58
56
|
}
|
|
59
57
|
@end
|
package/ios/RNInsider/RNUtils.h
CHANGED
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
|
|
26
26
|
+ (nonnull NSDictionary<NSString *, id> *)parseCustomParameters:(nonnull NSArray *)customParameters;
|
|
27
27
|
|
|
28
|
+
+ (nonnull NSDictionary<NSString *, id> *)convertParametersForBridge:(nullable NSDictionary *)parameters;
|
|
29
|
+
|
|
28
30
|
+ (nonnull NSString *)mapAppCardsErrorCode:(nonnull NSError *)error;
|
|
29
31
|
+ (nonnull NSDictionary *)appCardsErrorToDictionary:(nonnull NSError *)error;
|
|
30
32
|
|
package/ios/RNInsider/RNUtils.m
CHANGED
|
@@ -283,6 +283,85 @@
|
|
|
283
283
|
return mappedCustomParameters;
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Normalizes a recorded-event `parameters` dictionary into values React Native's bridge
|
|
288
|
+
* can actually serialize, mirroring Android's `RNUtils.convertObjectMapToWritableMap`.
|
|
289
|
+
*
|
|
290
|
+
* WHY THIS IS NOT OPTIONAL: RN converts an Obj-C payload with
|
|
291
|
+
* `convertIdToFollyDynamic` (ReactCommon/react/utils/platform/ios/react/utils/FollyConvert.mm).
|
|
292
|
+
* That function dispatches ONLY on NSNumber / NSString / NSArray / NSDictionary / nil /
|
|
293
|
+
* NSNull. Anything else — NSDate above all, but also NSURL, NSData, NSUUID — falls off
|
|
294
|
+
* the end of the chain and hits `return nil`, producing a NULL folly::dynamic. The key
|
|
295
|
+
* survives but its value silently arrives in JS as `null`, so a `Date` parameter that
|
|
296
|
+
* Android delivers as epoch-millis would reach an iOS integrator as nothing at all.
|
|
297
|
+
*
|
|
298
|
+
* Conversions applied, recursively through nested dictionaries and arrays:
|
|
299
|
+
* - NSDate -> epoch MILLISECONDS as an NSNumber, matching Android's
|
|
300
|
+
* `map.putDouble(key, ((Date) value).getTime())` and the repo's
|
|
301
|
+
* epoch-millis bridge convention for dates.
|
|
302
|
+
* - NSNull -> preserved (RN maps it to JS `null`).
|
|
303
|
+
* - Anything else non-serializable -> its `description`, so ONE odd value degrades to a
|
|
304
|
+
* readable string instead of vanishing.
|
|
305
|
+
*
|
|
306
|
+
* NOTE: this is only about `Date`s nested INSIDE `parameters`. The top-level `timestamp`
|
|
307
|
+
* of a recorded event stays in WHOLE SECONDS by explicit product decision — see
|
|
308
|
+
* `RNInsiderEventListener.onEventRecorded` in RNInsider.m.
|
|
309
|
+
*/
|
|
310
|
+
+ (NSDictionary<NSString *, id> *)convertParametersForBridge:(NSDictionary *)parameters {
|
|
311
|
+
if (![parameters isKindOfClass:[NSDictionary class]]) {
|
|
312
|
+
return @{};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
NSMutableDictionary<NSString *, id> *converted = [NSMutableDictionary dictionaryWithCapacity:parameters.count];
|
|
316
|
+
|
|
317
|
+
for (id key in parameters) {
|
|
318
|
+
// The bridge can only key an object by a string.
|
|
319
|
+
NSString *stringKey = [key isKindOfClass:[NSString class]] ? (NSString *)key : [key description];
|
|
320
|
+
if (stringKey == nil) continue;
|
|
321
|
+
|
|
322
|
+
converted[stringKey] = [self convertValueForBridge:parameters[key]];
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return converted;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/// Recursive leaf/branch conversion backing `convertParametersForBridge:`.
|
|
329
|
+
+ (id)convertValueForBridge:(id)value {
|
|
330
|
+
if (value == nil || value == [NSNull null]) {
|
|
331
|
+
return [NSNull null];
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if ([value isKindOfClass:[NSDictionary class]]) {
|
|
335
|
+
return [self convertParametersForBridge:(NSDictionary *)value];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if ([value isKindOfClass:[NSArray class]]) {
|
|
339
|
+
NSArray *array = (NSArray *)value;
|
|
340
|
+
NSMutableArray *convertedArray = [NSMutableArray arrayWithCapacity:array.count];
|
|
341
|
+
|
|
342
|
+
for (id element in array) {
|
|
343
|
+
[convertedArray addObject:[self convertValueForBridge:element]];
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return convertedArray;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// NSDate -> epoch millis. Checked before the NSNumber/NSString passthrough because a
|
|
350
|
+
// date is the value type that actually motivated this converter.
|
|
351
|
+
if ([value isKindOfClass:[NSDate class]]) {
|
|
352
|
+
NSTimeInterval seconds = [(NSDate *)value timeIntervalSince1970];
|
|
353
|
+
return @((long long)llround(seconds * 1000.0));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// NSNumber (incl. booleans) and NSString cross the bridge untouched.
|
|
357
|
+
if ([value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSString class]]) {
|
|
358
|
+
return value;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Unknown leaf: degrade to a readable string rather than letting RN null it out.
|
|
362
|
+
return [value description] ?: [NSNull null];
|
|
363
|
+
}
|
|
364
|
+
|
|
286
365
|
+ (NSString *)mapAppCardsErrorCode:(NSError *)error {
|
|
287
366
|
if ([error.domain isEqualToString:InsiderAppCardsErrorDomain]) {
|
|
288
367
|
switch (error.code) {
|
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-insider",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0-nh",
|
|
4
4
|
"description": "React Native Insider SDK",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"test": "jest",
|
|
9
|
-
"test:
|
|
9
|
+
"test:unit": "jest --selectProjects unit",
|
|
10
|
+
"test:integration": "jest --selectProjects integration",
|
|
11
|
+
"test:coverage": "jest --coverage --coverageReporters=text --coverageReporters=lcov --coverageReporters=json-summary",
|
|
12
|
+
"test:mutation": "stryker run",
|
|
13
|
+
"typecheck": "tsc --noEmit"
|
|
10
14
|
},
|
|
11
15
|
"keywords": [
|
|
12
16
|
"react-native",
|
|
@@ -20,23 +24,50 @@
|
|
|
20
24
|
],
|
|
21
25
|
"author": "insidermobile",
|
|
22
26
|
"homepage": "https://github.com/useinsider/react-native-insider",
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"react": ">=18.0.0",
|
|
29
|
+
"react-native": ">=0.74.0"
|
|
30
|
+
},
|
|
23
31
|
"devDependencies": {
|
|
24
|
-
"@babel/core": "^7.
|
|
32
|
+
"@babel/core": "^7.29.6",
|
|
25
33
|
"@babel/preset-env": "^7.20.0",
|
|
26
34
|
"@react-native/babel-preset": "0.83.0",
|
|
27
|
-
"
|
|
35
|
+
"@stryker-mutator/core": "^9.6.1",
|
|
36
|
+
"@stryker-mutator/jest-runner": "^9.6.1",
|
|
37
|
+
"@testing-library/react-native": "^14.0.1",
|
|
38
|
+
"babel-jest": "^29.7.0",
|
|
28
39
|
"jest": "^29.7.0",
|
|
40
|
+
"jest-config": "^29.7.0",
|
|
41
|
+
"jest-util": "^29.7.0",
|
|
29
42
|
"react": "19.2.0",
|
|
30
|
-
"react-native": "0.83.0"
|
|
43
|
+
"react-native": "0.83.0",
|
|
44
|
+
"react-test-renderer": "19.2.0",
|
|
45
|
+
"typescript": "^5.9.3"
|
|
31
46
|
},
|
|
32
47
|
"overrides": {
|
|
33
|
-
"shell-quote": "^1.
|
|
48
|
+
"shell-quote": "^1.9.0",
|
|
49
|
+
"js-yaml": "^3.15.2",
|
|
50
|
+
"brace-expansion": "^1.1.18",
|
|
51
|
+
"@stryker-mutator/core": {
|
|
52
|
+
"minimatch": {
|
|
53
|
+
"brace-expansion": "^5.0.8"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"browserslist": "^4.28.7",
|
|
57
|
+
"baseline-browser-mapping": "^2.11.0",
|
|
58
|
+
"ws": "^7.5.11"
|
|
34
59
|
},
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
60
|
+
"codegenConfig": {
|
|
61
|
+
"name": "RNInsiderSpec",
|
|
62
|
+
"type": "components",
|
|
63
|
+
"jsSrcsDir": "src/specs",
|
|
64
|
+
"android": {
|
|
65
|
+
"javaPackageName": "com.useinsider.react"
|
|
66
|
+
},
|
|
67
|
+
"ios": {
|
|
68
|
+
"componentProvider": {
|
|
69
|
+
"InsiderAppFrameView": "RNInsiderAppFrameView"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
41
72
|
}
|
|
42
73
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { NativeModules, NativeEventEmitter } from 'react-native';
|
|
2
|
+
|
|
3
|
+
const NotificationHandler = NativeModules.RNNotificationHandler;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The single NativeEventEmitter for `RNNotificationHandler`, shared by every module that
|
|
7
|
+
* listens to native → JS events (`index.js` for the init/push/ID callbacks,
|
|
8
|
+
* `src/InsiderEvents.js` for the recorded-event stream).
|
|
9
|
+
*
|
|
10
|
+
* It MUST stay a single instance. Constructing a second emitter over the same native
|
|
11
|
+
* module would make the native side see two independent listener sets, so listener
|
|
12
|
+
* bookkeeping (`addListener` counts, `removeAllListeners`) would be split across emitters
|
|
13
|
+
* and each side would be blind to the other's subscriptions.
|
|
14
|
+
*/
|
|
15
|
+
const eventHandler = new NativeEventEmitter(NotificationHandler);
|
|
16
|
+
|
|
17
|
+
export default eventHandler;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type {ForwardRefExoticComponent, RefAttributes} from 'react';
|
|
2
|
+
import type {ViewProps} from 'react-native';
|
|
3
|
+
import type {InsiderAppFramesError} from './InsiderAppFramesError';
|
|
4
|
+
import type {InsiderAppFrameStatusType} from './InsiderAppFrameStatus';
|
|
5
|
+
|
|
6
|
+
export interface InsiderAppFrameProps extends ViewProps {
|
|
7
|
+
/** The placement whose content this frame renders. */
|
|
8
|
+
placementId: string;
|
|
9
|
+
/** Called on every status transition with the new status and the one it replaced. */
|
|
10
|
+
onStatusChange?: (
|
|
11
|
+
status: InsiderAppFrameStatusType,
|
|
12
|
+
previousStatus: InsiderAppFrameStatusType,
|
|
13
|
+
) => void;
|
|
14
|
+
/** Called when resolving, downloading or rendering the content fails. */
|
|
15
|
+
onLoadError?: (error: InsiderAppFramesError) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Called when the content reports a new height, in dp. The frame applies this height itself
|
|
18
|
+
* unless `style.height` is set, which always wins.
|
|
19
|
+
*/
|
|
20
|
+
onHeightChange?: (height: number) => void;
|
|
21
|
+
/** Called when the content asks to be dismissed. A request, not a command. */
|
|
22
|
+
onDismissRequest?: () => void;
|
|
23
|
+
/** Called with the payload of a custom action defined in the content. */
|
|
24
|
+
onAction?: (data: Record<string, unknown>) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Imperative access to a frame's current state, through a ref. */
|
|
28
|
+
export interface InsiderAppFrameHandle {
|
|
29
|
+
/** The frame's current status; `DETACHED` until the first transition. */
|
|
30
|
+
getStatus(): InsiderAppFrameStatusType;
|
|
31
|
+
/** The error behind the current status; non-null only while the status is an `ERROR_*` value. */
|
|
32
|
+
getError(): InsiderAppFramesError | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Renders the content of an App Frames placement.
|
|
37
|
+
*
|
|
38
|
+
* On iOS, requires the New Architecture and React Native 0.77 or later. Without the New
|
|
39
|
+
* Architecture the frame renders nothing; below 0.77 it may not register. Either case logs a
|
|
40
|
+
* one-time warning in development.
|
|
41
|
+
*/
|
|
42
|
+
declare const InsiderAppFrame: ForwardRefExoticComponent<
|
|
43
|
+
InsiderAppFrameProps & RefAttributes<InsiderAppFrameHandle>
|
|
44
|
+
>;
|
|
45
|
+
export default InsiderAppFrame;
|