react-native-mytatva-rn-sdk 1.2.97 → 1.2.99
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/android/src/main/java/com/mytatvarnsdk/CgmTrackyLibModule.kt +25 -0
- package/android/src/main/java/com/mytatvarnsdk/activity/ConnectSensorActivity.kt +164 -18
- package/android/src/main/java/com/mytatvarnsdk/activity/HelpActivity.kt +3 -9
- package/android/src/main/java/com/mytatvarnsdk/activity/PermissionActivity.kt +8 -16
- package/android/src/main/java/com/mytatvarnsdk/activity/PlaceSensorActivity.kt +12 -13
- package/android/src/main/java/com/mytatvarnsdk/activity/PlaceTransmitterActivity.kt +12 -13
- package/android/src/main/java/com/mytatvarnsdk/activity/SearchTransmitterActivity.kt +9 -14
- package/android/src/main/java/com/mytatvarnsdk/activity/SensorConnectSuccessActivity.kt +12 -16
- package/android/src/main/java/com/mytatvarnsdk/activity/StartCGMActivity.kt +9 -15
- package/android/src/main/java/com/mytatvarnsdk/activity/VideoActivity.kt +96 -50
- package/android/src/main/java/com/mytatvarnsdk/network/AuthenticateSDKService.kt +50 -0
- package/android/src/main/java/com/mytatvarnsdk/utils/CustomerSupportConfig.kt +137 -0
- package/android/src/main/java/com/mytatvarnsdk/utils/ToolbarSupportHelper.kt +83 -0
- package/android/src/main/res/layout/activity_connect_sensor.xml +117 -2
- package/ios/Custom/CustomTopUIView.swift +25 -0
- package/ios/Database/KLTBluetoothManager.m +69 -14
- package/ios/MyReactNativeBridge.m +4 -0
- package/ios/Support/API.swift +117 -18
- package/ios/Support/CustomTopViewSupportHelper.swift +55 -0
- package/ios/Support/CustomerSupportConfig.swift +169 -0
- package/ios/Support/Global.swift +2 -2
- package/ios/Support/GlobalYouTubePlayerView.swift +102 -11
- package/ios/TableViewCell/ImageTVC/ImageTVC.swift +147 -15
- package/ios/ViewControllers/AttachTransmitterViewController.swift +16 -21
- package/ios/ViewControllers/ChatWithExpertViewController.swift +1 -7
- package/ios/ViewControllers/ConnectToSensorViewController.swift +213 -34
- package/ios/ViewControllers/ConnectToTransmitterViewController.swift +16 -21
- package/ios/ViewControllers/ProvidePermissionViewController.swift +16 -21
- package/ios/ViewControllers/PutOnTheSensorViewController.swift +16 -21
- package/ios/ViewControllers/StartConnectionViewController.swift +18 -25
- package/ios/ViewModel/FinalViewModel.swift +2 -2
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
//
|
|
2
2
|
// KLTBluetoothManager.m
|
|
3
3
|
// KaiLiTe
|
|
4
4
|
//
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
// 蓝牙管理状态
|
|
29
29
|
@property (nonatomic, assign) CBManagerState centralManagerState;
|
|
30
30
|
|
|
31
|
+
- (BOOL)klt_safeWriteData:(NSData *)data withType:(CBCharacteristicWriteType)type;
|
|
32
|
+
|
|
31
33
|
@end
|
|
32
34
|
|
|
33
35
|
@implementation KLTBluetoothManager
|
|
@@ -71,6 +73,19 @@
|
|
|
71
73
|
return self;
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
/// NSTimer must not be invalidated synchronously from its own fired callback — that
|
|
77
|
+
/// can block the main thread for seconds (Sentry AppHang in -[KLTBluetoothManager endScan]).
|
|
78
|
+
- (void)invalidateConnectTimerSafely {
|
|
79
|
+
NSTimer *timer = _connectTimer;
|
|
80
|
+
_connectTimer = nil;
|
|
81
|
+
if (!timer) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
85
|
+
[timer invalidate];
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
74
89
|
+ (KLTBluetoothManager *)sharedManager {
|
|
75
90
|
static KLTBluetoothManager *bluetooth;
|
|
76
91
|
static dispatch_once_t onceToken;
|
|
@@ -81,6 +96,21 @@
|
|
|
81
96
|
return bluetooth;
|
|
82
97
|
}
|
|
83
98
|
|
|
99
|
+
- (BOOL)klt_safeWriteData:(NSData *)data withType:(CBCharacteristicWriteType)type {
|
|
100
|
+
if (!data || data.length == 0) {
|
|
101
|
+
return NO;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
CBPeripheral *peripheral = self.connectedPeripheral;
|
|
105
|
+
CBCharacteristic *characteristic = self.writeReadCharacteristic;
|
|
106
|
+
if (!peripheral || peripheral.state != CBPeripheralStateConnected || !characteristic) {
|
|
107
|
+
KLTLog(@"BLE write skipped - peripheral or write characteristic unavailable");
|
|
108
|
+
return NO;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
[peripheral writeValue:data forCharacteristic:characteristic type:type];
|
|
112
|
+
return YES;
|
|
113
|
+
}
|
|
84
114
|
|
|
85
115
|
#pragma mark - cbcenteralmanager delegate
|
|
86
116
|
|
|
@@ -476,7 +506,8 @@
|
|
|
476
506
|
Device *currentDevice = self.currentDevice;
|
|
477
507
|
if (KLTLocalSettingManager.shareInstance.canConnectOtherDevice) {
|
|
478
508
|
// 直接开始自检协议
|
|
479
|
-
[self
|
|
509
|
+
[self klt_safeWriteData:[ProtocalTools check_request:self.eDevice]
|
|
510
|
+
withType:CBCharacteristicWriteWithoutResponse];
|
|
480
511
|
} else {
|
|
481
512
|
// 二次重连 0x22快传批量数据
|
|
482
513
|
NSInteger maxGlucoseId = [[KLTDatabaseHandler shared] getLatestAndMaxGlucoseIdOfDevice:currentDevice];
|
|
@@ -515,7 +546,9 @@
|
|
|
515
546
|
}
|
|
516
547
|
|
|
517
548
|
NSData *data = [ProtocalTools pullGlucose_series_request:glucoseId + 1 andNumber:pullNums andEDevice:self.eDevice];
|
|
518
|
-
[self
|
|
549
|
+
if (![self klt_safeWriteData:data withType:CBCharacteristicWriteWithoutResponse]) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
519
552
|
self.status = BluetoothManagerStatusResendStart;
|
|
520
553
|
self.isInResend = YES;
|
|
521
554
|
}
|
|
@@ -532,7 +565,8 @@
|
|
|
532
565
|
[Notification_Center postNotificationName:@"DidHandleResendData" object:nil];
|
|
533
566
|
|
|
534
567
|
// CT3/CT4发送重置协议这条命令0x11,结束重传状态
|
|
535
|
-
[self
|
|
568
|
+
[self klt_safeWriteData:[ProtocalTools reset_request:self.eDevice]
|
|
569
|
+
withType:CBCharacteristicWriteWithoutResponse];
|
|
536
570
|
} else {
|
|
537
571
|
for (Glucose *glucose in glucoseList) {
|
|
538
572
|
ReceiveData *receive = [self handleCommonReceiveGluData:glucose commandText:@"22"];
|
|
@@ -553,7 +587,7 @@
|
|
|
553
587
|
pullNums = remain_points;
|
|
554
588
|
}
|
|
555
589
|
NSData *data = [ProtocalTools pullGlucose_series_request:maxGlucoseId + 1 andNumber:pullNums andEDevice:self.eDevice];
|
|
556
|
-
[self
|
|
590
|
+
[self klt_safeWriteData:data withType:CBCharacteristicWriteWithoutResponse];
|
|
557
591
|
}
|
|
558
592
|
}
|
|
559
593
|
}
|
|
@@ -595,8 +629,7 @@
|
|
|
595
629
|
[self.peripherals removeAllObjects];
|
|
596
630
|
self.connectedPeripheral = nil;
|
|
597
631
|
|
|
598
|
-
[
|
|
599
|
-
_connectTimer = nil;
|
|
632
|
+
[self invalidateConnectTimerSafely];
|
|
600
633
|
[_rescanTimer invalidate];
|
|
601
634
|
_rescanTimer = nil;
|
|
602
635
|
|
|
@@ -610,6 +643,14 @@
|
|
|
610
643
|
#pragma mark - other method
|
|
611
644
|
|
|
612
645
|
- (void)endScan {
|
|
646
|
+
// endScan is the _connectTimer target. Defer to the next run-loop turn so we never
|
|
647
|
+
// invalidate the firing timer or run stopScan/KVO inside the timer callback.
|
|
648
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
649
|
+
[self performEndScanAfterTimer];
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
- (void)performEndScanAfterTimer {
|
|
613
654
|
// 20s后扫描设备超时未连上再次尝试连接
|
|
614
655
|
if (self.currentDevice && BluetoothManagerStatusScanStart == self.status) {
|
|
615
656
|
for (CBPeripheral *per in self.peripherals) {
|
|
@@ -620,7 +661,7 @@
|
|
|
620
661
|
}
|
|
621
662
|
}
|
|
622
663
|
}
|
|
623
|
-
|
|
664
|
+
|
|
624
665
|
[self stopScan];
|
|
625
666
|
self.status = BluetoothManagerStatusTimeOut;
|
|
626
667
|
}
|
|
@@ -630,8 +671,7 @@
|
|
|
630
671
|
self.status = BluetoothManagerStatusScanStop;
|
|
631
672
|
[self.centralManager stopScan];
|
|
632
673
|
|
|
633
|
-
[
|
|
634
|
-
_connectTimer = nil;
|
|
674
|
+
[self invalidateConnectTimerSafely];
|
|
635
675
|
}
|
|
636
676
|
|
|
637
677
|
// 开始扫描
|
|
@@ -678,23 +718,38 @@
|
|
|
678
718
|
//}
|
|
679
719
|
|
|
680
720
|
- (void)startScan {
|
|
721
|
+
// Defer to the next main-queue run-loop turn so scanForPeripheralsWithServices is
|
|
722
|
+
// never invoked synchronously from an NSTimer callback (Sentry AppHang on iOS).
|
|
723
|
+
// CBCentralManager is bound to the main queue — this does not move BLE off main.
|
|
724
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
725
|
+
[self performStartScan];
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
- (void)performStartScan {
|
|
730
|
+
if (self.status == BluetoothManagerStatusScanStart) {
|
|
731
|
+
KLTLog(@"startScan skipped — scan already in progress");
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
|
|
681
735
|
self.connectedPeripheral = nil;
|
|
682
736
|
[self.peripherals removeAllObjects];
|
|
683
|
-
|
|
737
|
+
|
|
684
738
|
if (self.centralManager.state != CBManagerStatePoweredOn) {
|
|
685
739
|
NSLog(@"Bluetooth is not powered on. Aborting scan.");
|
|
686
740
|
// Do not change status here – just wait for BluetoothEnable notification
|
|
687
741
|
// The UI layer listens for BluetoothEnable and will start scan once BLE is ready.
|
|
688
742
|
return;
|
|
689
743
|
}
|
|
690
|
-
|
|
744
|
+
|
|
691
745
|
NSLog(@"Allen manager start scan %d", __LINE__);
|
|
692
746
|
self.status = BluetoothManagerStatusScanStart;
|
|
693
747
|
self.isBgForceConnectDevice = NO;
|
|
694
|
-
|
|
748
|
+
|
|
695
749
|
NSDictionary *scanOptions = @{ CBCentralManagerScanOptionAllowDuplicatesKey: @(YES) };
|
|
696
750
|
[self.centralManager scanForPeripheralsWithServices:nil options:scanOptions];
|
|
697
|
-
|
|
751
|
+
|
|
752
|
+
[self invalidateConnectTimerSafely];
|
|
698
753
|
_connectTimer = [NSTimer scheduledTimerWithTimeInterval:20.0
|
|
699
754
|
target:self
|
|
700
755
|
selector:@selector(endScan)
|
|
@@ -471,6 +471,8 @@ RCT_EXPORT_METHOD(startCgmTracky:(NSString *)token envType:(NSString *)envType
|
|
|
471
471
|
|
|
472
472
|
[defaults synchronize];
|
|
473
473
|
|
|
474
|
+
[[CustomerSupportConfig shared] fetchCustomerSupportWithToken:token];
|
|
475
|
+
|
|
474
476
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
475
477
|
UIWindow *keyWindow = [UIApplication sharedApplication].delegate.window;
|
|
476
478
|
UIViewController *rootVC = keyWindow.rootViewController;
|
|
@@ -513,6 +515,8 @@ RCT_EXPORT_METHOD(reconnectCgmTracky:(NSString *)token envType:(NSString *)envT
|
|
|
513
515
|
|
|
514
516
|
[defaults synchronize];
|
|
515
517
|
|
|
518
|
+
[[CustomerSupportConfig shared] fetchCustomerSupportWithToken:token];
|
|
519
|
+
|
|
516
520
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
517
521
|
UIWindow *keyWindow = [UIApplication sharedApplication].delegate.window;
|
|
518
522
|
UIViewController *rootVC = keyWindow.rootViewController;
|
package/ios/Support/API.swift
CHANGED
|
@@ -23,8 +23,22 @@ let STAGE_ENC_IV = "9Ddyaf6rfywpiTvT"
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
let defaultToken = "ILtFcw+xtbuy8IgsBCSyD6nSpgZd5AOz7T+g3N8Tef/INZi+dxwPJhnBc2kfdq2e8Kw2yayjaKjuji64coUJsK7e4QcF7sqXCp3Cy5S2OVE+hONwCipkrn9d1yjsBWIDqfLK9ModJCWdigDj5ZeSwkWYSSeTEVoN3Dc2Y8cuqKUQvN9ZcNGmdRrXz2oo3rm4EHQrXPo1Ijufm0HfcqzSH/Gh0TC5gFKjde/GEXIm5Z2Ju1/TTXVSJg/zxNuOF4iUKNEh4UIFUYxYpnqlALRCdF75J+9WAZ7/LBGyB1Wrx+D7bsrzRiQ5sCTwxE3TCM3s66lYJizX22VIzYKOIrJ6GTOYGTINHeIJKEPEvFceR+cG54ez15V1r77ErQElevItD3xIGe0uDezfnixJYPxPhSEOPOyIpxUFInXBRdGjllHPIevzBqqHeP76nz+1U7mFNs1kofugN+5huT3/tCifTQqFyWuxD+VIhl0UJxhxPMbBgoryANgKCToWrK2FqdJwBtZKwCj3S+yp0rff0fwrbMXXsYytJyVLLY2oNviHtzDa/kih0AnMA3vEbqYkf1PQ6LZv+AmtpNYHKh7kMDrhVE3wVxH4+ga0+sfuxYgfiZXCoQW7yY0wOZTiFbgvL2TQflDZR6ttK5abtyKYV4gl5KqbFLZW+d4pKLQoZb02NbJc+5h5gAH2VclTJc6fbkd0qIWuXyqGIBEc2uYrNl2v3vmVvbcGevSqb900ozo6SbFccQLjLcEon+okmgBIDPvBEZFj8fZgQV4z8/yzayDcfqZQPc9Wrw8JNrFLCd8emvo7QHFfGttnIsAtmR8Yp3x49sjEn3hFW6nEKl9FRtRT1r3LzHmIdokL5tz3nO/T3FzLn5sqfp5QBJKKK2dgJdmAz0KC6vKtJ0PtVR4shL7pqjoZuJimFObYKyQbQudW7gLH8WjqORNG2dsplOLzDQ0y7nQdQ0tWrLJtnAch0p5PyTXtunlkNWeuzrldkqVMTUPaAtGcEnmS4RFncD3C6sq9V6lpBKLdrh99vnH3EX81DTH2Hrqm0bN5Tl3UqzUJ7n/JQCR3phDmOo+Uk81y3z3ikiGZZpZikN7c3DtSP34Vc+cNUosWrChqj1gLL4Eg/jMDxE4+bdAGr03ALxEAj2uLGEsDaZRuio6e8/iMcA0z3oqhtAxQ0nU1ivAK7cHeddjRFtvrNb0l+TgrcQI9UVffMOoGDEfAQNUpT2+R5dIysw=="
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
|
|
27
|
+
func currentAuthToken() -> String {
|
|
28
|
+
UserDefaults.standard.string(forKey: "authToken") ?? defaultToken
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
func currentEnvType() -> String {
|
|
32
|
+
UserDefaults.standard.string(forKey: "envType") ?? "uat"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func normalizedCipherText(_ response: String) -> String {
|
|
36
|
+
var text = response.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
37
|
+
if text.hasPrefix("\""), text.hasSuffix("\""), text.count >= 2 {
|
|
38
|
+
text = String(text.dropFirst().dropLast())
|
|
39
|
+
}
|
|
40
|
+
return text
|
|
41
|
+
}
|
|
28
42
|
|
|
29
43
|
/*TatvaEncryptionConfig {
|
|
30
44
|
const val STAGE_BASE_URL = "https://api-feature2.mytatva.in/api/v8"
|
|
@@ -99,7 +113,7 @@ class API {
|
|
|
99
113
|
func postCGMData(
|
|
100
114
|
data: Payload,
|
|
101
115
|
environment: TatvaEnvironment,
|
|
102
|
-
token: String =
|
|
116
|
+
token: String = currentAuthToken(),
|
|
103
117
|
encryptionKey: String = PROD_ENC_KEY,
|
|
104
118
|
encryptionIv: String = PROD_ENC_IV,
|
|
105
119
|
onSuccess: @escaping () -> Void,
|
|
@@ -137,7 +151,8 @@ class API {
|
|
|
137
151
|
print("===>Decrypted (for verification): \(decrypted)")
|
|
138
152
|
}
|
|
139
153
|
|
|
140
|
-
let
|
|
154
|
+
let env = currentEnvType()
|
|
155
|
+
let urlString = (env.lowercased() == "uat" ? STAGE_BASE_URL : PROD_BASE_URL) + "/cgm/logs"
|
|
141
156
|
// let urlString = (environment == .stage ? STAGE_BASE_URL : PROD_BASE_URL) + "/cgm/logs"
|
|
142
157
|
guard let url = URL(string: urlString) else {
|
|
143
158
|
throw URLError(.badURL)
|
|
@@ -146,7 +161,7 @@ class API {
|
|
|
146
161
|
var request = URLRequest(url: url)
|
|
147
162
|
request.httpMethod = "POST"
|
|
148
163
|
request.timeoutInterval = 30
|
|
149
|
-
request.setValue(
|
|
164
|
+
request.setValue(env.lowercased() == "uat" ? STAGE_API_KEY : PROD_API_KEY, forHTTPHeaderField: "api-key")
|
|
150
165
|
// request.setValue(environment == .stage ? STAGE_API_KEY : PROD_API_KEY, forHTTPHeaderField: "api-key")
|
|
151
166
|
request.setValue(token, forHTTPHeaderField: "token")
|
|
152
167
|
request.setValue("text/plain", forHTTPHeaderField: "Content-Type")
|
|
@@ -172,7 +187,8 @@ class API {
|
|
|
172
187
|
|
|
173
188
|
if let data = data, let responseStr = String(data: data, encoding: .utf8) {
|
|
174
189
|
print("===>Server Response: \(responseStr)")
|
|
175
|
-
|
|
190
|
+
let cipherText = normalizedCipherText(responseStr)
|
|
191
|
+
if let decrypted = Crypto.shared.getDecryptedData(cipherText: cipherText, encryptionKey: encryptionKey, encryptionIv: encryptionIv) {
|
|
176
192
|
print("===>Decrypted post cgm data response (for verification): \(decrypted)")
|
|
177
193
|
}
|
|
178
194
|
}
|
|
@@ -203,9 +219,9 @@ class API {
|
|
|
203
219
|
encryptionKey: String = PROD_ENC_KEY,
|
|
204
220
|
encryptionIv: String = PROD_ENC_IV) {
|
|
205
221
|
if sensorId.isEmpty { return }
|
|
206
|
-
|
|
207
|
-
print("envType: \(
|
|
208
|
-
let urlString = (
|
|
222
|
+
let env = currentEnvType()
|
|
223
|
+
print("envType: \(env.lowercased())")
|
|
224
|
+
let urlString = (env.lowercased() == "uat" ? STAGE_BASE_URL : PROD_BASE_URL) + "/cgm/connection"
|
|
209
225
|
//let urlString = (environment == .stage ? STAGE_BASE_URL : PROD_BASE_URL) + "/cgm/connection"
|
|
210
226
|
let url = URL(string: urlString)!
|
|
211
227
|
var request = URLRequest(url: url)
|
|
@@ -214,13 +230,14 @@ class API {
|
|
|
214
230
|
|
|
215
231
|
// Set headers
|
|
216
232
|
request.addValue("text/plain", forHTTPHeaderField: "Content-Type")
|
|
217
|
-
request.setValue(
|
|
233
|
+
request.setValue(env.lowercased() == "uat" ? STAGE_API_KEY : PROD_API_KEY, forHTTPHeaderField: "api-key")
|
|
218
234
|
//request.setValue(environment == .stage ? STAGE_API_KEY : PROD_API_KEY, forHTTPHeaderField: "api-key")
|
|
219
|
-
|
|
235
|
+
let token = currentAuthToken()
|
|
236
|
+
request.addValue(token, forHTTPHeaderField: "token")
|
|
220
237
|
print("===>url:", urlString)
|
|
221
238
|
print("===>api-key:", PROD_API_KEY)
|
|
222
239
|
print("===>httpmMethod:","POST")
|
|
223
|
-
print("===>ios token:",
|
|
240
|
+
print("===>ios token:", token)
|
|
224
241
|
// Set request body
|
|
225
242
|
let json: [String: Any] = [
|
|
226
243
|
"sensorId": sensorId,
|
|
@@ -259,7 +276,8 @@ class API {
|
|
|
259
276
|
}
|
|
260
277
|
if let responseString = String(data: data, encoding: .utf8) {
|
|
261
278
|
print("===>Server Response: \(responseString)")
|
|
262
|
-
|
|
279
|
+
let cipherText = normalizedCipherText(responseString)
|
|
280
|
+
if let decrypted = Crypto.shared.getDecryptedData(cipherText: cipherText, encryptionKey: encryptionKey, encryptionIv: encryptionIv) {
|
|
263
281
|
print("===>Decrypted connection api response (for verification): \(decrypted)")
|
|
264
282
|
}
|
|
265
283
|
}
|
|
@@ -330,6 +348,84 @@ class API {
|
|
|
330
348
|
|
|
331
349
|
}*/
|
|
332
350
|
|
|
351
|
+
func fetchCustomerSupportDetails(
|
|
352
|
+
token: String = currentAuthToken(),
|
|
353
|
+
encryptionKey: String = PROD_ENC_KEY,
|
|
354
|
+
encryptionIv: String = PROD_ENC_IV,
|
|
355
|
+
onSuccess: @escaping (String) -> Void,
|
|
356
|
+
onFailure: @escaping () -> Void
|
|
357
|
+
) {
|
|
358
|
+
guard !token.isEmpty else {
|
|
359
|
+
print("===>fetchCustomerSupportDetails: empty token")
|
|
360
|
+
onFailure()
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
let env = currentEnvType()
|
|
365
|
+
let urlString = (env.lowercased() == "uat" ? STAGE_BASE_URL : PROD_BASE_URL) + "/helper/customer_support_details"
|
|
366
|
+
|
|
367
|
+
guard let url = URL(string: urlString) else {
|
|
368
|
+
print("===>fetchCustomerSupportDetails: Invalid URL")
|
|
369
|
+
onFailure()
|
|
370
|
+
return
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
var request = URLRequest(url: url)
|
|
374
|
+
request.httpMethod = "GET"
|
|
375
|
+
request.setValue(env.lowercased() == "uat" ? STAGE_API_KEY : PROD_API_KEY, forHTTPHeaderField: "api-key")
|
|
376
|
+
request.setValue(token, forHTTPHeaderField: "token")
|
|
377
|
+
|
|
378
|
+
print("===>fetchCustomerSupportDetails URL:", urlString)
|
|
379
|
+
print("===>fetchCustomerSupportDetails env:", env)
|
|
380
|
+
print("===>fetchCustomerSupportDetails usingDefaultToken:", token == defaultToken)
|
|
381
|
+
|
|
382
|
+
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
|
383
|
+
if let error = error {
|
|
384
|
+
print("===>fetchCustomerSupportDetails Error: \(error.localizedDescription)")
|
|
385
|
+
DispatchQueue.main.async { onFailure() }
|
|
386
|
+
return
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
let statusCode = (response as? HTTPURLResponse)?.statusCode ?? 0
|
|
390
|
+
if statusCode > 0 {
|
|
391
|
+
print("===>fetchCustomerSupportDetails HTTP Status: \(statusCode)")
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
guard let data = data, let responseString = String(data: data, encoding: .utf8) else {
|
|
395
|
+
print("===>fetchCustomerSupportDetails: No data received")
|
|
396
|
+
DispatchQueue.main.async { onFailure() }
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
print("===>fetchCustomerSupportDetails Server Response: \(responseString)")
|
|
401
|
+
|
|
402
|
+
let cipherText = normalizedCipherText(responseString)
|
|
403
|
+
if let decrypted = Crypto.shared.getDecryptedData(
|
|
404
|
+
cipherText: cipherText,
|
|
405
|
+
encryptionKey: encryptionKey,
|
|
406
|
+
encryptionIv: encryptionIv
|
|
407
|
+
) {
|
|
408
|
+
print("===>fetchCustomerSupportDetails Decrypted: \(decrypted)")
|
|
409
|
+
if statusCode >= 400 {
|
|
410
|
+
print("===>fetchCustomerSupportDetails: API error \(statusCode)")
|
|
411
|
+
DispatchQueue.main.async { onFailure() }
|
|
412
|
+
return
|
|
413
|
+
}
|
|
414
|
+
DispatchQueue.main.async { onSuccess(decrypted) }
|
|
415
|
+
return
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if responseString.contains("\"code\"") {
|
|
419
|
+
DispatchQueue.main.async { onSuccess(responseString) }
|
|
420
|
+
return
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
print("===>fetchCustomerSupportDetails: Decryption failed")
|
|
424
|
+
DispatchQueue.main.async { onFailure() }
|
|
425
|
+
}
|
|
426
|
+
task.resume()
|
|
427
|
+
}
|
|
428
|
+
|
|
333
429
|
func verifyDevice(
|
|
334
430
|
sensorId: String,
|
|
335
431
|
encryptionKey: String = PROD_ENC_KEY,
|
|
@@ -337,7 +433,9 @@ class API {
|
|
|
337
433
|
onSuccess: @escaping (Int, Bool, String) -> Void,
|
|
338
434
|
onFailure: @escaping () -> Void
|
|
339
435
|
) {
|
|
340
|
-
let
|
|
436
|
+
let env = currentEnvType()
|
|
437
|
+
let token = currentAuthToken()
|
|
438
|
+
let urlString = (env.lowercased() == "uat" ? STAGE_BASE_URL : PROD_BASE_URL) + "/helper/device_verification?deviceId=\(sensorId)&deviceType=CGM&vendor=diasens"
|
|
341
439
|
|
|
342
440
|
guard let url = URL(string: urlString) else {
|
|
343
441
|
print("===>verifyDevice: Invalid URL")
|
|
@@ -347,11 +445,11 @@ class API {
|
|
|
347
445
|
|
|
348
446
|
var request = URLRequest(url: url)
|
|
349
447
|
request.httpMethod = "GET"
|
|
350
|
-
request.setValue(
|
|
351
|
-
request.setValue(
|
|
448
|
+
request.setValue(env.lowercased() == "uat" ? STAGE_API_KEY : PROD_API_KEY, forHTTPHeaderField: "api-key")
|
|
449
|
+
request.setValue(token, forHTTPHeaderField: "token")
|
|
352
450
|
|
|
353
451
|
print("===>verifyDevice URL:", urlString)
|
|
354
|
-
print("===>verifyDevice token:",
|
|
452
|
+
print("===>verifyDevice token:", token)
|
|
355
453
|
|
|
356
454
|
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
|
357
455
|
if let error = error {
|
|
@@ -368,7 +466,8 @@ class API {
|
|
|
368
466
|
|
|
369
467
|
print("===>verifyDevice Server Response: \(responseString)")
|
|
370
468
|
|
|
371
|
-
|
|
469
|
+
let cipherText = normalizedCipherText(responseString)
|
|
470
|
+
guard let decrypted = Crypto.shared.getDecryptedData(cipherText: cipherText, encryptionKey: encryptionKey, encryptionIv: encryptionIv) else {
|
|
372
471
|
print("===>verifyDevice: Decryption failed")
|
|
373
472
|
DispatchQueue.main.async { onFailure() }
|
|
374
473
|
return
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
//
|
|
2
|
+
// CustomTopViewSupportHelper.swift
|
|
3
|
+
// MyTatvaCare
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
import UIKit
|
|
7
|
+
|
|
8
|
+
enum CustomTopViewSupportHelper {
|
|
9
|
+
static func configureWhatsapp(customTopView: CustomTopUIView) {
|
|
10
|
+
customTopView.refreshWhatsappVisibility()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static func bindDemoAction(
|
|
14
|
+
customTopView: CustomTopUIView,
|
|
15
|
+
isForReconnect: Bool,
|
|
16
|
+
presenter: UIViewController,
|
|
17
|
+
analyticsHandler: (() -> Void)? = nil
|
|
18
|
+
) {
|
|
19
|
+
customTopView.onDemoTapped = {
|
|
20
|
+
analyticsHandler?()
|
|
21
|
+
|
|
22
|
+
let payload = ["status": "cgm_watch_demo_clicked"]
|
|
23
|
+
NotificationCenter.default.post(
|
|
24
|
+
name: Notification.Name("cgmwatchdemoclicked"),
|
|
25
|
+
object: nil,
|
|
26
|
+
userInfo: payload
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
let webVC = WebViewController()
|
|
30
|
+
webVC.modalPresentationStyle = .formSheet
|
|
31
|
+
webVC.videoURL = URL(string: CustomerSupportConfig.shared.demoVideoURL(isForReconnect: isForReconnect))
|
|
32
|
+
presenter.present(webVC, animated: true, completion: nil)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static func bindWhatsappAction(
|
|
37
|
+
customTopView: CustomTopUIView,
|
|
38
|
+
navigationController: UINavigationController?
|
|
39
|
+
) {
|
|
40
|
+
customTopView.onWhatsAppTapped = {
|
|
41
|
+
let payload = [
|
|
42
|
+
"screen_name": "connection journey page",
|
|
43
|
+
"status": "cgm_WA_support_clicked"
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
NotificationCenter.default.post(
|
|
47
|
+
name: Notification.Name("cgmWAsupportclicked"),
|
|
48
|
+
object: nil,
|
|
49
|
+
userInfo: payload
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
CustomerSupportConfig.shared.openWhatsApp()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
//
|
|
2
|
+
// CustomerSupportConfig.swift
|
|
3
|
+
// MyTatvaCare
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
import Foundation
|
|
7
|
+
import UIKit
|
|
8
|
+
|
|
9
|
+
extension Notification.Name {
|
|
10
|
+
static let customerSupportConfigDidUpdate = Notification.Name("customerSupportConfigDidUpdate")
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@objc public class CustomerSupportConfig: NSObject {
|
|
14
|
+
@objc public static let shared = CustomerSupportConfig()
|
|
15
|
+
|
|
16
|
+
static let defaultWhatsAppNumber = "918511975757"
|
|
17
|
+
static let defaultDemoVideoUrl =
|
|
18
|
+
"https://www.youtube.com/embed/65vl2aE1K_I"
|
|
19
|
+
|
|
20
|
+
private(set) var isLoaded = false
|
|
21
|
+
private(set) var isShowWhatsapp = false
|
|
22
|
+
private(set) var whatsAppNumber = CustomerSupportConfig.defaultWhatsAppNumber
|
|
23
|
+
private(set) var watchCGMDemoUrl = CustomerSupportConfig.defaultDemoVideoUrl
|
|
24
|
+
|
|
25
|
+
private override init() {}
|
|
26
|
+
|
|
27
|
+
func effectiveWhatsAppNumber() -> String {
|
|
28
|
+
whatsAppNumber.isEmpty ? Self.defaultWhatsAppNumber : whatsAppNumber
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
func shouldShowWhatsapp() -> Bool {
|
|
32
|
+
guard isLoaded else { return false }
|
|
33
|
+
return isShowWhatsapp && !effectiveWhatsAppNumber().isEmpty
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
func demoVideoURL(isForReconnect: Bool) -> String {
|
|
37
|
+
watchCGMDemoUrl.isEmpty ? Self.defaultDemoVideoUrl : watchCGMDemoUrl
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func updateFromResponse(_ response: String) {
|
|
41
|
+
guard let data = response.data(using: .utf8),
|
|
42
|
+
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
43
|
+
print("CustomerSupportConfig: failed to parse response, using failure defaults")
|
|
44
|
+
applyFailureDefaults()
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let code: Int
|
|
49
|
+
if let intCode = json["code"] as? Int {
|
|
50
|
+
code = intCode
|
|
51
|
+
} else if let strCode = json["code"] as? String, let parsed = Int(strCode) {
|
|
52
|
+
code = parsed
|
|
53
|
+
} else {
|
|
54
|
+
code = 0
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
guard code == 1,
|
|
58
|
+
let dataObject = json["data"] as? [String: Any] else {
|
|
59
|
+
print("CustomerSupportConfig: non-success API response, using failure defaults")
|
|
60
|
+
applyFailureDefaults()
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
isShowWhatsapp = parseBool(dataObject["isShowWhatsapp"])
|
|
65
|
+
|
|
66
|
+
if let apiNumber = dataObject["whatsAppNumber"] as? String {
|
|
67
|
+
whatsAppNumber = apiNumber.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
68
|
+
}
|
|
69
|
+
if whatsAppNumber.isEmpty {
|
|
70
|
+
whatsAppNumber = Self.defaultWhatsAppNumber
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if let apiDemoUrl = dataObject["watchCGMDemoUrl"] as? String {
|
|
74
|
+
watchCGMDemoUrl = apiDemoUrl.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
75
|
+
}
|
|
76
|
+
if watchCGMDemoUrl.isEmpty {
|
|
77
|
+
watchCGMDemoUrl = Self.defaultDemoVideoUrl
|
|
78
|
+
}
|
|
79
|
+
isLoaded = true
|
|
80
|
+
|
|
81
|
+
print("CustomerSupportConfig loaded | showWhatsapp=\(isShowWhatsapp) | demoUrl=\(watchCGMDemoUrl)")
|
|
82
|
+
notifyConfigDidUpdate()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private func notifyConfigDidUpdate() {
|
|
86
|
+
DispatchQueue.main.async {
|
|
87
|
+
NotificationCenter.default.post(
|
|
88
|
+
name: .customerSupportConfigDidUpdate,
|
|
89
|
+
object: nil
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
func applyFailureDefaults() {
|
|
95
|
+
isShowWhatsapp = true
|
|
96
|
+
whatsAppNumber = Self.defaultWhatsAppNumber
|
|
97
|
+
watchCGMDemoUrl = Self.defaultDemoVideoUrl
|
|
98
|
+
isLoaded = true
|
|
99
|
+
print("CustomerSupportConfig: failure defaults applied | showWhatsapp=\(isShowWhatsapp) | demoUrl=\(watchCGMDemoUrl)")
|
|
100
|
+
notifyConfigDidUpdate()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@objc(fetchCustomerSupportWithToken:)
|
|
104
|
+
public func fetchCustomerSupport(withToken token: String) {
|
|
105
|
+
guard !token.isEmpty else {
|
|
106
|
+
print("CustomerSupportConfig: empty token, using failure defaults")
|
|
107
|
+
DispatchQueue.main.async { [weak self] in
|
|
108
|
+
self?.applyFailureDefaults()
|
|
109
|
+
}
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
API.shared.fetchCustomerSupportDetails(
|
|
114
|
+
token: token,
|
|
115
|
+
onSuccess: { [weak self] response in
|
|
116
|
+
DispatchQueue.main.async {
|
|
117
|
+
self?.updateFromResponse(response)
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
onFailure: { [weak self] in
|
|
121
|
+
print("CustomerSupportConfig: API fetch failed, using failure defaults")
|
|
122
|
+
DispatchQueue.main.async {
|
|
123
|
+
self?.applyFailureDefaults()
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
func fetchIfNeeded() {
|
|
130
|
+
guard !isLoaded else { return }
|
|
131
|
+
let token = currentAuthToken()
|
|
132
|
+
guard !token.isEmpty, token != defaultToken else {
|
|
133
|
+
print("CustomerSupportConfig: no valid token, using failure defaults")
|
|
134
|
+
applyFailureDefaults()
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
fetchCustomerSupport(withToken: token)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
func openWhatsApp() {
|
|
141
|
+
let cleanedNumber = effectiveWhatsAppNumber().replacingOccurrences(
|
|
142
|
+
of: "[^0-9]",
|
|
143
|
+
with: "",
|
|
144
|
+
options: .regularExpression
|
|
145
|
+
)
|
|
146
|
+
guard !cleanedNumber.isEmpty,
|
|
147
|
+
let url = URL(string: "https://wa.me/\(cleanedNumber)") else {
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if UIApplication.shared.canOpenURL(url) {
|
|
152
|
+
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private func parseBool(_ value: Any?) -> Bool {
|
|
157
|
+
guard let value else { return false }
|
|
158
|
+
switch value {
|
|
159
|
+
case let bool as Bool:
|
|
160
|
+
return bool
|
|
161
|
+
case let int as Int:
|
|
162
|
+
return int == 1
|
|
163
|
+
case let string as String:
|
|
164
|
+
return string.lowercased() == "true" || string == "1"
|
|
165
|
+
default:
|
|
166
|
+
return false
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
package/ios/Support/Global.swift
CHANGED
|
@@ -14,8 +14,8 @@ enum Enum_stroyboard: String {
|
|
|
14
14
|
case Main = "MainCGM"
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
var watchCGMVideo = "https://www.youtube.com/embed/
|
|
18
|
-
var reconnectionwatchCGMVideo = "https://www.youtube.com/embed/
|
|
17
|
+
var watchCGMVideo = "https://www.youtube.com/embed/65vl2aE1K_I"
|
|
18
|
+
var reconnectionwatchCGMVideo = "https://www.youtube.com/embed/65vl2aE1K_I"
|
|
19
19
|
var applyingTheSensor = "https://www.youtube.com/embed/O01BBxzxGmE?si=SblmJ14boqu7Y4qM"
|
|
20
20
|
var applyingTheTransmitter = "https://www.youtube.com/embed/6m3pxl5BK9A?si=08OGnUJf2BdvAYfd"
|
|
21
21
|
var connectionJourneyError = "https://www.youtube.com/embed/mVMBdUeTlhc?si=ODZSiDW96UqyFiZb"
|