airborne-react-native 0.1.0 → 0.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.
@@ -33,7 +33,10 @@ Pod::Spec.new do |s|
33
33
  }
34
34
 
35
35
  s.source_files = "ios/**/*.{h,m,mm,cpp}"
36
- s.private_header_files = "ios/**/*.h"
36
+ s.public_header_files = "ios/**/*.h"
37
+ s.static_framework = false
38
+
39
+ s.dependency "Airborne", "0.0.4"
37
40
 
38
41
  install_modules_dependencies(s)
39
42
  end
package/ios/Airborne.h CHANGED
@@ -1,3 +1,4 @@
1
+ #import "AirborneiOS.h"
1
2
  #ifdef RCT_NEW_ARCH_ENABLED
2
3
  #import <AirborneSpec/AirborneSpec.h>
3
4
 
@@ -5,13 +6,20 @@
5
6
  #else
6
7
  #import <React/RCTBridgeModule.h>
7
8
 
9
+
8
10
  @interface Airborne : NSObject <RCTBridgeModule>
9
11
  #endif
10
12
 
11
- + (void)initializeAirborneWithAppId:(NSString *)appId
12
- indexFileName:(NSString *)indexFileName
13
- appVersion:(NSString *)appVersion
14
- releaseConfigTemplateUrl:(NSString *)releaseConfigTemplateUrl
15
- headers:(nullable NSDictionary<NSString *, NSString *> *)headers;
13
+ + (void)initializeAirborneWithReleaseConfigUrl:(NSString *) releaseConfigUrl;
14
+
15
+ + (void)initializeAirborneWithReleaseConfigUrl:(NSString *) releaseConfigUrl
16
+ inNamespace:(NSString *) ns;
17
+
18
+ + (void)initializeAirborneWithReleaseConfigUrl:(NSString *)releaseConfigUrl
19
+ delegate:delegate;
20
+
21
+ + (void)initializeAirborneWithReleaseConfigUrl:(NSString *) releaseConfigUrl
22
+ inNamespace:(NSString *) ns
23
+ delegate:(id<AirborneReactDelegate>) delegate;
16
24
 
17
25
  @end
package/ios/Airborne.mm CHANGED
@@ -1,34 +1,39 @@
1
1
  #import "Airborne.h"
2
2
  #import "AirborneiOS.h"
3
3
  #import <React/RCTLog.h>
4
+ #import <Airborne/Airborne.h>
5
+ #import <Airborne/Airborne-Swift.h>
4
6
 
5
7
  @implementation Airborne
6
8
 
7
9
  RCT_EXPORT_MODULE(Airborne)
8
10
 
9
- + (void)initializeAirborneWithAppId:(NSString *)appId
10
- indexFileName:(NSString *)indexFileName
11
- appVersion:(NSString *)appVersion
12
- releaseConfigTemplateUrl:(NSString *)releaseConfigTemplateUrl
13
- headers:(nullable NSDictionary<NSString *, NSString *> *)headers {
14
- [[AirborneiOS sharedInstance] initializeWithAppId:appId
15
- indexFileName:indexFileName
16
- appVersion:appVersion
17
- releaseConfigTemplateUrl:releaseConfigTemplateUrl
18
- headers:headers
19
- lazyDownloadCallback:^(NSString *filePath, BOOL success) {
20
- RCTLogInfo(@"Airborne: File %@ - %@", filePath, success ? @"installed" : @"failed");
21
- }
22
- lazySplitsCallback:^(BOOL success) {
23
- RCTLogInfo(@"Airborne: Lazy splits - %@", success ? @"installed" : @"failed");
24
- }];
11
+ static NSString * const defaultNamespace = @"default";
12
+
13
+ + (void)initializeAirborneWithReleaseConfigUrl:(NSString *)releaseConfigUrl {
14
+ [self initializeAirborneWithReleaseConfigUrl:releaseConfigUrl inNamespace:defaultNamespace];
15
+ }
16
+
17
+ + (void)initializeAirborneWithReleaseConfigUrl:(NSString *)releaseConfigUrl inNamespace:ns {
18
+ AirborneiOS* air = [AirborneiOS sharedInstanceWithNamespace:ns];
19
+ [air loadWithReleaseConfig:releaseConfigUrl delegate:nil];
20
+ }
21
+
22
+ + (void)initializeAirborneWithReleaseConfigUrl:(NSString *)releaseConfigUrl delegate:delegate {
23
+ AirborneiOS* air = [AirborneiOS sharedInstanceWithNamespace:defaultNamespace];
24
+ [air loadWithReleaseConfig:releaseConfigUrl delegate:delegate];
25
+ }
26
+
27
+ + (void)initializeAirborneWithReleaseConfigUrl:(NSString *)releaseConfigUrl inNamespace:ns delegate:delegate {
28
+ AirborneiOS* air = [AirborneiOS sharedInstanceWithNamespace:ns];
29
+ [air loadWithReleaseConfig:releaseConfigUrl delegate:delegate];
25
30
  }
26
31
 
27
32
  #ifdef RCT_NEW_ARCH_ENABLED
28
33
  - (void)readReleaseConfig:(RCTPromiseResolveBlock)resolve
29
34
  reject:(RCTPromiseRejectBlock)reject {
30
35
  @try {
31
- NSString *config = [[AirborneiOS sharedInstance] getReleaseConfig];
36
+ NSString *config = [[AirborneiOS sharedInstanceWithNamespace:defaultNamespace] getReleaseConfig];
32
37
  resolve(config);
33
38
  } @catch (NSException *exception) {
34
39
  reject(@"AIRBORNE_ERROR", exception.reason, nil);
@@ -39,7 +44,7 @@ RCT_EXPORT_MODULE(Airborne)
39
44
  resolve:(RCTPromiseResolveBlock)resolve
40
45
  reject:(RCTPromiseRejectBlock)reject {
41
46
  @try {
42
- NSString *content = [[AirborneiOS sharedInstance] getFileContent:filePath];
47
+ NSString *content = [[AirborneiOS sharedInstanceWithNamespace:defaultNamespace] getFileContent:filePath];
43
48
  resolve(content);
44
49
  } @catch (NSException *exception) {
45
50
  reject(@"AIRBORNE_ERROR", exception.reason, nil);
@@ -49,7 +54,7 @@ RCT_EXPORT_MODULE(Airborne)
49
54
  - (void)getBundlePath:(RCTPromiseResolveBlock)resolve
50
55
  reject:(RCTPromiseRejectBlock)reject {
51
56
  @try {
52
- NSString *bundlePath = [[AirborneiOS sharedInstance] getBundlePath];
57
+ NSString *bundlePath = [[AirborneiOS sharedInstanceWithNamespace:defaultNamespace] getBundlePath];
53
58
  resolve(bundlePath);
54
59
  } @catch (NSException *exception) {
55
60
  reject(@"AIRBORNE_ERROR", exception.reason, nil);
@@ -59,7 +64,7 @@ RCT_EXPORT_MODULE(Airborne)
59
64
  RCT_EXPORT_METHOD(readReleaseConfig:(RCTPromiseResolveBlock)resolve
60
65
  rejecter:(RCTPromiseRejectBlock)reject) {
61
66
  @try {
62
- NSString *config = [[AirborneiOS sharedInstance] getReleaseConfig];
67
+ NSString *config = [[AirborneiOS sharedInstanceWithNamespace:defaultNamespace] getReleaseConfig];
63
68
  resolve(config);
64
69
  } @catch (NSException *exception) {
65
70
  reject(@"AIRBORNE_ERROR", exception.reason, nil);
@@ -70,7 +75,7 @@ RCT_EXPORT_METHOD(getFileContent:(NSString *)filePath
70
75
  resolver:(RCTPromiseResolveBlock)resolve
71
76
  rejecter:(RCTPromiseRejectBlock)reject) {
72
77
  @try {
73
- NSString *content = [[AirborneiOS sharedInstance] getFileContent:filePath];
78
+ NSString *content = [[AirborneiOS sharedInstanceWithNamespace:defaultNamespace] getFileContent:filePath];
74
79
  resolve(content);
75
80
  } @catch (NSException *exception) {
76
81
  reject(@"AIRBORNE_ERROR", exception.reason, nil);
@@ -80,7 +85,7 @@ RCT_EXPORT_METHOD(getFileContent:(NSString *)filePath
80
85
  RCT_EXPORT_METHOD(getBundlePath:(RCTPromiseResolveBlock)resolve
81
86
  rejecter:(RCTPromiseRejectBlock)reject) {
82
87
  @try {
83
- NSString *bundlePath = [[AirborneiOS sharedInstance] getBundlePath];
88
+ NSString *bundlePath = [[AirborneiOS sharedInstanceWithNamespace:defaultNamespace] getBundlePath];
84
89
  resolve(bundlePath);
85
90
  } @catch (NSException *exception) {
86
91
  reject(@"AIRBORNE_ERROR", exception.reason, nil);
package/ios/AirborneiOS.h CHANGED
@@ -2,21 +2,70 @@
2
2
 
3
3
  NS_ASSUME_NONNULL_BEGIN
4
4
 
5
+ @protocol AirborneReactDelegate <NSObject>
6
+
7
+ /**
8
+ * Returns custom dimensions/metadata to include with release configuration requests.
9
+ *
10
+ * These dimensions are sent as HTTP headers when fetching the release configuration
11
+ * and can be used for:
12
+ * - A/B testing and feature flags
13
+ * - Device-specific configurations
14
+ * - User segmentation
15
+ * - Analytics and debugging context
16
+ *
17
+ * @return A dictionary of header field names and values to include in network requests.
18
+ * If not implemented, defaults to an empty dictionary.
19
+ */
20
+ - (NSDictionary<NSString *, NSString *> *)getDimensions;
21
+
22
+ /**
23
+ * Called when the OTA boot process has completed successfully.
24
+ *
25
+ * This callback indicates that the application is ready to load the packages & resources
26
+ *
27
+ * @note This method is called on a background queue. Dispatch UI updates
28
+ * to the main queue if needed.
29
+ * @note Boot completion occurs even if some downloads failed or timed out.
30
+ * Check the release configuration for actual status.
31
+ */
32
+ - (void)onBootComplete:(NSString *) bundlePath;
33
+
34
+ /**
35
+ * Called when significant events occur during the OTA update process.
36
+ *
37
+ * This callback provides detailed information about:
38
+ * - Download progress and completion
39
+ * - Error conditions and failures
40
+ * - Performance metrics and timing
41
+ * - State transitions in the update process
42
+ *
43
+ * @param level The severity level of the event ("info", "error", "warning")
44
+ * @param label A category label for the event (e.g., "ota_update")
45
+ * @param key A specific identifier for the event type
46
+ * @param value Additional structured data about the event
47
+ * @param category The broad category of the event (e.g., "lifecycle")
48
+ * @param subcategory The specific subcategory (e.g., "hyperota")
49
+ *
50
+ * @note Use this for logging, analytics, debugging, and monitoring OTA performance.
51
+ */
52
+ - (void)onEventWithLevel:(NSString *)level
53
+ label:(NSString *)label
54
+ key:(NSString *)key
55
+ value:(NSDictionary<NSString *, id> *)value
56
+ category:(NSString *)category
57
+ subcategory:(NSString *)subcategory;
58
+
59
+ @end
60
+
5
61
  typedef void (^HyperOTALazyDownloadCallback)(NSString *filePath, BOOL success);
6
62
  typedef void (^HyperOTALazySplitsCallback)(BOOL success);
7
63
 
8
64
  @interface AirborneiOS : NSObject
9
65
 
10
- + (instancetype)sharedInstance;
11
-
12
- - (void)initializeWithAppId:(NSString *)appId
13
- indexFileName:(NSString *)indexFileName
14
- appVersion:(NSString *)appVersion
15
- releaseConfigTemplateUrl:(NSString *)releaseConfigTemplateUrl
16
- headers:(nullable NSDictionary<NSString *, NSString *> *)headers
17
- lazyDownloadCallback:(nullable HyperOTALazyDownloadCallback)lazyDownloadCallback
18
- lazySplitsCallback:(nullable HyperOTALazySplitsCallback)lazySplitsCallback;
66
+ + (instancetype)sharedInstanceWithNamespace:(NSString *)ns;
19
67
 
68
+ - (void) loadWithReleaseConfig:(NSString *) rcurl delegate:(id<AirborneReactDelegate>) delegate;
20
69
  - (NSString *)getBundlePath;
21
70
  - (NSString *)getFileContent:(NSString *)filePath;
22
71
  - (NSString *)getReleaseConfig;
package/ios/AirborneiOS.m CHANGED
@@ -1,128 +1,114 @@
1
1
  #import "AirborneiOS.h"
2
+ #import "Airborne/Airborne.h"
3
+
4
+
5
+ @interface AirborneLocalDelegate : NSObject<AirborneDelegate>
6
+ @property (nonatomic, weak) NSString* ns;
7
+ @property (nonatomic, weak) id<AirborneReactDelegate> delegate;
8
+ -(instancetype)initWithNamespace:(NSString*) ns
9
+ delegate:(id<AirborneReactDelegate>) delegate;
10
+ @end
11
+
12
+ @implementation AirborneLocalDelegate
13
+
14
+ -(instancetype)initWithNamespace:(NSString*) ns delegate:(id<AirborneReactDelegate>) del {
15
+ self = [super init];
16
+ if (self) {
17
+ _ns = ns;
18
+ _delegate = del;
19
+ }
20
+ return self;
21
+ }
22
+
23
+ - (NSString *)namespace{
24
+ return self.ns;
25
+ }
26
+
27
+ - (NSDictionary *)dimensions{
28
+ if(_delegate == nil) return @{};
29
+ return [_delegate getDimensions];
30
+ }
31
+
32
+ - (void)onBootCompleteWithIndexBundlePath:(NSString *)indexBundlePath{
33
+ if (_delegate == nil) return;
34
+ [_delegate onBootComplete:indexBundlePath];
35
+ }
36
+
37
+ -(void)onEventWithLevel:(NSString *)level label:(NSString *)label key:(NSString *)key value:(NSDictionary<NSString *,id> *)value category:(NSString *)category subcategory:(NSString *)subcategory{
38
+ if (_delegate == nil) return;
39
+ [_delegate onEventWithLevel:level label:label key:key value:value category:category subcategory:subcategory];
40
+ }
41
+
42
+ @end
43
+
2
44
 
3
45
  @interface AirborneiOS ()
4
- @property (nonatomic, assign) BOOL isInitialized;
5
- @property (nonatomic, strong) NSString *indexFileName;
6
- // In a real implementation, you would have references to the actual Airborne SDK objects here
46
+ @property (nonatomic, strong) NSString* ns;
47
+ @property (nonatomic, strong) AirborneServices* air;
48
+ @property (nonatomic, strong) id<AirborneDelegate> delegate;
49
+ @property (nonatomic, strong) AirborneLocalDelegate* delegateproxy;
50
+
7
51
  @end
8
52
 
9
53
  @implementation AirborneiOS
10
54
 
11
- + (instancetype)sharedInstance {
12
- static AirborneiOS *sharedInstance = nil;
55
+ + (instancetype)sharedInstanceWithNamespace:(NSString *)namespace{
56
+ static NSMutableDictionary<NSString *, AirborneiOS *> *instances = nil;
57
+ static dispatch_queue_t syncQueue;
13
58
  static dispatch_once_t onceToken;
59
+
60
+ // Initialize dictionary and queue once
14
61
  dispatch_once(&onceToken, ^{
15
- sharedInstance = [[self alloc] init];
62
+ instances = [NSMutableDictionary dictionary];
63
+ syncQueue = dispatch_queue_create("in.juspay.Airborne.singleton", DISPATCH_QUEUE_CONCURRENT);
16
64
  });
17
- return sharedInstance;
65
+
66
+ __block AirborneiOS *instance = nil;
67
+
68
+ // Read existing instance (concurrent)
69
+ dispatch_sync(syncQueue, ^{
70
+ instance = instances[namespace];
71
+ });
72
+
73
+ if (instance == nil) {
74
+ // Write new instance (barrier to prevent concurrent writes)
75
+ dispatch_barrier_sync(syncQueue, ^{
76
+ if (!instances[namespace]) {
77
+ instances[namespace] = [[self alloc] initWithNamespace:namespace];
78
+ }
79
+ instance = instances[namespace];
80
+ });
81
+ }
82
+
83
+ return instance;
18
84
  }
19
85
 
20
- - (instancetype)init {
86
+
87
+ - (instancetype)initWithNamespace:(NSString *) ns{
21
88
  self = [super init];
22
89
  if (self) {
23
- _isInitialized = NO;
90
+ _ns = ns;
91
+
24
92
  }
25
93
  return self;
26
94
  }
27
95
 
28
- - (void)initializeWithAppId:(NSString *)appId
29
- indexFileName:(NSString *)indexFileName
30
- appVersion:(NSString *)appVersion
31
- releaseConfigTemplateUrl:(NSString *)releaseConfigTemplateUrl
32
- headers:(nullable NSDictionary<NSString *, NSString *> *)headers
33
- lazyDownloadCallback:(nullable HyperOTALazyDownloadCallback)lazyDownloadCallback
34
- lazySplitsCallback:(nullable HyperOTALazySplitsCallback)lazySplitsCallback {
35
-
36
- if (self.isInitialized) {
37
- NSLog(@"AirborneiOS: Already initialized");
38
- return;
39
- }
40
-
41
- self.indexFileName = indexFileName;
42
-
43
- // TODO: Initialize the actual Airborne SDK here
44
- // This is a placeholder implementation
45
- // In a real implementation, you would:
46
- // 1. Import the HyperOTA iOS SDK
47
- // 2. Initialize HyperOTAServices with the provided parameters
48
- // 3. Create an ApplicationManager
49
- // 4. Load the application
50
-
51
- NSLog(@"AirborneiOS: Initializing with appId: %@, indexFileName: %@, appVersion: %@",
52
- appId, indexFileName, appVersion);
53
-
54
- self.isInitialized = YES;
55
-
56
- // Simulate callbacks for demo purposes
57
- if (lazyDownloadCallback) {
58
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
59
- lazyDownloadCallback(@"demo/file.js", YES);
60
- });
61
- }
62
-
63
- if (lazySplitsCallback) {
64
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
65
- lazySplitsCallback(YES);
66
- });
67
- }
96
+ - (void)loadWithReleaseConfig:(NSString *) rcurl delegate:(id<AirborneReactDelegate>) delegate{
97
+ _delegateproxy = [[AirborneLocalDelegate alloc] initWithNamespace: self.ns delegate:delegate];
98
+ _air = [[AirborneServices alloc] initWithReleaseConfigURL:rcurl delegate:_delegateproxy];
68
99
  }
69
100
 
70
101
  - (NSString *)getBundlePath {
71
- // if (!self.isInitialized) {
72
- // @throw [NSException exceptionWithName:@"HyperOTANotInitialized"
73
- // reason:@"HyperOTA is not initialized. Call initialize first."
74
- // userInfo:nil];
75
- // }
76
- //
77
- // // TODO: Get the actual bundle path from HyperOTA SDK //
78
- // // This is a placeholder implementation
79
- // NSString *bundlePath = [[NSBundle mainBundle] pathForResource:self.indexFileName ofType:nil];
80
- // if (!bundlePath) {
81
- // bundlePath = [NSString stringWithFormat:@"assets://%@", self.indexFileName];
82
- // }
83
- //
84
- // return bundlePath;
85
- return @"";
102
+ return [_air getIndexBundlePath] ;
86
103
  }
87
104
 
88
105
  - (NSString *)getFileContent:(NSString *)filePath {
89
- // if (!self.isInitialized) {
90
- // @throw [NSException exceptionWithName:@"HyperOTANotInitialized"
91
- // reason:@"HyperOTA is not initialized. Call initialize first."
92
- // userInfo:nil];
93
- // }
94
- //
95
- // // TODO: Read the actual file content from HyperOTA SDK //
96
- // // This is a placeholder implementation
97
- // return [NSString stringWithFormat:@"File content for: %@", filePath];
98
- return @"";
106
+ return [_air getFileContentAtPath:filePath];
99
107
  }
100
108
 
101
109
  - (NSString *)getReleaseConfig {
102
- // if (!self.isInitialized) {
103
- // @throw [NSException exceptionWithName:@"HyperOTANotInitialized"
104
- // reason:@"HyperOTA is not initialized. Call initialize first."
105
- // userInfo:nil];
106
- // }
107
- //
108
- // // TODO: Get the actual release config from HyperOTA SDK //
109
- // // This is a placeholder implementation
110
- // NSDictionary *config = @{
111
- // @"version": @"1.0.0",
112
- // @"environment": @"production",
113
- // @"features": @{
114
- // @"featureA": @YES,
115
- // @"featureB": @NO
116
- // }
117
- // };
118
- //
119
- // NSError *error;
120
- // NSData *jsonData = [NSJSONSerialization dataWithJSONObject:config options:0 error:&error];
121
- // if (jsonData) {
122
- // return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
123
- // }
124
-
125
- return @"{}";
110
+ return [_air getReleaseConfig];
126
111
  }
127
112
 
128
113
  @end
114
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "airborne-react-native",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Airborne",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -58,6 +58,9 @@
58
58
  "publishConfig": {
59
59
  "registry": "https://registry.npmjs.org/"
60
60
  },
61
+ "dependencies": {
62
+ "airborne-cli-react-native": "file:../airborne_cli"
63
+ },
61
64
  "devDependencies": {
62
65
  "@commitlint/config-conventional": "^19.6.0",
63
66
  "@eslint/compat": "^1.2.7",