initial commit

This commit is contained in:
evgeny 2018-12-25 18:16:30 +03:00
commit f3a37f1d38
5 changed files with 206 additions and 0 deletions

9
binding.gyp Executable file
View File

@ -0,0 +1,9 @@
{
"targets": [
{
"target_name": "mojave-permissions",
"sources": [ "index.mm" ],
"libraries": [ "-framework AVFoundation" ]
}
]
}

19
index.js Executable file
View 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
View 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
View 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
View 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}`)
}
}