summaryrefslogtreecommitdiff
path: root/index.mm
blob: 0ee829b360badd545bd20cb9ddb979924cdb4b70 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#include <node.h>
#include <v8.h>
#include <stdio.h>
#include <unistd.h>
#include <uv.h>
#include <string>

#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";
  }
}

bool isMojave() {
    NSOperatingSystemVersion minimumSupportedOSVersion = { .majorVersion = 10, .minorVersion = 14, .patchVersion = 0 };
    return [NSProcessInfo.processInfo isOperatingSystemAtLeastVersion:minimumSupportedOSVersion];
}

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 (isMojave()) {
            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 (isMojave()) {
            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(Local<Object> exports) {
    NODE_SET_METHOD(exports, "getMediaAccessStatus", GetMediaAccessStatus);
    NODE_SET_METHOD(exports, "askForMediaAccess", AskForMediaAccess);
}

NODE_MODULE(mojavepermissions, Init)