@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.
- package/LICENSE +20 -0
- package/README.md +37 -0
- package/SniConnect.podspec +36 -0
- package/android/build.gradle +89 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/sniconnect/SniConnectModule.kt +346 -0
- package/android/src/main/java/com/sniconnect/SniConnectPackage.kt +31 -0
- package/ios/SniConnect-Bridging-Header.h +13 -0
- package/ios/SniConnect.mm +141 -0
- package/ios/SniConnect.swift +185 -0
- package/ios/SniConnectClient.swift +490 -0
- package/lib/module/@types/react-native-codegen.d.js +2 -0
- package/lib/module/@types/react-native-codegen.d.js.map +1 -0
- package/lib/module/NativeSniConnect.js +17 -0
- package/lib/module/NativeSniConnect.js.map +1 -0
- package/lib/module/index.js +31 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeSniConnect.d.ts +50 -0
- package/lib/typescript/src/NativeSniConnect.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +19 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +167 -0
- package/src/@types/react-native-codegen.d.ts +8 -0
- package/src/NativeSniConnect.ts +70 -0
- package/src/index.tsx +46 -0
|
@@ -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
|
+
|