@onekeyfe/react-native-sni-connect 1.0.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.
@@ -0,0 +1,141 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTEventEmitter.h>
3
+ #import <React/RCTUtils.h>
4
+
5
+ #ifdef RCT_NEW_ARCH_ENABLED
6
+ #import <SniConnectSpec/SniConnectSpec.h>
7
+ #endif
8
+
9
+ // Forward declaration of the Swift implementation
10
+ @interface SniConnectImpl : NSObject
11
+ - (instancetype)initWithEventSender:(id)eventSender;
12
+ - (void)request:(NSDictionary *)config
13
+ resolve:(RCTPromiseResolveBlock)resolve
14
+ reject:(RCTPromiseRejectBlock)reject;
15
+ - (void)cancelRequest:(NSString *)requestId
16
+ resolve:(RCTPromiseResolveBlock)resolve
17
+ reject:(RCTPromiseRejectBlock)reject;
18
+ - (void)cancelAllRequests:(RCTPromiseResolveBlock)resolve
19
+ reject:(RCTPromiseRejectBlock)reject;
20
+ - (void)clearDNSCache:(RCTPromiseResolveBlock)resolve
21
+ reject:(RCTPromiseRejectBlock)reject;
22
+ @end
23
+
24
+ @interface SniConnect : RCTEventEmitter
25
+ #ifdef RCT_NEW_ARCH_ENABLED
26
+ <NativeSniConnectSpec>
27
+ #else
28
+ <RCTBridgeModule>
29
+ #endif
30
+ @end
31
+
32
+ @implementation SniConnect {
33
+ SniConnectImpl *_implementation;
34
+ BOOL _hasListeners;
35
+ }
36
+
37
+ RCT_EXPORT_MODULE(SniConnect)
38
+
39
+ // Expose hasListeners as a property for Swift access
40
+ - (BOOL)hasListeners {
41
+ return _hasListeners;
42
+ }
43
+
44
+ + (BOOL)requiresMainQueueSetup {
45
+ return NO;
46
+ }
47
+
48
+ - (instancetype)init {
49
+ if (self = [super init]) {
50
+ _implementation = [[SniConnectImpl alloc] initWithEventSender:self];
51
+ _hasListeners = NO;
52
+ }
53
+ return self;
54
+ }
55
+
56
+ // Event emitter methods
57
+ - (NSArray<NSString *> *)supportedEvents {
58
+ return @[@"SniConnectLog"];
59
+ }
60
+
61
+ - (void)startObserving {
62
+ _hasListeners = YES;
63
+ }
64
+
65
+ - (void)stopObserving {
66
+ _hasListeners = NO;
67
+ }
68
+
69
+ // Method to send log event to JS
70
+ - (void)sendLogEvent:(NSDictionary *)logData {
71
+ if (_hasListeners) {
72
+ [self sendEventWithName:@"SniConnectLog" body:logData];
73
+ }
74
+ }
75
+
76
+ #ifdef RCT_NEW_ARCH_ENABLED
77
+ // TurboModule interface implementation
78
+ - (void)request:(JS::NativeSniConnect::SniConnectRequest &)config
79
+ resolve:(RCTPromiseResolveBlock)resolve
80
+ reject:(RCTPromiseRejectBlock)reject {
81
+ // Convert Codegen struct to NSDictionary for Swift implementation
82
+ NSDictionary *configDict = @{
83
+ @"ip": config.ip(),
84
+ @"hostname": config.hostname(),
85
+ @"method": config.method(),
86
+ @"path": config.path(),
87
+ @"headers": config.headers(),
88
+ @"body": config.body() ?: [NSNull null],
89
+ @"timeout": @(config.timeout())
90
+ };
91
+
92
+ [_implementation request:configDict resolve:resolve reject:reject];
93
+ }
94
+
95
+ - (void)cancelRequest:(NSString *)requestId
96
+ resolve:(RCTPromiseResolveBlock)resolve
97
+ reject:(RCTPromiseRejectBlock)reject {
98
+ [_implementation cancelRequest:requestId resolve:resolve reject:reject];
99
+ }
100
+
101
+ - (void)cancelAllRequests:(RCTPromiseResolveBlock)resolve
102
+ reject:(RCTPromiseRejectBlock)reject {
103
+ [_implementation cancelAllRequests:resolve reject:reject];
104
+ }
105
+
106
+ - (void)clearDNSCache:(RCTPromiseResolveBlock)resolve
107
+ reject:(RCTPromiseRejectBlock)reject {
108
+ [_implementation clearDNSCache:resolve reject:reject];
109
+ }
110
+
111
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
112
+ (const facebook::react::ObjCTurboModule::InitParams &)params
113
+ {
114
+ return std::make_shared<facebook::react::NativeSniConnectSpecJSI>(params);
115
+ }
116
+ #else
117
+ // Bridge module interface implementation
118
+ RCT_EXPORT_METHOD(request:(NSDictionary *)config
119
+ resolver:(RCTPromiseResolveBlock)resolve
120
+ rejecter:(RCTPromiseRejectBlock)reject) {
121
+ [_implementation request:config resolve:resolve reject:reject];
122
+ }
123
+
124
+ RCT_EXPORT_METHOD(cancelRequest:(NSString *)requestId
125
+ resolver:(RCTPromiseResolveBlock)resolve
126
+ rejecter:(RCTPromiseRejectBlock)reject) {
127
+ [_implementation cancelRequest:requestId resolve:resolve reject:reject];
128
+ }
129
+
130
+ RCT_EXPORT_METHOD(cancelAllRequests:(RCTPromiseResolveBlock)resolver
131
+ rejecter:(RCTPromiseRejectBlock)rejecter) {
132
+ [_implementation cancelAllRequests:resolver reject:rejecter];
133
+ }
134
+
135
+ RCT_EXPORT_METHOD(clearDNSCache:(RCTPromiseResolveBlock)resolver
136
+ rejecter:(RCTPromiseRejectBlock)rejecter) {
137
+ [_implementation clearDNSCache:resolver reject:rejecter];
138
+ }
139
+ #endif
140
+
141
+ @end
@@ -0,0 +1,185 @@
1
+ import Foundation
2
+ import React
3
+
4
+ private enum SniConnectError: Error {
5
+ case invalidConfig(String)
6
+ }
7
+
8
+ @objc(SniConnectImpl)
9
+ final class SniConnectImpl: NSObject {
10
+ private let client: SniConnectClient
11
+ private weak var eventSender: AnyObject?
12
+
13
+ @objc
14
+ init(eventSender: AnyObject) {
15
+ self.eventSender = eventSender
16
+ self.client = SniConnectClient()
17
+ super.init()
18
+
19
+ // Set up logging closure for the client
20
+ self.client.onLog = { [weak self] level, message in
21
+ self?.sendLogEvent(level: level, message: message)
22
+ }
23
+ }
24
+
25
+ private func sendLogEvent(level: String, message: String) {
26
+ // Send log event to the Objective-C bridge
27
+ guard let eventSender = eventSender else {
28
+ return
29
+ }
30
+
31
+ let logData: [String: Any] = [
32
+ "level": level,
33
+ "message": message,
34
+ "timestamp": Int(Date().timeIntervalSince1970 * 1000)
35
+ ]
36
+
37
+ // Call the sendLogEvent method on the bridge
38
+ let selector = NSSelectorFromString("sendLogEvent:")
39
+ if eventSender.responds(to: selector) {
40
+ eventSender.perform(selector, with: logData)
41
+ }
42
+ }
43
+
44
+ @objc
45
+ public func request(
46
+ _ config: NSDictionary,
47
+ resolve resolve: @escaping RCTPromiseResolveBlock,
48
+ reject reject: @escaping RCTPromiseRejectBlock
49
+ ) {
50
+ do {
51
+ let parsed = try Self.parseDictionary(config)
52
+ handleRequest(config: parsed, resolve: resolve, reject: reject)
53
+ } catch {
54
+ NSLog("[SniConnect] ❌ Config parsing failed: \(error)")
55
+ sendLogEvent(level: "error", message: "Config parsing failed: \(error)")
56
+ reject("SNI_REQUEST_FAILED", error.localizedDescription, error)
57
+ }
58
+ }
59
+
60
+ private func handleRequest(
61
+ config: SniConnectClient.RequestConfig,
62
+ resolve: @escaping RCTPromiseResolveBlock,
63
+ reject: @escaping RCTPromiseRejectBlock
64
+ ) {
65
+ // Create task and register it synchronously before it starts executing
66
+ let task = Task { () -> SniConnectClient.Response in
67
+ return try await client.performRequest(config: config)
68
+ }
69
+
70
+ // Register task synchronously if requestId is provided
71
+ if let requestId = config.requestId {
72
+ client.registerTask(task, for: requestId)
73
+ }
74
+
75
+ // Handle the task result asynchronously
76
+ Task {
77
+ do {
78
+ let result = try await task.value
79
+ let responseBody = Self.serializeResponseData(result.data)
80
+
81
+ resolve([
82
+ "data": responseBody,
83
+ "status": result.status,
84
+ "statusText": result.statusText,
85
+ "headers": result.headers,
86
+ "multiValueHeaders": result.multiValueHeaders,
87
+ ])
88
+ } catch let error as SniConnectClient.SniConnectError {
89
+ NSLog("[SniConnect] ❌ [\(error.code)] \(error.message)")
90
+ sendLogEvent(level: "error", message: "[\(error.code)] \(error.message)")
91
+ reject(error.code, error.message, error)
92
+ } catch is CancellationError {
93
+ NSLog("[SniConnect] ❌ Request cancelled")
94
+ sendLogEvent(level: "info", message: "Request cancelled")
95
+ reject("SNI_CANCELLED", "Request cancelled", nil)
96
+ } catch {
97
+ NSLog("[SniConnect] ❌ Request failed: \(error.localizedDescription)")
98
+ sendLogEvent(level: "error", message: "Request failed: \(error.localizedDescription)")
99
+ reject("SNI_UNKNOWN_ERROR", error.localizedDescription, error)
100
+ }
101
+ }
102
+ }
103
+
104
+ @objc
105
+ public func cancelRequest(
106
+ _ requestId: String,
107
+ resolve resolve: @escaping RCTPromiseResolveBlock,
108
+ reject reject: @escaping RCTPromiseRejectBlock
109
+ ) {
110
+ client.cancelRequest(requestId: requestId)
111
+ resolve(["success": true])
112
+ }
113
+
114
+ @objc
115
+ public func cancelAllRequests(
116
+ _ resolve: @escaping RCTPromiseResolveBlock,
117
+ reject reject: @escaping RCTPromiseRejectBlock
118
+ ) {
119
+ client.cancelAllRequests()
120
+ resolve(["success": true])
121
+ }
122
+
123
+ @objc
124
+ public func clearDNSCache(
125
+ _ resolve: @escaping RCTPromiseResolveBlock,
126
+ reject reject: @escaping RCTPromiseRejectBlock
127
+ ) {
128
+ client.clearDNSCache()
129
+ resolve(["success": true])
130
+ }
131
+
132
+ private static func parseDictionary(_ dictionary: NSDictionary) throws -> SniConnectClient.RequestConfig {
133
+ guard let ip = dictionary["ip"] as? String, !ip.isEmpty else {
134
+ throw SniConnectError.invalidConfig("Missing ip")
135
+ }
136
+ guard let hostname = dictionary["hostname"] as? String, !hostname.isEmpty else {
137
+ throw SniConnectError.invalidConfig("Missing hostname")
138
+ }
139
+
140
+ let requestId = dictionary["requestId"] as? String
141
+ let method = (dictionary["method"] as? String)?.uppercased() ?? "GET"
142
+ let path = dictionary["path"] as? String ?? "/"
143
+ let headers = dictionary["headers"] as? [String: String] ?? [:]
144
+ let body = dictionary["body"] as? String
145
+ let timeout = dictionary["timeout"] as? NSNumber ?? 30_000
146
+
147
+ // Parse advanced timeout configurations
148
+ let connectTimeout = (dictionary["connectTimeout"] as? NSNumber)?.doubleValue
149
+ let totalTimeout = (dictionary["totalTimeout"] as? NSNumber)?.doubleValue
150
+
151
+ return SniConnectClient.RequestConfig(
152
+ requestId: requestId,
153
+ ip: ip,
154
+ hostname: hostname,
155
+ method: method,
156
+ path: path,
157
+ headers: headers,
158
+ body: body,
159
+ timeout: timeout.doubleValue,
160
+ connectTimeout: connectTimeout,
161
+ totalTimeout: totalTimeout
162
+ )
163
+ }
164
+
165
+ private static func serializeResponseData(_ data: Any) -> String {
166
+ if let dict = data as? [String: Any],
167
+ let jsonData = try? JSONSerialization.data(withJSONObject: dict, options: []),
168
+ let jsonString = String(data: jsonData, encoding: .utf8) {
169
+ return jsonString
170
+ }
171
+
172
+ if let array = data as? [[String: Any]],
173
+ let jsonData = try? JSONSerialization.data(withJSONObject: array, options: []),
174
+ let jsonString = String(data: jsonData, encoding: .utf8) {
175
+ return jsonString
176
+ }
177
+
178
+ if let stringValue = data as? String {
179
+ return stringValue
180
+ }
181
+
182
+ return String(describing: data)
183
+ }
184
+ }
185
+