appium-webdriveragent 16.2.2 → 16.4.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/CHANGELOG.md +12 -0
- package/PrivateHeaders/XCTest/XCSynthesizedEventRecord.h +3 -3
- package/PrivateHeaders/XCTest/XCUIApplication.h +1 -1
- package/PrivateHeaders/XCTest/XCUIDevice.h +0 -1
- package/PrivateHeaders/XCTest/XCUIElement.h +1 -1
- package/Scripts/build.sh +6 -0
- package/WebDriverAgent.xcodeproj/project.pbxproj +1482 -2
- package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/IntegrationApp_watchOS.xcscheme +76 -0
- package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/IntegrationTests_watchOS.xcscheme +86 -0
- package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentLib_watchOS.xcscheme +85 -0
- package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentRunner.xcscheme +1 -1
- package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentRunner_watchOS.xcscheme +128 -0
- package/WebDriverAgentLib/Categories/XCUIApplication+FBAlert.m +5 -0
- package/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m +2 -0
- package/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.m +1 -1
- package/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h +34 -0
- package/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m +76 -4
- package/WebDriverAgentLib/Categories/XCUIDevice+FBRotation.h +1 -1
- package/WebDriverAgentLib/Categories/XCUIDevice+FBRotation.m +1 -1
- package/WebDriverAgentLib/Categories/XCUIElement+FBPickerWheel.m +1 -1
- package/WebDriverAgentLib/Categories/XCUIElement+FBTyping.m +11 -0
- package/WebDriverAgentLib/Commands/FBCustomCommands.m +55 -4
- package/WebDriverAgentLib/Commands/FBElementCommands.m +6 -5
- package/WebDriverAgentLib/Commands/FBOrientationCommands.m +1 -1
- package/WebDriverAgentLib/Commands/FBSessionCommands.m +29 -5
- package/WebDriverAgentLib/Info.plist +2 -2
- package/WebDriverAgentLib/Routing/FBTCPSocket.h +70 -0
- package/WebDriverAgentLib/Routing/FBTCPSocket.m +199 -0
- package/WebDriverAgentLib/Routing/FBWebServer.m +33 -3
- package/WebDriverAgentLib/Routing/WatchOS/FBWatchHTTPServer.h +68 -0
- package/WebDriverAgentLib/Routing/WatchOS/FBWatchHTTPServer.m +376 -0
- package/WebDriverAgentLib/Routing/WatchOS/RouteRequest.h +30 -0
- package/WebDriverAgentLib/Routing/WatchOS/RouteRequest.m +25 -0
- package/WebDriverAgentLib/Routing/WatchOS/RouteResponse.h +30 -0
- package/WebDriverAgentLib/Routing/WatchOS/RouteResponse.m +54 -0
- package/WebDriverAgentLib/Utilities/FBActiveAppDetectionPoint.m +8 -0
- package/WebDriverAgentLib/Utilities/FBBaseActionsSynthesizer.h +1 -1
- package/WebDriverAgentLib/Utilities/FBBaseActionsSynthesizer.m +1 -1
- package/WebDriverAgentLib/Utilities/FBConfiguration.h +1 -1
- package/WebDriverAgentLib/Utilities/FBConfiguration.m +3 -3
- package/WebDriverAgentLib/Utilities/FBImageProcessor.m +7 -2
- package/WebDriverAgentLib/Utilities/FBMacros.h +14 -5
- package/WebDriverAgentLib/Utilities/FBMathUtils.h +1 -1
- package/WebDriverAgentLib/Utilities/FBMathUtils.m +1 -1
- package/WebDriverAgentLib/Utilities/FBPasteboard.h +1 -1
- package/WebDriverAgentLib/Utilities/FBPasteboard.m +1 -1
- package/WebDriverAgentLib/Utilities/FBSettingsHandler.m +2 -2
- package/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.h +1 -1
- package/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.m +1 -1
- package/WebDriverAgentTests/IntegrationApp_watchOS/ContentView.swift +31 -0
- package/WebDriverAgentTests/IntegrationApp_watchOS/WatchSpikeApp.swift +18 -0
- package/WebDriverAgentTests/IntegrationTests/FBForceTouchTests.m +3 -0
- package/WebDriverAgentTests/IntegrationTests/FBPasteboardTests.m +3 -2
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDAAlertIntegrationTests.swift +23 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDAAppLifecycleIntegrationTests.swift +57 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDAClickIntegrationTests.swift +31 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDADeviceIntegrationTests.swift +113 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDAElementAttributeIntegrationTests.swift +57 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDAFindIntegrationTests.swift +126 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDAScreenshotAndSourceIntegrationTests.swift +52 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDASessionIntegrationTests.swift +72 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDATypingIntegrationTests.swift +57 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDAUnknownCommandIntegrationTests.swift +24 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDAWatchHTTPClient.swift +95 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDAWatchIntegrationTestCase.swift +61 -0
- package/package.json +1 -1
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
#import "FBCustomCommands.h"
|
|
10
10
|
|
|
11
|
+
#if TARGET_OS_WATCH
|
|
12
|
+
@import WatchKit;
|
|
13
|
+
#endif
|
|
11
14
|
#import <XCTest/XCUIDevice.h>
|
|
12
15
|
#import <CoreLocation/CoreLocation.h>
|
|
13
16
|
|
|
@@ -52,7 +55,7 @@
|
|
|
52
55
|
[[FBRoute GET:@"/wda/screen"].withoutSession respondWithTarget:self action:@selector(handleGetScreen:)],
|
|
53
56
|
[[FBRoute GET:@"/wda/activeAppInfo"] respondWithTarget:self action:@selector(handleActiveAppInfo:)],
|
|
54
57
|
[[FBRoute GET:@"/wda/activeAppInfo"].withoutSession respondWithTarget:self action:@selector(handleActiveAppInfo:)],
|
|
55
|
-
#if !TARGET_OS_TV // tvOS
|
|
58
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH // tvOS/watchOS do not provide relevant APIs
|
|
56
59
|
[[FBRoute POST:@"/wda/setPasteboard"] respondWithTarget:self action:@selector(handleSetPasteboard:)],
|
|
57
60
|
[[FBRoute POST:@"/wda/setPasteboard"].withoutSession respondWithTarget:self action:@selector(handleSetPasteboard:)],
|
|
58
61
|
[[FBRoute POST:@"/wda/getPasteboard"] respondWithTarget:self action:@selector(handleGetPasteboard:)],
|
|
@@ -60,6 +63,10 @@
|
|
|
60
63
|
[[FBRoute GET:@"/wda/batteryInfo"] respondWithTarget:self action:@selector(handleGetBatteryInfo:)],
|
|
61
64
|
#endif
|
|
62
65
|
[[FBRoute POST:@"/wda/pressButton"] respondWithTarget:self action:@selector(handlePressButtonCommand:)],
|
|
66
|
+
#if TARGET_OS_WATCH
|
|
67
|
+
[[FBRoute POST:@"/wda/rotateDigitalCrown"] respondWithTarget:self action:@selector(handleRotateDigitalCrownCommand:)],
|
|
68
|
+
[[FBRoute POST:@"/wda/performHandGesture"] respondWithTarget:self action:@selector(handlePerformHandGestureCommand:)],
|
|
69
|
+
#endif
|
|
63
70
|
[[FBRoute POST:@"/wda/performAccessibilityAudit"] respondWithTarget:self action:@selector(handlePerformAccessibilityAudit:)],
|
|
64
71
|
[[FBRoute POST:@"/wda/performIoHidEvent"] respondWithTarget:self action:@selector(handlePeformIOHIDEvent:)],
|
|
65
72
|
[[FBRoute POST:@"/wda/expectNotification"] respondWithTarget:self action:@selector(handleExpectNotification:)],
|
|
@@ -72,8 +79,10 @@
|
|
|
72
79
|
[[FBRoute GET:@"/wda/device/location"] respondWithTarget:self action:@selector(handleGetLocation:)],
|
|
73
80
|
[[FBRoute GET:@"/wda/device/location"].withoutSession respondWithTarget:self action:@selector(handleGetLocation:)],
|
|
74
81
|
#if !TARGET_OS_TV // tvOS does not provide relevant APIs
|
|
82
|
+
#if !TARGET_OS_WATCH
|
|
75
83
|
#if __clang_major__ >= 15
|
|
76
84
|
[[FBRoute POST:@"/wda/element/:uuid/keyboardInput"] respondWithTarget:self action:@selector(handleKeyboardInput:)],
|
|
85
|
+
#endif
|
|
77
86
|
#endif
|
|
78
87
|
[[FBRoute GET:@"/wda/simulatedLocation"] respondWithTarget:self action:@selector(handleGetSimulatedLocation:)],
|
|
79
88
|
[[FBRoute GET:@"/wda/simulatedLocation"].withoutSession respondWithTarget:self action:@selector(handleGetSimulatedLocation:)],
|
|
@@ -151,7 +160,7 @@
|
|
|
151
160
|
XCUIElement *mainStatusBar = app.statusBars.allElementsBoundByIndex.firstObject;
|
|
152
161
|
CGSize statusBarSize = (nil == mainStatusBar) ? CGSizeZero : mainStatusBar.frame.size;
|
|
153
162
|
|
|
154
|
-
#if TARGET_OS_TV
|
|
163
|
+
#if TARGET_OS_TV || TARGET_OS_WATCH
|
|
155
164
|
CGSize screenSize = app.frame.size;
|
|
156
165
|
#else
|
|
157
166
|
CGSize screenSize = FBAdjustDimensionsForApplication(app.wdFrame.size, app.interfaceOrientation);
|
|
@@ -240,7 +249,7 @@
|
|
|
240
249
|
};
|
|
241
250
|
}
|
|
242
251
|
|
|
243
|
-
#if !TARGET_OS_TV
|
|
252
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
244
253
|
+ (id<FBResponsePayload>)handleSetPasteboard:(FBRouteRequest *)request
|
|
245
254
|
{
|
|
246
255
|
NSString *contentType = request.arguments[@"contentType"] ?: @"plaintext";
|
|
@@ -290,6 +299,38 @@
|
|
|
290
299
|
return FBResponseWithOK();
|
|
291
300
|
}
|
|
292
301
|
|
|
302
|
+
#if TARGET_OS_WATCH
|
|
303
|
+
+ (id<FBResponsePayload>)handleRotateDigitalCrownCommand:(FBRouteRequest *)request
|
|
304
|
+
{
|
|
305
|
+
NSNumber *delta = request.arguments[@"delta"];
|
|
306
|
+
if (nil == delta) {
|
|
307
|
+
return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:@"'delta' argument is mandatory"
|
|
308
|
+
traceback:nil]);
|
|
309
|
+
}
|
|
310
|
+
NSError *error;
|
|
311
|
+
if (![XCUIDevice.sharedDevice fb_rotateDigitalCrown:delta.doubleValue
|
|
312
|
+
velocity:request.arguments[@"velocity"]
|
|
313
|
+
error:&error]) {
|
|
314
|
+
return FBResponseWithUnknownError(error);
|
|
315
|
+
}
|
|
316
|
+
return FBResponseWithOK();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
+ (id<FBResponsePayload>)handlePerformHandGestureCommand:(FBRouteRequest *)request
|
|
320
|
+
{
|
|
321
|
+
NSString *gestureName = request.arguments[@"name"];
|
|
322
|
+
if (nil == gestureName) {
|
|
323
|
+
return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:@"'name' argument is mandatory"
|
|
324
|
+
traceback:nil]);
|
|
325
|
+
}
|
|
326
|
+
NSError *error;
|
|
327
|
+
if (![XCUIDevice.sharedDevice fb_performHandGesture:gestureName error:&error]) {
|
|
328
|
+
return FBResponseWithUnknownError(error);
|
|
329
|
+
}
|
|
330
|
+
return FBResponseWithOK();
|
|
331
|
+
}
|
|
332
|
+
#endif
|
|
333
|
+
|
|
293
334
|
+ (id<FBResponsePayload>)handleActivateSiri:(FBRouteRequest *)request
|
|
294
335
|
{
|
|
295
336
|
NSError *error;
|
|
@@ -357,7 +398,9 @@
|
|
|
357
398
|
[locationManager setDistanceFilter:kCLHeadingFilterNone];
|
|
358
399
|
// Always return the best acurate location data
|
|
359
400
|
[locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
|
|
401
|
+
#if !TARGET_OS_WATCH
|
|
360
402
|
[locationManager setPausesLocationUpdatesAutomatically:NO];
|
|
403
|
+
#endif
|
|
361
404
|
[locationManager startUpdatingLocation];
|
|
362
405
|
|
|
363
406
|
CLAuthorizationStatus authStatus;
|
|
@@ -438,11 +481,17 @@
|
|
|
438
481
|
@{
|
|
439
482
|
@"currentLocale": currentLocale,
|
|
440
483
|
@"timeZone": self.timeZone,
|
|
484
|
+
#if TARGET_OS_WATCH
|
|
485
|
+
@"name": WKInterfaceDevice.currentDevice.name,
|
|
486
|
+
@"model": WKInterfaceDevice.currentDevice.model,
|
|
487
|
+
@"uuid": @"unknown",
|
|
488
|
+
#else
|
|
441
489
|
@"name": UIDevice.currentDevice.name,
|
|
442
490
|
@"model": UIDevice.currentDevice.model,
|
|
443
491
|
@"uuid": [UIDevice.currentDevice.identifierForVendor UUIDString] ?: @"unknown",
|
|
444
492
|
// https://developer.apple.com/documentation/uikit/uiuserinterfaceidiom?language=objc
|
|
445
493
|
@"userInterfaceIdiom": @(UIDevice.currentDevice.userInterfaceIdiom),
|
|
494
|
+
#endif
|
|
446
495
|
@"userInterfaceStyle": self.userInterfaceStyle,
|
|
447
496
|
#if TARGET_OS_SIMULATOR
|
|
448
497
|
@"isSimulator": @(YES),
|
|
@@ -473,6 +522,7 @@
|
|
|
473
522
|
}
|
|
474
523
|
|
|
475
524
|
static id userInterfaceStyle = nil;
|
|
525
|
+
#if !TARGET_OS_WATCH
|
|
476
526
|
static dispatch_once_t styleOnceToken;
|
|
477
527
|
dispatch_once(&styleOnceToken, ^{
|
|
478
528
|
if ([UITraitCollection respondsToSelector:NSSelectorFromString(@"currentTraitCollection")]) {
|
|
@@ -482,6 +532,7 @@
|
|
|
482
532
|
}
|
|
483
533
|
}
|
|
484
534
|
});
|
|
535
|
+
#endif
|
|
485
536
|
|
|
486
537
|
if (nil == userInterfaceStyle) {
|
|
487
538
|
return @"unsupported";
|
|
@@ -571,7 +622,7 @@
|
|
|
571
622
|
return FBResponseWithOK();
|
|
572
623
|
}
|
|
573
624
|
|
|
574
|
-
#if __clang_major__ >= 15
|
|
625
|
+
#if __clang_major__ >= 15 && !TARGET_OS_WATCH
|
|
575
626
|
+ (id<FBResponsePayload>)handleKeyboardInput:(FBRouteRequest *)request
|
|
576
627
|
{
|
|
577
628
|
FBElementCache *elementCache = request.session.elementCache;
|
|
@@ -73,7 +73,8 @@
|
|
|
73
73
|
#if TARGET_OS_TV
|
|
74
74
|
[[FBRoute GET:@"/element/:uuid/attribute/focused"] respondWithTarget:self action:@selector(handleGetFocused:)],
|
|
75
75
|
[[FBRoute POST:@"/wda/element/:uuid/focuse"] respondWithTarget:self action:@selector(handleFocuse:)],
|
|
76
|
-
#
|
|
76
|
+
#elif !TARGET_OS_WATCH
|
|
77
|
+
// Gesture synthesis isn't supported on watchOS - only /element/:uuid/click is available.
|
|
77
78
|
[[FBRoute POST:@"/wda/element/:uuid/swipe"] respondWithTarget:self action:@selector(handleSwipe:)],
|
|
78
79
|
[[FBRoute POST:@"/wda/swipe"] respondWithTarget:self action:@selector(handleSwipe:)],
|
|
79
80
|
|
|
@@ -205,7 +206,7 @@
|
|
|
205
206
|
? [value componentsJoinedByString:@""]
|
|
206
207
|
: value;
|
|
207
208
|
XCUIElementType elementType = [element elementType];
|
|
208
|
-
#if !TARGET_OS_TV
|
|
209
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
209
210
|
if (elementType == XCUIElementTypePickerWheel) {
|
|
210
211
|
[element adjustToPickerWheelValue:textToType];
|
|
211
212
|
return FBResponseWithOK();
|
|
@@ -239,7 +240,7 @@
|
|
|
239
240
|
{
|
|
240
241
|
FBElementCache *elementCache = request.session.elementCache;
|
|
241
242
|
XCUIElement *element = [elementCache elementForUUID:(NSString *)request.parameters[@"uuid"] checkStaleness:YES];
|
|
242
|
-
#if TARGET_OS_IOS
|
|
243
|
+
#if TARGET_OS_IOS || TARGET_OS_WATCH
|
|
243
244
|
[element tap];
|
|
244
245
|
#elif TARGET_OS_TV
|
|
245
246
|
NSError *error = nil;
|
|
@@ -534,7 +535,7 @@
|
|
|
534
535
|
XCUIApplication *app = request.session.activeApplication ?: XCUIApplication.fb_activeApplication;
|
|
535
536
|
|
|
536
537
|
CGRect frame = app.wdFrame;
|
|
537
|
-
#if TARGET_OS_TV
|
|
538
|
+
#if TARGET_OS_TV || TARGET_OS_WATCH
|
|
538
539
|
CGSize screenSize = frame.size;
|
|
539
540
|
#else
|
|
540
541
|
CGSize screenSize = FBAdjustDimensionsForApplication(frame.size, app.interfaceOrientation);
|
|
@@ -551,7 +552,7 @@
|
|
|
551
552
|
XCUIApplication *app = request.session.activeApplication ?: XCUIApplication.fb_activeApplication;
|
|
552
553
|
|
|
553
554
|
CGRect frame = app.wdFrame;
|
|
554
|
-
#if TARGET_OS_TV
|
|
555
|
+
#if TARGET_OS_TV || TARGET_OS_WATCH
|
|
555
556
|
CGSize screenSize = frame.size;
|
|
556
557
|
#else
|
|
557
558
|
CGSize screenSize = FBAdjustDimensionsForApplication(frame.size, app.interfaceOrientation);
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
#import "FBSessionCommands.h"
|
|
10
10
|
|
|
11
|
+
#if TARGET_OS_WATCH
|
|
12
|
+
@import WatchKit;
|
|
13
|
+
#endif
|
|
14
|
+
|
|
11
15
|
#import "FBCapabilities.h"
|
|
12
16
|
#import "FBConfiguration.h"
|
|
13
17
|
#import "FBExceptions.h"
|
|
@@ -189,6 +193,16 @@
|
|
|
189
193
|
[buildInfo setObject:version forKey:@"version"];
|
|
190
194
|
}
|
|
191
195
|
|
|
196
|
+
#if TARGET_OS_WATCH
|
|
197
|
+
NSString *osName = @"watchOS";
|
|
198
|
+
NSString *osVersion = WKInterfaceDevice.currentDevice.systemVersion;
|
|
199
|
+
NSString *deviceKind = @"watch";
|
|
200
|
+
#else
|
|
201
|
+
NSString *osName = [[UIDevice currentDevice] systemName];
|
|
202
|
+
NSString *osVersion = [[UIDevice currentDevice] systemVersion];
|
|
203
|
+
NSString *deviceKind = [self.class deviceNameByUserInterfaceIdiom:[UIDevice currentDevice].userInterfaceIdiom];
|
|
204
|
+
#endif
|
|
205
|
+
|
|
192
206
|
return FBResponseWithObject(
|
|
193
207
|
@{
|
|
194
208
|
@"ready" : @YES,
|
|
@@ -196,20 +210,20 @@
|
|
|
196
210
|
@"state" : @"success",
|
|
197
211
|
@"os" :
|
|
198
212
|
@{
|
|
199
|
-
@"name" :
|
|
200
|
-
@"version" :
|
|
213
|
+
@"name" : osName,
|
|
214
|
+
@"version" : osVersion,
|
|
201
215
|
@"sdkVersion": FBSDKVersion() ?: @"unknown",
|
|
202
216
|
@"testmanagerdVersion": @(FBTestmanagerdVersion()),
|
|
203
217
|
},
|
|
204
218
|
@"ios" :
|
|
205
219
|
@{
|
|
206
|
-
#if TARGET_OS_SIMULATOR
|
|
207
|
-
@"simulatorVersion" :
|
|
220
|
+
#if TARGET_OS_SIMULATOR && !TARGET_OS_WATCH
|
|
221
|
+
@"simulatorVersion" : osVersion,
|
|
208
222
|
#endif
|
|
209
223
|
@"ip" : [XCUIDevice sharedDevice].fb_wifiIPAddress ?: [NSNull null]
|
|
210
224
|
},
|
|
211
225
|
@"build" : buildInfo.copy,
|
|
212
|
-
@"device":
|
|
226
|
+
@"device": deviceKind
|
|
213
227
|
}
|
|
214
228
|
);
|
|
215
229
|
}
|
|
@@ -431,6 +445,7 @@
|
|
|
431
445
|
};
|
|
432
446
|
}
|
|
433
447
|
|
|
448
|
+
#if !TARGET_OS_WATCH
|
|
434
449
|
/*
|
|
435
450
|
Return the device kind as lower case
|
|
436
451
|
*/
|
|
@@ -447,14 +462,23 @@
|
|
|
447
462
|
return @"Unknown";
|
|
448
463
|
|
|
449
464
|
}
|
|
465
|
+
#endif
|
|
450
466
|
|
|
451
467
|
+ (NSDictionary *)currentCapabilities
|
|
452
468
|
{
|
|
469
|
+
#if TARGET_OS_WATCH
|
|
470
|
+
return
|
|
471
|
+
@{
|
|
472
|
+
@"device": @"watch",
|
|
473
|
+
@"sdkVersion": WKInterfaceDevice.currentDevice.systemVersion
|
|
474
|
+
};
|
|
475
|
+
#else
|
|
453
476
|
return
|
|
454
477
|
@{
|
|
455
478
|
@"device": [self.class deviceNameByUserInterfaceIdiom:[UIDevice currentDevice].userInterfaceIdiom],
|
|
456
479
|
@"sdkVersion": [[UIDevice currentDevice] systemVersion]
|
|
457
480
|
};
|
|
481
|
+
#endif
|
|
458
482
|
}
|
|
459
483
|
|
|
460
484
|
+(nullable id<FBResponsePayload>)openDeepLink:(NSString *)initialUrl
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
<key>CFBundlePackageType</key>
|
|
16
16
|
<string>FMWK</string>
|
|
17
17
|
<key>CFBundleShortVersionString</key>
|
|
18
|
-
<string>16.
|
|
18
|
+
<string>16.4.0</string>
|
|
19
19
|
<key>CFBundleSignature</key>
|
|
20
20
|
<string>????</string>
|
|
21
21
|
<key>CFBundleVersion</key>
|
|
22
|
-
<string>16.
|
|
22
|
+
<string>16.4.0</string>
|
|
23
23
|
<key>NSPrincipalClass</key>
|
|
24
24
|
<string/>
|
|
25
25
|
</dict>
|
|
@@ -6,10 +6,56 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
// TARGET_OS_WATCH must be defined before the #if below runs, or (on some older Xcode/SDK
|
|
10
|
+
// toolchains) it silently evaluates as undefined/false here despite being true for the rest of
|
|
11
|
+
// the translation unit - which desyncs this file's declarations from FBTCPSocket.m's own
|
|
12
|
+
// #if TARGET_OS_WATCH branch.
|
|
13
|
+
#import <TargetConditionals.h>
|
|
14
|
+
|
|
15
|
+
#if TARGET_OS_WATCH
|
|
16
|
+
@import Foundation;
|
|
17
|
+
// A textual import, not `@import Network;` - older Xcode/watchOS SDK combinations (verified:
|
|
18
|
+
// Xcode 15.4/watchOS 10.5) fail to expose nw_listener_t/nw_connection_t and friends through the
|
|
19
|
+
// Network module map on watchOS, even though the underlying API has existed since watchOS 5.0.
|
|
20
|
+
#import <Network/Network.h>
|
|
21
|
+
#else
|
|
9
22
|
#import "GCDAsyncSocket.h"
|
|
23
|
+
#endif
|
|
10
24
|
|
|
11
25
|
NS_ASSUME_NONNULL_BEGIN
|
|
12
26
|
|
|
27
|
+
#if TARGET_OS_WATCH
|
|
28
|
+
|
|
29
|
+
// watchOS forbids BSD sockets, so this is backed by Network.framework instead of GCDAsyncSocket -
|
|
30
|
+
// hence a differently-shaped, push-style delegate protocol.
|
|
31
|
+
@protocol FBTCPSocketDelegate <NSObject>
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
The callback which is fired on new TCP client connection
|
|
35
|
+
|
|
36
|
+
@param newClient The newly connected client
|
|
37
|
+
*/
|
|
38
|
+
- (void)didClientConnect:(nw_connection_t)newClient;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
The callback which is fired when the TCP server receives data from a connected client
|
|
42
|
+
|
|
43
|
+
@param client The client, which sent the data
|
|
44
|
+
@param data The received data
|
|
45
|
+
*/
|
|
46
|
+
- (void)client:(nw_connection_t)client didReceiveData:(NSData *)data;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
The callback which is fired when TCP client disconnects
|
|
50
|
+
|
|
51
|
+
@param client The actual disconnected client
|
|
52
|
+
*/
|
|
53
|
+
- (void)didClientDisconnect:(nw_connection_t)client;
|
|
54
|
+
|
|
55
|
+
@end
|
|
56
|
+
|
|
57
|
+
#else
|
|
58
|
+
|
|
13
59
|
@protocol FBTCPSocketDelegate
|
|
14
60
|
|
|
15
61
|
/**
|
|
@@ -35,6 +81,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
35
81
|
|
|
36
82
|
@end
|
|
37
83
|
|
|
84
|
+
#endif
|
|
85
|
+
|
|
38
86
|
|
|
39
87
|
@interface FBTCPSocket : NSObject
|
|
40
88
|
|
|
@@ -64,6 +112,28 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
64
112
|
Stops the socket if it is running
|
|
65
113
|
*/
|
|
66
114
|
- (void)stop;
|
|
115
|
+
|
|
116
|
+
#if TARGET_OS_WATCH
|
|
117
|
+
/**
|
|
118
|
+
Writes data to the given connected client
|
|
119
|
+
|
|
120
|
+
@param data The data to send
|
|
121
|
+
@param client The destination client, as received via -didClientConnect: or -client:didReceiveData:
|
|
122
|
+
*/
|
|
123
|
+
- (void)writeData:(NSData *)data toClient:(nw_connection_t)client;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
Like -writeData:toClient:, but invokes completion once the send actually goes out. Use this
|
|
127
|
+
before cancelling the connection - nw_connection_send is async, so cancelling right away can
|
|
128
|
+
drop the write before it's sent.
|
|
129
|
+
|
|
130
|
+
@param data The data to send
|
|
131
|
+
@param client The destination client
|
|
132
|
+
@param completion Called once the send attempt finishes
|
|
133
|
+
*/
|
|
134
|
+
- (void)writeData:(NSData *)data toClient:(nw_connection_t)client completion:(nullable void (^)(void))completion;
|
|
135
|
+
#endif
|
|
136
|
+
|
|
67
137
|
@end
|
|
68
138
|
|
|
69
139
|
NS_ASSUME_NONNULL_END
|
|
@@ -8,6 +8,203 @@
|
|
|
8
8
|
|
|
9
9
|
#import "FBTCPSocket.h"
|
|
10
10
|
|
|
11
|
+
#if TARGET_OS_WATCH
|
|
12
|
+
|
|
13
|
+
@interface FBTCPSocket()
|
|
14
|
+
@property (readonly, nonatomic) dispatch_queue_t socketQueue;
|
|
15
|
+
@property (nullable, nonatomic) nw_listener_t listener;
|
|
16
|
+
@property (readonly, nonatomic) NSMutableArray<nw_connection_t> *connectedClients;
|
|
17
|
+
@property (readonly, nonatomic) uint16_t port;
|
|
18
|
+
@end
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@implementation FBTCPSocket
|
|
22
|
+
|
|
23
|
+
- (instancetype)initWithPort:(uint16_t)port
|
|
24
|
+
{
|
|
25
|
+
if ((self = [super init])) {
|
|
26
|
+
_socketQueue = dispatch_queue_create("socketQueue", NULL);
|
|
27
|
+
_connectedClients = [[NSMutableArray alloc] initWithCapacity:1];
|
|
28
|
+
_port = port;
|
|
29
|
+
_delegate = nil;
|
|
30
|
+
}
|
|
31
|
+
return self;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
- (BOOL)startWithError:(NSError **)error
|
|
35
|
+
{
|
|
36
|
+
nw_parameters_t parameters = nw_parameters_create_secure_tcp(NW_PARAMETERS_DISABLE_PROTOCOL,
|
|
37
|
+
NW_PARAMETERS_DEFAULT_CONFIGURATION);
|
|
38
|
+
NSString *portString = [NSString stringWithFormat:@"%u", (unsigned int)self.port];
|
|
39
|
+
// portString is always valid UTF8; -UTF8String is just declared nullable in general.
|
|
40
|
+
const char * _Nonnull portCString = (const char * _Nonnull)portString.UTF8String;
|
|
41
|
+
nw_listener_t listener = nw_listener_create_with_port(portCString, parameters);
|
|
42
|
+
if (nil == listener) {
|
|
43
|
+
if (error) {
|
|
44
|
+
*error = [NSError errorWithDomain:@"FBTCPSocket"
|
|
45
|
+
code:1
|
|
46
|
+
userInfo:@{NSLocalizedDescriptionKey: @"Failed to create the TCP listener"}];
|
|
47
|
+
}
|
|
48
|
+
return NO;
|
|
49
|
+
}
|
|
50
|
+
self.listener = listener;
|
|
51
|
+
|
|
52
|
+
__weak typeof(self) weakSelf = self;
|
|
53
|
+
nw_listener_set_queue(listener, self.socketQueue);
|
|
54
|
+
nw_listener_set_new_connection_handler(listener, ^(nw_connection_t connection) {
|
|
55
|
+
[weakSelf acceptConnection:connection];
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
dispatch_semaphore_t startupSemaphore = dispatch_semaphore_create(0);
|
|
59
|
+
__block NSError *startupError = nil;
|
|
60
|
+
nw_listener_set_state_changed_handler(listener, ^(nw_listener_state_t state, nw_error_t nwError) {
|
|
61
|
+
// if/else, not switch: -Wswitch-enum, -Wswitch-default, and -Wcovered-switch-default can't
|
|
62
|
+
// all be satisfied by one switch statement at once.
|
|
63
|
+
if (nw_listener_state_ready == state) {
|
|
64
|
+
dispatch_semaphore_signal(startupSemaphore);
|
|
65
|
+
} else if (nw_listener_state_failed == state || nw_listener_state_cancelled == state) {
|
|
66
|
+
// NSLocalizedDescriptionKey must be a string, not the underlying NSError itself, or
|
|
67
|
+
// -[NSError localizedDescription] crashes trying to treat it as one.
|
|
68
|
+
NSError *underlyingError = nwError ? (NSError *)CFBridgingRelease(nw_error_copy_cf_error(nwError)) : nil;
|
|
69
|
+
NSMutableDictionary<NSString *, id> *userInfo = [NSMutableDictionary dictionary];
|
|
70
|
+
userInfo[NSLocalizedDescriptionKey] = underlyingError.localizedDescription ?: @"The TCP listener failed to start";
|
|
71
|
+
if (underlyingError) {
|
|
72
|
+
userInfo[NSUnderlyingErrorKey] = underlyingError;
|
|
73
|
+
}
|
|
74
|
+
startupError = [NSError errorWithDomain:@"FBTCPSocket" code:2 userInfo:userInfo];
|
|
75
|
+
dispatch_semaphore_signal(startupSemaphore);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
nw_listener_start(listener);
|
|
79
|
+
BOOL didStartInTime = 0 == dispatch_semaphore_wait(startupSemaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)));
|
|
80
|
+
if (!didStartInTime) {
|
|
81
|
+
startupError = [NSError errorWithDomain:@"FBTCPSocket"
|
|
82
|
+
code:3
|
|
83
|
+
userInfo:@{NSLocalizedDescriptionKey: @"Timed out while starting the TCP listener"}];
|
|
84
|
+
}
|
|
85
|
+
if (nil != startupError) {
|
|
86
|
+
if (error) {
|
|
87
|
+
*error = startupError;
|
|
88
|
+
}
|
|
89
|
+
self.listener = nil;
|
|
90
|
+
return NO;
|
|
91
|
+
}
|
|
92
|
+
return YES;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
- (void)acceptConnection:(nw_connection_t)connection
|
|
96
|
+
{
|
|
97
|
+
@synchronized (self.connectedClients) {
|
|
98
|
+
[self.connectedClients addObject:connection];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
__weak typeof(self) weakSelf = self;
|
|
102
|
+
nw_connection_set_queue(connection, self.socketQueue);
|
|
103
|
+
nw_connection_set_state_changed_handler(connection, ^(nw_connection_state_t state, nw_error_t connectionError) {
|
|
104
|
+
// Same reasoning as the listener state handler above.
|
|
105
|
+
if (nw_connection_state_ready == state) {
|
|
106
|
+
__strong typeof(weakSelf) strongSelf = weakSelf;
|
|
107
|
+
if (nil == strongSelf) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
id<FBTCPSocketDelegate> delegate = strongSelf.delegate;
|
|
111
|
+
if (nil != delegate) {
|
|
112
|
+
[delegate didClientConnect:connection];
|
|
113
|
+
}
|
|
114
|
+
[strongSelf scheduleReceiveForConnection:connection];
|
|
115
|
+
} else if (nw_connection_state_failed == state || nw_connection_state_cancelled == state) {
|
|
116
|
+
[weakSelf handleDisconnectForConnection:connection];
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
nw_connection_start(connection);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
- (void)scheduleReceiveForConnection:(nw_connection_t)connection
|
|
123
|
+
{
|
|
124
|
+
__weak typeof(self) weakSelf = self;
|
|
125
|
+
nw_connection_receive(connection, 1, UINT32_MAX, ^(dispatch_data_t _Nullable content,
|
|
126
|
+
nw_content_context_t _Nullable context,
|
|
127
|
+
bool isComplete,
|
|
128
|
+
nw_error_t _Nullable receiveError) {
|
|
129
|
+
__strong typeof(weakSelf) strongSelf = weakSelf;
|
|
130
|
+
if (nil == strongSelf) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (nil != content) {
|
|
134
|
+
dispatch_data_t nonnullContent = (dispatch_data_t _Nonnull)content;
|
|
135
|
+
__block NSData *data = nil;
|
|
136
|
+
dispatch_data_apply(nonnullContent, ^bool(dispatch_data_t _Nonnull region, size_t offset, const void * _Nonnull buffer, size_t size) {
|
|
137
|
+
NSMutableData *accumulated = [(data ?: [NSData data]) mutableCopy];
|
|
138
|
+
[accumulated appendBytes:buffer length:size];
|
|
139
|
+
data = accumulated.copy;
|
|
140
|
+
return true;
|
|
141
|
+
});
|
|
142
|
+
if (data.length > 0) {
|
|
143
|
+
id<FBTCPSocketDelegate> delegate = strongSelf.delegate;
|
|
144
|
+
if (nil != delegate) {
|
|
145
|
+
[delegate client:connection didReceiveData:data];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (nil != receiveError || (isComplete && nil == content)) {
|
|
150
|
+
[strongSelf handleDisconnectForConnection:connection];
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
[strongSelf scheduleReceiveForConnection:connection];
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
- (void)handleDisconnectForConnection:(nw_connection_t)connection
|
|
158
|
+
{
|
|
159
|
+
BOOL wasConnected;
|
|
160
|
+
@synchronized (self.connectedClients) {
|
|
161
|
+
wasConnected = [self.connectedClients containsObject:connection];
|
|
162
|
+
[self.connectedClients removeObject:connection];
|
|
163
|
+
}
|
|
164
|
+
if (wasConnected) {
|
|
165
|
+
id<FBTCPSocketDelegate> delegate = self.delegate;
|
|
166
|
+
if (nil != delegate) {
|
|
167
|
+
[delegate didClientDisconnect:connection];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
- (void)writeData:(NSData *)data toClient:(nw_connection_t)client
|
|
173
|
+
{
|
|
174
|
+
[self writeData:data toClient:client completion:nil];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
- (void)writeData:(NSData *)data toClient:(nw_connection_t)client completion:(nullable void (^)(void))completion
|
|
178
|
+
{
|
|
179
|
+
dispatch_data_t dispatchData = dispatch_data_create(data.bytes, data.length, self.socketQueue, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
|
|
180
|
+
nw_connection_send(client, dispatchData, NW_CONNECTION_DEFAULT_STREAM_CONTEXT, false, ^(nw_error_t _Nullable sendError) {
|
|
181
|
+
if (completion) {
|
|
182
|
+
completion();
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
- (void)stop
|
|
188
|
+
{
|
|
189
|
+
@synchronized (self.connectedClients) {
|
|
190
|
+
NSArray<nw_connection_t> *clients = self.connectedClients.copy;
|
|
191
|
+
[self.connectedClients removeAllObjects];
|
|
192
|
+
for (nw_connection_t client in clients) {
|
|
193
|
+
nw_connection_cancel(client);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
self.delegate = nil;
|
|
198
|
+
nw_listener_t listener = self.listener;
|
|
199
|
+
if (nil != listener) {
|
|
200
|
+
nw_listener_cancel((nw_listener_t _Nonnull)listener);
|
|
201
|
+
self.listener = nil;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@end
|
|
206
|
+
|
|
207
|
+
#else
|
|
11
208
|
|
|
12
209
|
@interface FBTCPSocket()
|
|
13
210
|
@property (readonly, nonatomic) dispatch_queue_t socketQueue;
|
|
@@ -95,3 +292,5 @@
|
|
|
95
292
|
}
|
|
96
293
|
|
|
97
294
|
@end
|
|
295
|
+
|
|
296
|
+
#endif
|