initial commit
This commit is contained in:
commit
f3a37f1d38
9
binding.gyp
Executable file
9
binding.gyp
Executable file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "mojave-permissions",
|
||||
"sources": [ "index.mm" ],
|
||||
"libraries": [ "-framework AVFoundation" ]
|
||||
}
|
||||
]
|
||||
}
|
19
index.js
Executable file
19
index.js
Executable file
@ -0,0 +1,19 @@
|
||||
const bin = require('./build/Release/mojave-permissions')
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* @param {String} mediaType
|
||||
* @return {String}
|
||||
*/
|
||||
getMediaAccessStatus(mediaType) {
|
||||
return bin.getMediaAccessStatus(mediaType)
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {String} mediaType
|
||||
* @param {Function} callback
|
||||
*/
|
||||
askForMediaAccess(mediaType, callback) {
|
||||
return bin.askForMediaAccess(mediaType, callback)
|
||||
}
|
||||
}
|
151
index.mm
Normal file
151
index.mm
Normal file
@ -0,0 +1,151 @@
|
||||
#include <node.h>
|
||||
#include <v8.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <uv.h>
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
using namespace v8;
|
||||
|
||||
AVMediaType ParseMediaType(const std::string& media_type) {
|
||||
if (media_type == "camera") {
|
||||
return AVMediaTypeVideo;
|
||||
} else if (media_type == "microphone") {
|
||||
return AVMediaTypeAudio;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
std::string ConvertAuthorizationStatus(AVAuthorizationStatus status) {
|
||||
switch (status) {
|
||||
case AVAuthorizationStatusNotDetermined:
|
||||
return "not-determined";
|
||||
case AVAuthorizationStatusRestricted:
|
||||
return "restricted";
|
||||
case AVAuthorizationStatusDenied:
|
||||
return "denied";
|
||||
case AVAuthorizationStatusAuthorized:
|
||||
return "granted";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
struct Baton {
|
||||
uv_work_t request;
|
||||
Persistent<Function> callback;
|
||||
AVMediaType type;
|
||||
bool hasResponse;
|
||||
bool granted;
|
||||
Baton() : hasResponse(0), granted(0) {}
|
||||
};
|
||||
|
||||
// called by libuv worker in separate thread
|
||||
static void DelayAsync(uv_work_t *req) {
|
||||
Baton *baton = static_cast<Baton *>(req->data);
|
||||
[AVCaptureDevice requestAccessForMediaType:baton->type
|
||||
completionHandler:^(BOOL granted) {
|
||||
baton->granted = granted;
|
||||
baton->hasResponse = true;
|
||||
}];
|
||||
|
||||
while (!baton->hasResponse) {
|
||||
usleep(100000);
|
||||
}
|
||||
}
|
||||
|
||||
// called by libuv in event loop when async function completes
|
||||
static void DelayAsyncAfter(uv_work_t *req,int status) {
|
||||
Isolate * isolate = Isolate::GetCurrent();
|
||||
HandleScope scope(isolate);
|
||||
|
||||
Baton *baton = static_cast<Baton *>(req->data);
|
||||
|
||||
Local<Value> argv[1] = {
|
||||
v8::Boolean::New(isolate, baton->granted)
|
||||
};
|
||||
|
||||
Local<Function>::New(isolate, baton->callback)->Call(isolate->GetCurrentContext()->Global(), 1, argv);
|
||||
baton->callback.Reset();
|
||||
|
||||
delete baton;
|
||||
}
|
||||
|
||||
void AskForMediaAccess(const v8::FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = Isolate::GetCurrent();
|
||||
HandleScope scope(isolate);
|
||||
|
||||
if (!args[0]->IsString()) {
|
||||
isolate->ThrowException(Exception::TypeError(
|
||||
String::NewFromUtf8(isolate, "argument 0 must be string")));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args[1]->IsFunction()) {
|
||||
isolate->ThrowException(Exception::TypeError(
|
||||
String::NewFromUtf8(isolate, "argument 1 must be function")));
|
||||
return;
|
||||
}
|
||||
|
||||
String::Utf8Value mediaTypeValue(args[0]);
|
||||
std::string mediaType(*mediaTypeValue);
|
||||
|
||||
Local<Function> cbFunc = Local<Function>::Cast(args[1]);
|
||||
|
||||
if (auto type = ParseMediaType(mediaType)) {
|
||||
if (@available(macOS 10.14, *)) {
|
||||
Baton *baton = new Baton;
|
||||
baton->type = type;
|
||||
baton->callback.Reset(isolate, cbFunc);
|
||||
baton->request.data = baton;
|
||||
|
||||
// queue the async function to the event loop
|
||||
// the uv default loop is the node.js event loop
|
||||
uv_queue_work(uv_default_loop(), &baton->request, DelayAsync, DelayAsyncAfter);
|
||||
} else {
|
||||
Local<Value> argv[1] = { v8::True(isolate) };
|
||||
cbFunc->Call(isolate->GetCurrentContext()->Global(), 1, argv);
|
||||
}
|
||||
} else {
|
||||
isolate->ThrowException(Exception::TypeError(
|
||||
String::NewFromUtf8(isolate, "invalid media type")));
|
||||
}
|
||||
|
||||
args.GetReturnValue().Set(Undefined(isolate));
|
||||
}
|
||||
|
||||
void GetMediaAccessStatus(const v8::FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = Isolate::GetCurrent();
|
||||
HandleScope scope(isolate);
|
||||
|
||||
if (!args[0]->IsString()) {
|
||||
isolate->ThrowException(Exception::TypeError(
|
||||
String::NewFromUtf8(isolate, "argument 0 must be string")));
|
||||
return;
|
||||
}
|
||||
|
||||
String::Utf8Value mediaTypeValue(args[0]);
|
||||
std::string mediaType(*mediaTypeValue);
|
||||
|
||||
if (auto type = ParseMediaType(mediaType)) {
|
||||
if (@available(macOS 10.14, *)) {
|
||||
args.GetReturnValue().Set(String::NewFromUtf8(isolate, ConvertAuthorizationStatus(
|
||||
[AVCaptureDevice authorizationStatusForMediaType:type]).c_str()));
|
||||
} else {
|
||||
// access always allowed pre-10.14 Mojave
|
||||
args.GetReturnValue().Set(String::NewFromUtf8(isolate, ConvertAuthorizationStatus(AVAuthorizationStatusAuthorized).c_str()));
|
||||
}
|
||||
} else {
|
||||
isolate->ThrowException(Exception::TypeError(
|
||||
String::NewFromUtf8(isolate, "invalid media type")));
|
||||
}
|
||||
}
|
||||
|
||||
void Init(Handle<Object> exports) {
|
||||
NODE_SET_METHOD(exports, "getMediaAccessStatus", GetMediaAccessStatus);
|
||||
NODE_SET_METHOD(exports, "askForMediaAccess", AskForMediaAccess);
|
||||
}
|
||||
|
||||
NODE_MODULE(mojavepermissions, Init)
|
15
package.json
Normal file
15
package.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"description": "",
|
||||
"devDependencies": {},
|
||||
"gypfile": true,
|
||||
"main": "index.js",
|
||||
"name": "mojave-permissions",
|
||||
"optionalDependencies": {},
|
||||
"private": true,
|
||||
"readme": "ERROR: No README data found!",
|
||||
"scripts": {
|
||||
"install": "node-gyp rebuild",
|
||||
"test": "node index.js"
|
||||
},
|
||||
"version": "1.0.0"
|
||||
}
|
12
test.js
Normal file
12
test.js
Normal file
@ -0,0 +1,12 @@
|
||||
const perm = require('./index')
|
||||
|
||||
for (let mediaType of ['camera', 'microphone']) {
|
||||
let access = perm.getMediaAccessStatus(mediaType)
|
||||
if (access == 'not-determined') {
|
||||
perm.askForMediaAccess(mediaType, (granted) => {
|
||||
console.log(mediaType + ' askForMediaAccess result:', granted)
|
||||
})
|
||||
} else {
|
||||
console.log(`${mediaType} access status: ${access}`)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user