appium-webdriveragent 16.2.2 → 16.3.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 +6 -0
- package/PrivateHeaders/XCTest/XCSynthesizedEventRecord.h +3 -3
- package/PrivateHeaders/XCTest/XCUIApplication.h +1 -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.m +4 -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 +19 -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 +50 -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,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.3.0</string>
|
|
19
19
|
<key>CFBundleSignature</key>
|
|
20
20
|
<string>????</string>
|
|
21
21
|
<key>CFBundleVersion</key>
|
|
22
|
-
<string>16.
|
|
22
|
+
<string>16.3.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
|
|
@@ -8,17 +8,21 @@
|
|
|
8
8
|
|
|
9
9
|
#import "FBWebServer.h"
|
|
10
10
|
|
|
11
|
+
#if TARGET_OS_WATCH
|
|
12
|
+
#import "FBWatchHTTPServer.h"
|
|
13
|
+
#else
|
|
11
14
|
#import "RoutingConnection.h"
|
|
12
15
|
#import "RoutingHTTPServer.h"
|
|
16
|
+
#import "FBMjpegServer.h"
|
|
17
|
+
#import "FBTCPSocket.h"
|
|
18
|
+
#endif
|
|
13
19
|
|
|
14
20
|
#import "FBCommandHandler.h"
|
|
15
21
|
#import "FBErrorBuilder.h"
|
|
16
22
|
#import "FBExceptionHandler.h"
|
|
17
|
-
#import "FBMjpegServer.h"
|
|
18
23
|
#import "FBRouteRequest.h"
|
|
19
24
|
#import "FBRuntimeUtils.h"
|
|
20
25
|
#import "FBSession.h"
|
|
21
|
-
#import "FBTCPSocket.h"
|
|
22
26
|
#import "FBUnknownCommands.h"
|
|
23
27
|
#import "FBConfiguration.h"
|
|
24
28
|
#import "FBLogger.h"
|
|
@@ -28,6 +32,7 @@
|
|
|
28
32
|
static NSString *const FBServerURLBeginMarker = @"ServerURLHere->";
|
|
29
33
|
static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
|
|
30
34
|
|
|
35
|
+
#if !TARGET_OS_WATCH
|
|
31
36
|
@interface FBHTTPConnection : RoutingConnection
|
|
32
37
|
@end
|
|
33
38
|
|
|
@@ -45,21 +50,28 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
|
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
@end
|
|
53
|
+
#endif
|
|
48
54
|
|
|
49
55
|
|
|
50
56
|
@interface FBWebServer ()
|
|
51
57
|
@property (nonatomic, strong) FBExceptionHandler *exceptionHandler;
|
|
58
|
+
#if TARGET_OS_WATCH
|
|
59
|
+
@property (nonatomic, strong) FBWatchHTTPServer *server;
|
|
60
|
+
#else
|
|
52
61
|
@property (nonatomic, strong) RoutingHTTPServer *server;
|
|
53
|
-
@property (atomic, assign) BOOL keepAlive;
|
|
54
62
|
@property (nonatomic, nullable) FBTCPSocket *screenshotsBroadcaster;
|
|
55
63
|
@property (nonatomic, nullable, strong) FBMjpegServer *mjpegServer;
|
|
64
|
+
#endif
|
|
65
|
+
@property (atomic, assign) BOOL keepAlive;
|
|
56
66
|
@end
|
|
57
67
|
|
|
58
68
|
@implementation FBWebServer
|
|
59
69
|
|
|
60
70
|
- (void)dealloc
|
|
61
71
|
{
|
|
72
|
+
#if !TARGET_OS_WATCH
|
|
62
73
|
[self stopScreenshotsBroadcaster];
|
|
74
|
+
#endif
|
|
63
75
|
}
|
|
64
76
|
|
|
65
77
|
+ (NSArray<Class<FBCommandHandler>> *)collectCommandHandlerClasses
|
|
@@ -84,7 +96,9 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
|
|
|
84
96
|
if (![self startHTTPServer]) {
|
|
85
97
|
return;
|
|
86
98
|
}
|
|
99
|
+
#if !TARGET_OS_WATCH
|
|
87
100
|
[self initScreenshotsBroadcaster];
|
|
101
|
+
#endif
|
|
88
102
|
|
|
89
103
|
self.keepAlive = YES;
|
|
90
104
|
NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
|
|
@@ -94,22 +108,30 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
|
|
|
94
108
|
|
|
95
109
|
- (BOOL)startHTTPServer
|
|
96
110
|
{
|
|
111
|
+
#if TARGET_OS_WATCH
|
|
112
|
+
self.server = [[FBWatchHTTPServer alloc] init];
|
|
113
|
+
#else
|
|
97
114
|
self.server = [[RoutingHTTPServer alloc] init];
|
|
115
|
+
#endif
|
|
98
116
|
[self.server setRouteQueue:dispatch_get_main_queue()];
|
|
99
117
|
[self.server setDefaultHeader:@"Server" value:@"WebDriverAgent/1.0"];
|
|
100
118
|
[self.server setDefaultHeader:@"Access-Control-Allow-Origin" value:@"*"];
|
|
101
119
|
[self.server setDefaultHeader:@"Access-Control-Allow-Headers" value:@"Content-Type, X-Requested-With"];
|
|
120
|
+
#if !TARGET_OS_WATCH
|
|
102
121
|
[self.server setConnectionClass:[FBHTTPConnection self]];
|
|
122
|
+
#endif
|
|
103
123
|
|
|
104
124
|
[self registerRouteHandlers:[self.class collectCommandHandlerClasses]];
|
|
105
125
|
[self registerServerKeyRouteHandlers];
|
|
106
126
|
|
|
107
127
|
NSRange serverPortRange = FBConfiguration.sharedInstance.bindingPortRange;
|
|
108
128
|
NSString *bindingIP = FBConfiguration.sharedInstance.bindingIPAddress;
|
|
129
|
+
#if !TARGET_OS_WATCH
|
|
109
130
|
if (bindingIP != nil) {
|
|
110
131
|
[self.server setInterface:bindingIP];
|
|
111
132
|
[FBLogger logFmt:@"Using custom binding IP address: %@", bindingIP];
|
|
112
133
|
}
|
|
134
|
+
#endif
|
|
113
135
|
|
|
114
136
|
NSError *error;
|
|
115
137
|
BOOL serverStarted = NO;
|
|
@@ -141,6 +163,7 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
|
|
|
141
163
|
return YES;
|
|
142
164
|
}
|
|
143
165
|
|
|
166
|
+
#if !TARGET_OS_WATCH
|
|
144
167
|
- (void)initScreenshotsBroadcaster
|
|
145
168
|
{
|
|
146
169
|
[self readMjpegSettingsFromEnv];
|
|
@@ -186,11 +209,14 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
|
|
|
186
209
|
FBConfiguration.sharedInstance.mjpegServerScreenshotQuality = [screenshotQuality integerValue];
|
|
187
210
|
}
|
|
188
211
|
}
|
|
212
|
+
#endif
|
|
189
213
|
|
|
190
214
|
- (void)stopServing
|
|
191
215
|
{
|
|
192
216
|
[FBSession.activeSession kill];
|
|
217
|
+
#if !TARGET_OS_WATCH
|
|
193
218
|
[self stopScreenshotsBroadcaster];
|
|
219
|
+
#endif
|
|
194
220
|
if (self.server.isRunning) {
|
|
195
221
|
[self.server stop:NO];
|
|
196
222
|
}
|
|
@@ -199,7 +225,11 @@ static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
|
|
|
199
225
|
self.keepAlive = NO;
|
|
200
226
|
}
|
|
201
227
|
|
|
228
|
+
#if TARGET_OS_WATCH
|
|
229
|
+
- (BOOL)attemptToStartServer:(FBWatchHTTPServer *)server onPort:(NSInteger)port withError:(NSError **)error
|
|
230
|
+
#else
|
|
202
231
|
- (BOOL)attemptToStartServer:(RoutingHTTPServer *)server onPort:(NSInteger)port withError:(NSError **)error
|
|
232
|
+
#endif
|
|
203
233
|
{
|
|
204
234
|
server.port = (UInt16)port;
|
|
205
235
|
NSError *innerError = nil;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2015-present, Facebook, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the BSD-style license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// RoutingHTTPServer/CocoaHTTPServer need BSD sockets (via GCDAsyncSocket), which watchOS
|
|
10
|
+
// forbids - see FBTCPSocket.h/.m. This is a minimal HTTP/1.1 server on top of the watchOS
|
|
11
|
+
// FBTCPSocket, mirroring just enough of RoutingHTTPServer's API for FBWebServer.m to swap
|
|
12
|
+
// servers with a single #if TARGET_OS_WATCH.
|
|
13
|
+
//
|
|
14
|
+
// No chunked encoding, range requests, or pipelining - just request line + headers +
|
|
15
|
+
// Content-Length body, and ":param" path matching like RoutingHTTPServer.m.
|
|
16
|
+
|
|
17
|
+
@import Foundation;
|
|
18
|
+
|
|
19
|
+
#import "RouteRequest.h"
|
|
20
|
+
#import "RouteResponse.h"
|
|
21
|
+
|
|
22
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
23
|
+
|
|
24
|
+
@interface FBWatchHTTPServer : NSObject
|
|
25
|
+
|
|
26
|
+
/*! The port the server is (or will be) listening on */
|
|
27
|
+
@property (nonatomic) uint16_t port;
|
|
28
|
+
|
|
29
|
+
/*! Whether the server is currently listening for connections */
|
|
30
|
+
@property (nonatomic, readonly) BOOL isRunning;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
Sets the dispatch queue on which route blocks are invoked. Pass NULL to invoke them
|
|
34
|
+
synchronously on the socket's own queue.
|
|
35
|
+
*/
|
|
36
|
+
- (void)setRouteQueue:(nullable dispatch_queue_t)queue;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
Sets a header which is added to every response, unless overridden by the route itself.
|
|
40
|
+
*/
|
|
41
|
+
- (void)setDefaultHeader:(NSString *)field value:(NSString *)value;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
Registers a route handler for the given HTTP method and path pattern (":param" segments are
|
|
45
|
+
captured into the request's `params`, matching RoutingHTTPServer's convention).
|
|
46
|
+
*/
|
|
47
|
+
- (void)handleMethod:(NSString *)method
|
|
48
|
+
withPath:(NSString *)path
|
|
49
|
+
block:(void (^)(RouteRequest *request, RouteResponse *response))block;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
Convenience for -handleMethod:@"GET" withPath:path block:block.
|
|
53
|
+
*/
|
|
54
|
+
- (void)get:(NSString *)path withBlock:(void (^)(RouteRequest *request, RouteResponse *response))block;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
Starts listening on `port`.
|
|
58
|
+
*/
|
|
59
|
+
- (BOOL)start:(NSError **)error;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
Stops listening and disconnects all clients.
|
|
63
|
+
*/
|
|
64
|
+
- (void)stop:(BOOL)immediately;
|
|
65
|
+
|
|
66
|
+
@end
|
|
67
|
+
|
|
68
|
+
NS_ASSUME_NONNULL_END
|