@appium/coresim 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 +201 -0
- package/README.md +66 -0
- package/binding.gyp +53 -0
- package/lib/src/commands/app.d.ts +88 -0
- package/lib/src/commands/app.d.ts.map +1 -0
- package/lib/src/commands/app.js +133 -0
- package/lib/src/commands/app.js.map +1 -0
- package/lib/src/commands/biometric.d.ts +33 -0
- package/lib/src/commands/biometric.d.ts.map +1 -0
- package/lib/src/commands/biometric.js +44 -0
- package/lib/src/commands/biometric.js.map +1 -0
- package/lib/src/commands/darwin-notification.d.ts +36 -0
- package/lib/src/commands/darwin-notification.d.ts.map +1 -0
- package/lib/src/commands/darwin-notification.js +35 -0
- package/lib/src/commands/darwin-notification.js.map +1 -0
- package/lib/src/commands/interaction.d.ts +48 -0
- package/lib/src/commands/interaction.d.ts.map +1 -0
- package/lib/src/commands/interaction.js +48 -0
- package/lib/src/commands/interaction.js.map +1 -0
- package/lib/src/commands/keychain.d.ts +25 -0
- package/lib/src/commands/keychain.d.ts.map +1 -0
- package/lib/src/commands/keychain.js +52 -0
- package/lib/src/commands/keychain.js.map +1 -0
- package/lib/src/commands/lifecycle.d.ts +101 -0
- package/lib/src/commands/lifecycle.d.ts.map +1 -0
- package/lib/src/commands/lifecycle.js +165 -0
- package/lib/src/commands/lifecycle.js.map +1 -0
- package/lib/src/commands/media.d.ts +28 -0
- package/lib/src/commands/media.d.ts.map +1 -0
- package/lib/src/commands/media.js +27 -0
- package/lib/src/commands/media.js.map +1 -0
- package/lib/src/commands/misc.d.ts +15 -0
- package/lib/src/commands/misc.d.ts.map +1 -0
- package/lib/src/commands/misc.js +12 -0
- package/lib/src/commands/misc.js.map +1 -0
- package/lib/src/commands/pasteboard.d.ts +24 -0
- package/lib/src/commands/pasteboard.d.ts.map +1 -0
- package/lib/src/commands/pasteboard.js +22 -0
- package/lib/src/commands/pasteboard.js.map +1 -0
- package/lib/src/commands/permissions.d.ts +48 -0
- package/lib/src/commands/permissions.d.ts.map +1 -0
- package/lib/src/commands/permissions.js +84 -0
- package/lib/src/commands/permissions.js.map +1 -0
- package/lib/src/commands/process.d.ts +16 -0
- package/lib/src/commands/process.d.ts.map +1 -0
- package/lib/src/commands/process.js +68 -0
- package/lib/src/commands/process.js.map +1 -0
- package/lib/src/commands/screenshot.d.ts +29 -0
- package/lib/src/commands/screenshot.d.ts.map +1 -0
- package/lib/src/commands/screenshot.js +30 -0
- package/lib/src/commands/screenshot.js.map +1 -0
- package/lib/src/commands/spawn.d.ts +48 -0
- package/lib/src/commands/spawn.d.ts.map +1 -0
- package/lib/src/commands/spawn.js +108 -0
- package/lib/src/commands/spawn.js.map +1 -0
- package/lib/src/commands/ui.d.ts +42 -0
- package/lib/src/commands/ui.d.ts.map +1 -0
- package/lib/src/commands/ui.js +44 -0
- package/lib/src/commands/ui.js.map +1 -0
- package/lib/src/commands/webinspector.d.ts +19 -0
- package/lib/src/commands/webinspector.d.ts.map +1 -0
- package/lib/src/commands/webinspector.js +16 -0
- package/lib/src/commands/webinspector.js.map +1 -0
- package/lib/src/errors.d.ts +51 -0
- package/lib/src/errors.d.ts.map +1 -0
- package/lib/src/errors.js +82 -0
- package/lib/src/errors.js.map +1 -0
- package/lib/src/index.d.ts +7 -0
- package/lib/src/index.d.ts.map +1 -0
- package/lib/src/index.js +5 -0
- package/lib/src/index.js.map +1 -0
- package/lib/src/native-simctl.d.ts +62 -0
- package/lib/src/native-simctl.d.ts.map +1 -0
- package/lib/src/native-simctl.js +221 -0
- package/lib/src/native-simctl.js.map +1 -0
- package/lib/src/types.d.ts +267 -0
- package/lib/src/types.d.ts.map +1 -0
- package/lib/src/types.js +29 -0
- package/lib/src/types.js.map +1 -0
- package/lib/src/utils/index.d.ts +3 -0
- package/lib/src/utils/index.d.ts.map +1 -0
- package/lib/src/utils/index.js +3 -0
- package/lib/src/utils/index.js.map +1 -0
- package/lib/src/utils/pkg-root.d.ts +6 -0
- package/lib/src/utils/pkg-root.d.ts.map +1 -0
- package/lib/src/utils/pkg-root.js +12 -0
- package/lib/src/utils/pkg-root.js.map +1 -0
- package/lib/src/utils/run-catching.d.ts +3 -0
- package/lib/src/utils/run-catching.d.ts.map +1 -0
- package/lib/src/utils/run-catching.js +11 -0
- package/lib/src/utils/run-catching.js.map +1 -0
- package/package.json +73 -0
- package/scripts/install.mjs +22 -0
- package/src/commands/app.ts +186 -0
- package/src/commands/biometric.ts +69 -0
- package/src/commands/darwin-notification.ts +51 -0
- package/src/commands/interaction.ts +74 -0
- package/src/commands/keychain.ts +63 -0
- package/src/commands/lifecycle.ts +210 -0
- package/src/commands/media.ts +38 -0
- package/src/commands/misc.ts +20 -0
- package/src/commands/pasteboard.ts +31 -0
- package/src/commands/permissions.ts +122 -0
- package/src/commands/process.ts +80 -0
- package/src/commands/screenshot.ts +46 -0
- package/src/commands/spawn.ts +133 -0
- package/src/commands/ui.ts +61 -0
- package/src/commands/webinspector.ts +23 -0
- package/src/coresim.mm +1066 -0
- package/src/errors.ts +93 -0
- package/src/index.ts +23 -0
- package/src/native/async_bridge.h +164 -0
- package/src/native/nserror_bridge.h +64 -0
- package/src/native/nserror_bridge.mm +30 -0
- package/src/native/objc_runtime.h +45 -0
- package/src/native/objc_runtime.mm +76 -0
- package/src/native/safe_dispatch.h +38 -0
- package/src/native/sim_device.h +143 -0
- package/src/native/sim_device.mm +355 -0
- package/src/native/sim_device_set.h +16 -0
- package/src/native/sim_device_set.mm +40 -0
- package/src/native/sim_pasteboard.h +16 -0
- package/src/native/sim_pasteboard.mm +295 -0
- package/src/native/sim_process.h +13 -0
- package/src/native/sim_process.mm +139 -0
- package/src/native/sim_screenshot.h +28 -0
- package/src/native/sim_screenshot.mm +226 -0
- package/src/native/sim_service_context.h +42 -0
- package/src/native/sim_service_context.mm +92 -0
- package/src/native/tcc_privacy.h +35 -0
- package/src/native/tcc_privacy.mm +256 -0
- package/src/native/value_bridge.h +18 -0
- package/src/native/value_bridge.mm +111 -0
- package/src/native-simctl.ts +288 -0
- package/src/types.ts +302 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/pkg-root.ts +14 -0
- package/src/utils/run-catching.ts +10 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
#include "sim_screenshot.h"
|
|
2
|
+
|
|
3
|
+
#import <CoreImage/CoreImage.h>
|
|
4
|
+
#import <IOSurface/IOSurface.h>
|
|
5
|
+
#import <ImageIO/ImageIO.h>
|
|
6
|
+
#import <objc/message.h>
|
|
7
|
+
|
|
8
|
+
#include "objc_runtime.h"
|
|
9
|
+
#include "safe_dispatch.h"
|
|
10
|
+
|
|
11
|
+
namespace coresim {
|
|
12
|
+
|
|
13
|
+
namespace {
|
|
14
|
+
|
|
15
|
+
NSString* const kScreenshotErrorDomain = @"com.appium.coresim.Screenshot";
|
|
16
|
+
|
|
17
|
+
NSError* MakeError(NSInteger code, NSString* message) {
|
|
18
|
+
return [NSError errorWithDomain:kScreenshotErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : message}];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
id IdGetter(id target, const std::string& selectorName) {
|
|
22
|
+
RequireSelector(target, selectorName);
|
|
23
|
+
SEL selector = SelectorNamed(selectorName);
|
|
24
|
+
return SafeInvoke([&] {
|
|
25
|
+
using Fn = id (*)(id, SEL);
|
|
26
|
+
return ((Fn)objc_msgSend)(target, selector);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// -[<SimDisplayDescriptorState> displayClass] — iOS's main display is class 0; tvOS renders only
|
|
31
|
+
// on the (non-zero) TVOut class.
|
|
32
|
+
unsigned short DisplayClass(id descriptorState) {
|
|
33
|
+
static const std::string kSelectorName = "displayClass";
|
|
34
|
+
RequireSelector(descriptorState, kSelectorName);
|
|
35
|
+
SEL selector = SelectorNamed(kSelectorName);
|
|
36
|
+
return SafeInvoke([&] {
|
|
37
|
+
using Fn = unsigned short (*)(id, SEL);
|
|
38
|
+
return ((Fn)objc_msgSend)(descriptorState, selector);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// One renderable display IO port found among -[SimDeviceIOClient ioPorts]. Keeps "descriptor"
|
|
43
|
+
// alongside the JS-facing "id"/"displayClass" fields so CaptureScreenshot can resolve a requested
|
|
44
|
+
// displayId against the same list ListDisplays reports, without walking ioPorts twice.
|
|
45
|
+
NSString* const kCandidateUUIDKey = @"id";
|
|
46
|
+
NSString* const kCandidateDisplayClassKey = @"displayClass";
|
|
47
|
+
NSString* const kCandidateDescriptorKey = @"descriptor";
|
|
48
|
+
|
|
49
|
+
// Every IO port that actually renders a screen, in -[SimDeviceIOClient ioPorts]'s own order. Only
|
|
50
|
+
// a port conforming to the (private, headerless) SimDisplayIOSurfaceRenderable/SimDisplayRenderable
|
|
51
|
+
// protocols carries a "displayClass" or either surface accessor — other ports (audio, the
|
|
52
|
+
// screenshot mach service itself) don't, so the respondsToSelector: checks below are the actual
|
|
53
|
+
// filter, not defensive leftovers. Returns nil (and sets *error) only if the device has no IO
|
|
54
|
+
// client at all (e.g. not booted); no renderable port is an empty array, not an error — the caller
|
|
55
|
+
// decides whether that's fatal.
|
|
56
|
+
NSArray<NSDictionary*>* RenderableDisplayCandidates(id device, NSError** error) {
|
|
57
|
+
id ioClient = IdGetter(device, "io");
|
|
58
|
+
if (ioClient == nil) {
|
|
59
|
+
*error = MakeError(1, @"Device has no IO client available — is it booted?");
|
|
60
|
+
return nil;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
NSArray* ports = IdGetter(ioClient, "ioPorts");
|
|
64
|
+
NSMutableArray<NSDictionary*>* candidates = [NSMutableArray array];
|
|
65
|
+
for (id port in ports) {
|
|
66
|
+
id descriptor = IdGetter(port, "descriptor");
|
|
67
|
+
if (descriptor == nil) continue;
|
|
68
|
+
if (![descriptor respondsToSelector:NSSelectorFromString(@"framebufferSurface")] &&
|
|
69
|
+
![descriptor respondsToSelector:NSSelectorFromString(@"ioSurface")]) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (![descriptor respondsToSelector:NSSelectorFromString(@"state")]) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
id state = IdGetter(descriptor, "state");
|
|
76
|
+
if (state == nil || ![state respondsToSelector:NSSelectorFromString(@"displayClass")]) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
NSUUID* uuid = IdGetter(port, "uuid");
|
|
80
|
+
[candidates addObject:@{
|
|
81
|
+
kCandidateUUIDKey : uuid ? uuid.UUIDString : @"",
|
|
82
|
+
kCandidateDisplayClassKey : @(DisplayClass(state)),
|
|
83
|
+
kCandidateDescriptorKey : descriptor,
|
|
84
|
+
}];
|
|
85
|
+
}
|
|
86
|
+
return candidates;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Picks which candidate CaptureScreenshot should read from: the one matching `displayId` if given
|
|
90
|
+
// (an error if none match), else the primary display (displayClass 0), falling back to the first
|
|
91
|
+
// renderable display found so a target with no class-0 display (e.g. tvOS) still gets a
|
|
92
|
+
// screenshot instead of an outright failure.
|
|
93
|
+
id ResolveDisplayDescriptor(NSArray<NSDictionary*>* candidates, NSString* displayId, NSError** error) {
|
|
94
|
+
if (displayId != nil) {
|
|
95
|
+
for (NSDictionary* candidate in candidates) {
|
|
96
|
+
if ([candidate[kCandidateUUIDKey] isEqualToString:displayId]) {
|
|
97
|
+
return candidate[kCandidateDescriptorKey];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
*error = MakeError(2, [NSString stringWithFormat:@"No display with id '%@' was found on this device", displayId]);
|
|
101
|
+
return nil;
|
|
102
|
+
}
|
|
103
|
+
id fallback = nil;
|
|
104
|
+
for (NSDictionary* candidate in candidates) {
|
|
105
|
+
if ([candidate[kCandidateDisplayClassKey] unsignedShortValue] == 0) {
|
|
106
|
+
return candidate[kCandidateDescriptorKey];
|
|
107
|
+
}
|
|
108
|
+
if (fallback == nil) {
|
|
109
|
+
fallback = candidate[kCandidateDescriptorKey];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (fallback == nil) {
|
|
113
|
+
*error = MakeError(3, @"No renderable display port was found on this device");
|
|
114
|
+
}
|
|
115
|
+
return fallback;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Unlike IdGetter, doesn't RequireSelector: a display proxy that has lost its connection (e.g. the
|
|
119
|
+
// device just shut down) can legitimately stop responding to a selector it answered a moment ago —
|
|
120
|
+
// that's "no surface right now", not "unsupported on this CoreSimulator", so it must not raise
|
|
121
|
+
// NativeSimUnavailableError the way a genuinely missing selector should.
|
|
122
|
+
id OptionalIdGetter(id target, NSString* selectorName) {
|
|
123
|
+
SEL selector = NSSelectorFromString(selectorName);
|
|
124
|
+
if (![target respondsToSelector:selector]) {
|
|
125
|
+
return nil;
|
|
126
|
+
}
|
|
127
|
+
return SafeInvoke([&] {
|
|
128
|
+
using Fn = id (*)(id, SEL);
|
|
129
|
+
return ((Fn)objc_msgSend)(target, selector);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// `framebufferSurface` is the primary surface since Xcode 13.2 split what used to be a single
|
|
134
|
+
// `ioSurface`; both are real (non-optional) members of the descriptor's protocol once it's passed
|
|
135
|
+
// RenderableDisplayCandidates' filter above, but the underlying remote proxy can still legitimately
|
|
136
|
+
// vend nil for either — or stop responding entirely if the connection just dropped — so both are
|
|
137
|
+
// tried before giving up.
|
|
138
|
+
id RenderableSurface(id descriptor) {
|
|
139
|
+
return OptionalIdGetter(descriptor, @"framebufferSurface") ?: OptionalIdGetter(descriptor, @"ioSurface");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
NSString* const kPNGUTI = @"public.png";
|
|
143
|
+
NSString* const kJPEGUTI = @"public.jpeg";
|
|
144
|
+
|
|
145
|
+
} // namespace
|
|
146
|
+
|
|
147
|
+
NSArray<NSDictionary*>* ListDisplays(id device, NSError** error) {
|
|
148
|
+
NSArray<NSDictionary*>* candidates = RenderableDisplayCandidates(device, error);
|
|
149
|
+
if (candidates == nil) {
|
|
150
|
+
return nil;
|
|
151
|
+
}
|
|
152
|
+
NSMutableArray<NSDictionary*>* result = [NSMutableArray arrayWithCapacity:candidates.count];
|
|
153
|
+
for (NSDictionary* candidate in candidates) {
|
|
154
|
+
unsigned short displayClass = [candidate[kCandidateDisplayClassKey] unsignedShortValue];
|
|
155
|
+
[result addObject:@{
|
|
156
|
+
@"id" : candidate[kCandidateUUIDKey],
|
|
157
|
+
@"displayClass" : candidate[kCandidateDisplayClassKey],
|
|
158
|
+
@"isMain" : @(displayClass == 0),
|
|
159
|
+
}];
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
NSData* CaptureScreenshot(id device, NSString* displayId, ScreenshotFormat format, NSNumber* jpegQualityPercent,
|
|
165
|
+
NSError** error) {
|
|
166
|
+
NSArray<NSDictionary*>* candidates = RenderableDisplayCandidates(device, error);
|
|
167
|
+
if (candidates == nil) {
|
|
168
|
+
return nil;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
id descriptor = ResolveDisplayDescriptor(candidates, displayId, error);
|
|
172
|
+
if (descriptor == nil) {
|
|
173
|
+
return nil;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
id surfaceObj = RenderableSurface(descriptor);
|
|
177
|
+
if (surfaceObj == nil) {
|
|
178
|
+
*error = MakeError(4, @"The device's display surface is not available yet");
|
|
179
|
+
return nil;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
IOSurfaceRef surfaceRef = (__bridge IOSurfaceRef)surfaceObj;
|
|
183
|
+
CIImage* ciImage = [CIImage imageWithIOSurface:surfaceRef];
|
|
184
|
+
if (ciImage == nil) {
|
|
185
|
+
*error = MakeError(5, @"Failed to wrap the device's display surface as an image");
|
|
186
|
+
return nil;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// A fresh CIContext per call, matching this operation's one-shot semantics (mirrors
|
|
190
|
+
// simctl's own screenshot command) rather than the persistent, reused context a
|
|
191
|
+
// continuous video/streaming path would want.
|
|
192
|
+
CIContext* context = [CIContext contextWithOptions:nil];
|
|
193
|
+
CGImageRef cgImage = [context createCGImage:ciImage fromRect:ciImage.extent];
|
|
194
|
+
if (cgImage == nil) {
|
|
195
|
+
*error = MakeError(6, @"Failed to render the device's display surface");
|
|
196
|
+
return nil;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
NSString* uti = format == ScreenshotFormat::kJPEG ? kJPEGUTI : kPNGUTI;
|
|
200
|
+
NSMutableData* imageData = [NSMutableData data];
|
|
201
|
+
CGImageDestinationRef destination =
|
|
202
|
+
CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, (__bridge CFStringRef)uti, 1, NULL);
|
|
203
|
+
if (destination == nullptr) {
|
|
204
|
+
CGImageRelease(cgImage);
|
|
205
|
+
*error = MakeError(7, @"Failed to create an image encoder for the captured screenshot");
|
|
206
|
+
return nil;
|
|
207
|
+
}
|
|
208
|
+
// kCGImageDestinationLossyCompressionQuality is meaningless for PNG (always lossless) — ImageIO
|
|
209
|
+
// silently ignores properties a format doesn't use, so this is only gated on jpegQualityPercent
|
|
210
|
+
// being present, not on `format` too.
|
|
211
|
+
NSDictionary* properties =
|
|
212
|
+
jpegQualityPercent != nil
|
|
213
|
+
? @{(NSString*)kCGImageDestinationLossyCompressionQuality : @(jpegQualityPercent.doubleValue / 100.0)}
|
|
214
|
+
: nil;
|
|
215
|
+
CGImageDestinationAddImage(destination, cgImage, (__bridge CFDictionaryRef)properties);
|
|
216
|
+
BOOL ok = CGImageDestinationFinalize(destination);
|
|
217
|
+
CFRelease(destination);
|
|
218
|
+
CGImageRelease(cgImage);
|
|
219
|
+
if (!ok) {
|
|
220
|
+
*error = MakeError(8, @"Failed to encode the captured screenshot");
|
|
221
|
+
return nil;
|
|
222
|
+
}
|
|
223
|
+
return imageData;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
} // namespace coresim
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#import <Foundation/Foundation.h>
|
|
4
|
+
|
|
5
|
+
namespace coresim {
|
|
6
|
+
|
|
7
|
+
// +[SimServiceContext sharedServiceContextForDeveloperDir:error:]. Returns nil (with *error set)
|
|
8
|
+
// on failure. `developerDir` is the "which Xcode" selector — the actual physical CoreSimulator
|
|
9
|
+
// binary loaded is always the one shared system-wide copy; this only picks which runtimes/device
|
|
10
|
+
// types/behaviors that shared framework resolves against.
|
|
11
|
+
id SharedServiceContext(NSString* developerDir, NSError** error);
|
|
12
|
+
|
|
13
|
+
// -[SimServiceContext defaultDeviceSetWithError:]
|
|
14
|
+
id DefaultDeviceSet(id serviceContext, NSError** error);
|
|
15
|
+
|
|
16
|
+
// -[SimServiceContext supportedDeviceTypes] -> NSArray<SimDeviceType*>
|
|
17
|
+
NSArray* SupportedDeviceTypes(id serviceContext);
|
|
18
|
+
|
|
19
|
+
// -[SimServiceContext supportedRuntimes] -> NSArray<SimRuntime*>
|
|
20
|
+
NSArray* SupportedRuntimes(id serviceContext);
|
|
21
|
+
|
|
22
|
+
// -[SimServiceContext supportedDeviceTypesByIdentifier] -> NSDictionary<NSString*, SimDeviceType*>
|
|
23
|
+
NSDictionary* SupportedDeviceTypesByIdentifier(id serviceContext);
|
|
24
|
+
|
|
25
|
+
// -[SimServiceContext supportedRuntimesByIdentifier] -> NSDictionary<NSString*, SimRuntime*>
|
|
26
|
+
NSDictionary* SupportedRuntimesByIdentifier(id serviceContext);
|
|
27
|
+
|
|
28
|
+
// -[SimDeviceType identifier] / -[SimDeviceType name]
|
|
29
|
+
NSString* DeviceTypeIdentifier(id deviceType);
|
|
30
|
+
NSString* DeviceTypeName(id deviceType);
|
|
31
|
+
|
|
32
|
+
// -[SimRuntime identifier] / -[SimRuntime name] / -[SimRuntime versionString]
|
|
33
|
+
NSString* RuntimeIdentifier(id runtime);
|
|
34
|
+
NSString* RuntimeName(id runtime);
|
|
35
|
+
NSString* RuntimeVersionString(id runtime);
|
|
36
|
+
|
|
37
|
+
// -[SimRuntime root] -> the runtime bundle's `RuntimeRoot` directory, same as a spawned process's
|
|
38
|
+
// own `$SIMULATOR_ROOT`. Used to resolve guest-OS-specific executables (e.g. `launchctl` — see
|
|
39
|
+
// CLAUDE.md) before spawning them, since SimDevice's spawn API takes a literal path, no PATH search.
|
|
40
|
+
NSString* RuntimeRootPath(id runtime);
|
|
41
|
+
|
|
42
|
+
} // namespace coresim
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#include "sim_service_context.h"
|
|
2
|
+
|
|
3
|
+
#import <objc/message.h>
|
|
4
|
+
|
|
5
|
+
#include "objc_runtime.h"
|
|
6
|
+
#include "safe_dispatch.h"
|
|
7
|
+
|
|
8
|
+
namespace coresim {
|
|
9
|
+
|
|
10
|
+
id SharedServiceContext(NSString* developerDir, NSError** error) {
|
|
11
|
+
Class cls = RequireClass("SimServiceContext");
|
|
12
|
+
static const std::string kSelectorName = "sharedServiceContextForDeveloperDir:error:";
|
|
13
|
+
RequireClassSelector(cls, kSelectorName);
|
|
14
|
+
SEL selector = SelectorNamed(kSelectorName);
|
|
15
|
+
return SafeInvoke([&] {
|
|
16
|
+
using Fn = id (*)(Class, SEL, NSString*, NSError**);
|
|
17
|
+
return ((Fn)objc_msgSend)(cls, selector, developerDir, error);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
id DefaultDeviceSet(id serviceContext, NSError** error) {
|
|
22
|
+
static const std::string kSelectorName = "defaultDeviceSetWithError:";
|
|
23
|
+
RequireSelector(serviceContext, kSelectorName);
|
|
24
|
+
SEL selector = SelectorNamed(kSelectorName);
|
|
25
|
+
return SafeInvoke([&] {
|
|
26
|
+
using Fn = id (*)(id, SEL, NSError**);
|
|
27
|
+
return ((Fn)objc_msgSend)(serviceContext, selector, error);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
NSArray* SupportedDeviceTypes(id serviceContext) {
|
|
32
|
+
static const std::string kSelectorName = "supportedDeviceTypes";
|
|
33
|
+
RequireSelector(serviceContext, kSelectorName);
|
|
34
|
+
SEL selector = SelectorNamed(kSelectorName);
|
|
35
|
+
return SafeInvoke([&] {
|
|
36
|
+
using Fn = NSArray* (*)(id, SEL);
|
|
37
|
+
return ((Fn)objc_msgSend)(serviceContext, selector);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
NSArray* SupportedRuntimes(id serviceContext) {
|
|
42
|
+
static const std::string kSelectorName = "supportedRuntimes";
|
|
43
|
+
RequireSelector(serviceContext, kSelectorName);
|
|
44
|
+
SEL selector = SelectorNamed(kSelectorName);
|
|
45
|
+
return SafeInvoke([&] {
|
|
46
|
+
using Fn = NSArray* (*)(id, SEL);
|
|
47
|
+
return ((Fn)objc_msgSend)(serviceContext, selector);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
namespace {
|
|
52
|
+
|
|
53
|
+
// Shared shape for every zero-arg id-returning getter used below (identifier/name/versionString).
|
|
54
|
+
NSString* StringGetter(id target, const std::string& selectorName) {
|
|
55
|
+
RequireSelector(target, selectorName);
|
|
56
|
+
SEL selector = SelectorNamed(selectorName);
|
|
57
|
+
return SafeInvoke([&] {
|
|
58
|
+
using Fn = NSString* (*)(id, SEL);
|
|
59
|
+
return ((Fn)objc_msgSend)(target, selector);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
} // namespace
|
|
64
|
+
|
|
65
|
+
NSDictionary* SupportedDeviceTypesByIdentifier(id serviceContext) {
|
|
66
|
+
static const std::string kSelectorName = "supportedDeviceTypesByIdentifier";
|
|
67
|
+
RequireSelector(serviceContext, kSelectorName);
|
|
68
|
+
SEL selector = SelectorNamed(kSelectorName);
|
|
69
|
+
return SafeInvoke([&] {
|
|
70
|
+
using Fn = NSDictionary* (*)(id, SEL);
|
|
71
|
+
return ((Fn)objc_msgSend)(serviceContext, selector);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
NSDictionary* SupportedRuntimesByIdentifier(id serviceContext) {
|
|
76
|
+
static const std::string kSelectorName = "supportedRuntimesByIdentifier";
|
|
77
|
+
RequireSelector(serviceContext, kSelectorName);
|
|
78
|
+
SEL selector = SelectorNamed(kSelectorName);
|
|
79
|
+
return SafeInvoke([&] {
|
|
80
|
+
using Fn = NSDictionary* (*)(id, SEL);
|
|
81
|
+
return ((Fn)objc_msgSend)(serviceContext, selector);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
NSString* DeviceTypeIdentifier(id deviceType) { return StringGetter(deviceType, "identifier"); }
|
|
86
|
+
NSString* DeviceTypeName(id deviceType) { return StringGetter(deviceType, "name"); }
|
|
87
|
+
NSString* RuntimeIdentifier(id runtime) { return StringGetter(runtime, "identifier"); }
|
|
88
|
+
NSString* RuntimeName(id runtime) { return StringGetter(runtime, "name"); }
|
|
89
|
+
NSString* RuntimeVersionString(id runtime) { return StringGetter(runtime, "versionString"); }
|
|
90
|
+
NSString* RuntimeRootPath(id runtime) { return StringGetter(runtime, "root"); }
|
|
91
|
+
|
|
92
|
+
} // namespace coresim
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#import <Foundation/Foundation.h>
|
|
4
|
+
|
|
5
|
+
namespace coresim {
|
|
6
|
+
|
|
7
|
+
// Grants or revokes a privacy permission for `bundleId` in the simulator's own TCC (privacy)
|
|
8
|
+
// database, located under the device's data directory (see DeviceDataPath, sim_device.h) at
|
|
9
|
+
// `Library/TCC/TCC.db`. `service` is the raw TCC service identifier (e.g. "kTCCServiceCamera"),
|
|
10
|
+
// not a friendly name — callers map friendly names to these before calling in (see
|
|
11
|
+
// commands/permissions.ts). Fails with a descriptive NSError if the device has never been booted
|
|
12
|
+
// (TCC.db doesn't exist yet) or the database can't be written to.
|
|
13
|
+
BOOL SetTCCAccess(NSString* dataPath, NSString* service, NSString* bundleId, BOOL granted, NSError** error);
|
|
14
|
+
|
|
15
|
+
// Resets a previously granted/revoked permission back to its default (unprompted, "unset") state
|
|
16
|
+
// by deleting its row from TCC.db, if one exists.
|
|
17
|
+
BOOL ResetTCCAccess(NSString* dataPath, NSString* service, NSString* bundleId, NSError** error);
|
|
18
|
+
|
|
19
|
+
// Own status codes GetTCCAccess reports through `outStatus` — deliberately not TCC's own raw wire
|
|
20
|
+
// values (which reuse 0 for "denied", not "no row"), so a missing row and an explicit denial are
|
|
21
|
+
// never confusable. `kTCCAuthLimited` only ever occurs for kTCCServicePhotos's "selected photos"
|
|
22
|
+
// grant — SetTCCAccess never writes it, but a row can still carry it if something else did.
|
|
23
|
+
enum TCCAuthStatus {
|
|
24
|
+
kTCCAuthNotDetermined = 0,
|
|
25
|
+
kTCCAuthDenied,
|
|
26
|
+
kTCCAuthGranted,
|
|
27
|
+
kTCCAuthLimited,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Reads TCC.db for the current authorization status of (service, bundleId), translating either
|
|
31
|
+
// schema (see HasAuthValueColumn in the .mm) into the TCCAuthStatus values above. Fails with the
|
|
32
|
+
// same descriptive NSError as SetTCCAccess if the device has never been booted (no TCC.db yet).
|
|
33
|
+
BOOL GetTCCAccess(NSString* dataPath, NSString* service, NSString* bundleId, TCCAuthStatus* outStatus, NSError** error);
|
|
34
|
+
|
|
35
|
+
} // namespace coresim
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#include "tcc_privacy.h"
|
|
2
|
+
|
|
3
|
+
#include <sqlite3.h>
|
|
4
|
+
|
|
5
|
+
#include <cstring>
|
|
6
|
+
#include <string>
|
|
7
|
+
|
|
8
|
+
namespace coresim {
|
|
9
|
+
|
|
10
|
+
namespace {
|
|
11
|
+
|
|
12
|
+
NSString* const kTCCErrorDomain = @"com.appium.coresim.TCCPrivacy";
|
|
13
|
+
|
|
14
|
+
NSError* MakeError(int code, NSString* message) {
|
|
15
|
+
return [NSError errorWithDomain:kTCCErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : message}];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
NSString* TCCDatabasePath(NSString* dataPath) {
|
|
19
|
+
return [dataPath stringByAppendingPathComponent:@"Library/TCC/TCC.db"];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// SQLITE_OPEN_READWRITE only (no _CREATE): fails with SQLITE_CANTOPEN instead of silently
|
|
23
|
+
// creating an empty, schema-less database when the simulator has never been booted.
|
|
24
|
+
sqlite3* OpenDatabase(NSString* dataPath, NSError** error) {
|
|
25
|
+
NSString* path = TCCDatabasePath(dataPath);
|
|
26
|
+
sqlite3* db = nullptr;
|
|
27
|
+
int rc = sqlite3_open_v2(path.UTF8String, &db, SQLITE_OPEN_READWRITE, nullptr);
|
|
28
|
+
if (rc != SQLITE_OK) {
|
|
29
|
+
if (error) {
|
|
30
|
+
*error = MakeError(rc, [NSString stringWithFormat:@"Could not open TCC database at '%@' — the device may "
|
|
31
|
+
@"need to be booted at least once first: %s",
|
|
32
|
+
path, sqlite3_errstr(rc)]);
|
|
33
|
+
}
|
|
34
|
+
if (db) {
|
|
35
|
+
sqlite3_close(db);
|
|
36
|
+
}
|
|
37
|
+
return nullptr;
|
|
38
|
+
}
|
|
39
|
+
// Waits (rather than immediately failing) if the simulator's own tccd process has the database
|
|
40
|
+
// locked, instead of a manual sleep/retry loop.
|
|
41
|
+
sqlite3_busy_timeout(db, 5000);
|
|
42
|
+
return db;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// TCC schema gained `auth_value` (replacing the older boolean `allowed` column) in iOS 14; checked
|
|
46
|
+
// against the actual database rather than an OS version threshold, since the schema version tracks
|
|
47
|
+
// the *simulator's* iOS release, not the host's.
|
|
48
|
+
bool HasAuthValueColumn(sqlite3* db) {
|
|
49
|
+
sqlite3_stmt* stmt = nullptr;
|
|
50
|
+
if (sqlite3_prepare_v2(db, "PRAGMA table_info(access)", -1, &stmt, nullptr) != SQLITE_OK) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
bool found = false;
|
|
54
|
+
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
55
|
+
const unsigned char* name = sqlite3_column_text(stmt, 1);
|
|
56
|
+
if (name && std::strcmp(reinterpret_cast<const char*>(name), "auth_value") == 0) {
|
|
57
|
+
found = true;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
sqlite3_finalize(stmt);
|
|
62
|
+
return found;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
void BindText(sqlite3_stmt* stmt, int index, NSString* value) {
|
|
66
|
+
sqlite3_bind_text(stmt, index, value.UTF8String, -1, SQLITE_TRANSIENT);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Runs one parameterized statement to completion. `bind` receives the prepared statement to bind
|
|
70
|
+
// its own parameters onto.
|
|
71
|
+
template <typename BindFn>
|
|
72
|
+
bool ExecuteStatement(sqlite3* db, const char* sql, BindFn bind, NSError** error) {
|
|
73
|
+
sqlite3_stmt* stmt = nullptr;
|
|
74
|
+
if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
|
|
75
|
+
if (error) {
|
|
76
|
+
*error =
|
|
77
|
+
MakeError(sqlite3_errcode(db),
|
|
78
|
+
[NSString stringWithFormat:@"Failed to prepare TCC database statement: %s", sqlite3_errmsg(db)]);
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
bind(stmt);
|
|
83
|
+
int rc = sqlite3_step(stmt);
|
|
84
|
+
sqlite3_finalize(stmt);
|
|
85
|
+
if (rc != SQLITE_DONE) {
|
|
86
|
+
if (error) {
|
|
87
|
+
*error = MakeError(rc, [NSString stringWithFormat:@"Failed to update TCC database: %s", sqlite3_errmsg(db)]);
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
bool DeleteAccessRow(sqlite3* db, NSString* service, NSString* bundleId, NSError** error) {
|
|
95
|
+
return ExecuteStatement(
|
|
96
|
+
db, "DELETE FROM access WHERE service = ? AND client = ? AND client_type = 0",
|
|
97
|
+
[&](sqlite3_stmt* stmt) {
|
|
98
|
+
BindText(stmt, 1, service);
|
|
99
|
+
BindText(stmt, 2, bundleId);
|
|
100
|
+
},
|
|
101
|
+
error);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// The delete+insert pair below must not be allowed to commit independently — a failure between
|
|
105
|
+
// them would otherwise permanently drop the prior row instead of leaving it unchanged.
|
|
106
|
+
bool BeginTransaction(sqlite3* db, NSError** error) {
|
|
107
|
+
if (sqlite3_exec(db, "BEGIN IMMEDIATE TRANSACTION", nullptr, nullptr, nullptr) == SQLITE_OK) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
if (error) {
|
|
111
|
+
*error = MakeError(sqlite3_errcode(db),
|
|
112
|
+
[NSString stringWithFormat:@"Failed to begin TCC database transaction: %s", sqlite3_errmsg(db)]);
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Returns whether the transaction actually ended up committed. On a COMMIT failure (e.g.
|
|
118
|
+
// SQLITE_BUSY from a concurrent reader — SQLite documents that a busy COMMIT leaves the
|
|
119
|
+
// transaction still active, not rolled back automatically) the transaction is explicitly rolled
|
|
120
|
+
// back here rather than left for sqlite3_close to implicitly discard, so the caller gets an
|
|
121
|
+
// accurate failure instead of the `true` that the statements before COMMIT itself had reported.
|
|
122
|
+
bool CommitOrRollback(sqlite3* db, bool commit, NSError** error) {
|
|
123
|
+
if (commit) {
|
|
124
|
+
if (sqlite3_exec(db, "COMMIT", nullptr, nullptr, nullptr) == SQLITE_OK) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
if (error) {
|
|
128
|
+
*error =
|
|
129
|
+
MakeError(sqlite3_errcode(db),
|
|
130
|
+
[NSString stringWithFormat:@"Failed to commit TCC database transaction: %s", sqlite3_errmsg(db)]);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
sqlite3_exec(db, "ROLLBACK", nullptr, nullptr, nullptr);
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
} // namespace
|
|
138
|
+
|
|
139
|
+
BOOL SetTCCAccess(NSString* dataPath, NSString* service, NSString* bundleId, BOOL granted, NSError** error) {
|
|
140
|
+
sqlite3* db = OpenDatabase(dataPath, error);
|
|
141
|
+
if (!db) {
|
|
142
|
+
return NO;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
BOOL success = BeginTransaction(db, error);
|
|
146
|
+
if (success) {
|
|
147
|
+
success = DeleteAccessRow(db, service, bundleId, error);
|
|
148
|
+
}
|
|
149
|
+
if (success) {
|
|
150
|
+
if (HasAuthValueColumn(db)) {
|
|
151
|
+
// `kTCCServicePhotos` rows use auth_version 2 (supports the "limited" library access
|
|
152
|
+
// introduced alongside it); every other service uses version 1. Not exposing "limited"
|
|
153
|
+
// as a grantable status here — only plain granted/denied, matching grantPermission's BOOL.
|
|
154
|
+
BOOL isPhotos = [service isEqualToString:@"kTCCServicePhotos"];
|
|
155
|
+
success = ExecuteStatement(
|
|
156
|
+
db,
|
|
157
|
+
"INSERT INTO access (service, client, client_type, auth_value, auth_reason, auth_version, flags) "
|
|
158
|
+
"VALUES (?, ?, 0, ?, 2, ?, 0)",
|
|
159
|
+
[&](sqlite3_stmt* stmt) {
|
|
160
|
+
BindText(stmt, 1, service);
|
|
161
|
+
BindText(stmt, 2, bundleId);
|
|
162
|
+
sqlite3_bind_int(stmt, 3, granted ? 2 : 0);
|
|
163
|
+
sqlite3_bind_int(stmt, 4, isPhotos ? 2 : 1);
|
|
164
|
+
},
|
|
165
|
+
error);
|
|
166
|
+
} else {
|
|
167
|
+
success = ExecuteStatement(
|
|
168
|
+
db, "REPLACE INTO access (service, client, client_type, allowed, prompt_count) VALUES (?, ?, 0, ?, 1)",
|
|
169
|
+
[&](sqlite3_stmt* stmt) {
|
|
170
|
+
BindText(stmt, 1, service);
|
|
171
|
+
BindText(stmt, 2, bundleId);
|
|
172
|
+
sqlite3_bind_int(stmt, 3, granted ? 1 : 0);
|
|
173
|
+
},
|
|
174
|
+
error);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
success = CommitOrRollback(db, success, error);
|
|
179
|
+
sqlite3_close(db);
|
|
180
|
+
return success;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
BOOL ResetTCCAccess(NSString* dataPath, NSString* service, NSString* bundleId, NSError** error) {
|
|
184
|
+
sqlite3* db = OpenDatabase(dataPath, error);
|
|
185
|
+
if (!db) {
|
|
186
|
+
return NO;
|
|
187
|
+
}
|
|
188
|
+
BOOL success = DeleteAccessRow(db, service, bundleId, error);
|
|
189
|
+
sqlite3_close(db);
|
|
190
|
+
return success;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
BOOL GetTCCAccess(NSString* dataPath, NSString* service, NSString* bundleId, TCCAuthStatus* outStatus,
|
|
194
|
+
NSError** error) {
|
|
195
|
+
sqlite3* db = OpenDatabase(dataPath, error);
|
|
196
|
+
if (!db) {
|
|
197
|
+
return NO;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
bool hasAuthValue = HasAuthValueColumn(db);
|
|
201
|
+
const char* sql = hasAuthValue ? "SELECT auth_value FROM access WHERE service = ? AND client = ? AND client_type = 0"
|
|
202
|
+
: "SELECT allowed FROM access WHERE service = ? AND client = ? AND client_type = 0";
|
|
203
|
+
sqlite3_stmt* stmt = nullptr;
|
|
204
|
+
if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
|
|
205
|
+
if (error) {
|
|
206
|
+
*error =
|
|
207
|
+
MakeError(sqlite3_errcode(db),
|
|
208
|
+
[NSString stringWithFormat:@"Failed to prepare TCC database statement: %s", sqlite3_errmsg(db)]);
|
|
209
|
+
}
|
|
210
|
+
sqlite3_close(db);
|
|
211
|
+
return NO;
|
|
212
|
+
}
|
|
213
|
+
BindText(stmt, 1, service);
|
|
214
|
+
BindText(stmt, 2, bundleId);
|
|
215
|
+
|
|
216
|
+
BOOL success = YES;
|
|
217
|
+
TCCAuthStatus status = kTCCAuthNotDetermined;
|
|
218
|
+
int rc = sqlite3_step(stmt);
|
|
219
|
+
if (rc == SQLITE_ROW) {
|
|
220
|
+
int raw = sqlite3_column_int(stmt, 0);
|
|
221
|
+
if (hasAuthValue) {
|
|
222
|
+
// 0/2/3 per the schema comment in SetTCCAccess above; any other raw value (a reserved/future
|
|
223
|
+
// one this addon doesn't know about) is reported as not-determined rather than guessed at.
|
|
224
|
+
switch (raw) {
|
|
225
|
+
case 0:
|
|
226
|
+
status = kTCCAuthDenied;
|
|
227
|
+
break;
|
|
228
|
+
case 2:
|
|
229
|
+
status = kTCCAuthGranted;
|
|
230
|
+
break;
|
|
231
|
+
case 3:
|
|
232
|
+
status = kTCCAuthLimited;
|
|
233
|
+
break;
|
|
234
|
+
default:
|
|
235
|
+
status = kTCCAuthNotDetermined;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
status = raw ? kTCCAuthGranted : kTCCAuthDenied;
|
|
240
|
+
}
|
|
241
|
+
} else if (rc != SQLITE_DONE) {
|
|
242
|
+
success = NO;
|
|
243
|
+
if (error) {
|
|
244
|
+
*error = MakeError(rc, [NSString stringWithFormat:@"Failed to read TCC database: %s", sqlite3_errmsg(db)]);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
sqlite3_finalize(stmt);
|
|
248
|
+
sqlite3_close(db);
|
|
249
|
+
|
|
250
|
+
if (success && outStatus) {
|
|
251
|
+
*outStatus = status;
|
|
252
|
+
}
|
|
253
|
+
return success;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
} // namespace coresim
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#import <Foundation/Foundation.h>
|
|
4
|
+
|
|
5
|
+
#include <napi.h>
|
|
6
|
+
|
|
7
|
+
namespace coresim {
|
|
8
|
+
|
|
9
|
+
// Converts a JS value (string/number/boolean/null/undefined/Array/plain Object/Buffer) into the
|
|
10
|
+
// corresponding Foundation object (NSString/NSNumber/NSNull/NSArray/NSDictionary/NSData).
|
|
11
|
+
// Used for option dictionaries (boot/install/uninstall options) and JSON payloads (push).
|
|
12
|
+
NSObject* JsValueToNSObject(Napi::Env env, Napi::Value value);
|
|
13
|
+
|
|
14
|
+
// Converts a Foundation object back into a JS value. Used for propertiesOfApplication/
|
|
15
|
+
// installedApps/getenv results. NSNull -> null; unrecognized types -> their -description string.
|
|
16
|
+
Napi::Value NSObjectToJsValue(Napi::Env env, id object);
|
|
17
|
+
|
|
18
|
+
} // namespace coresim
|