├── .gitignore ├── index.js ├── test.js ├── binding.gyp ├── package.json ├── LICENSE ├── README.md └── src ├── XpcConnection.h └── XpcConnection.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | 16 | build/ 17 | node_modules/ 18 | 19 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var events = require('events'); 2 | 3 | var binding = require('./build/Release/binding.node'); 4 | var XpcConnection = binding.XpcConnection; 5 | 6 | inherits(XpcConnection, events.EventEmitter); 7 | 8 | // extend prototype 9 | function inherits(target, source) { 10 | for (var k in source.prototype) { 11 | target.prototype[k] = source.prototype[k]; 12 | } 13 | } 14 | 15 | module.exports = XpcConnection; 16 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var XpcConnection = require('./index'); 2 | 3 | var bluedService = new XpcConnection('com.apple.blued'); 4 | 5 | 6 | bluedService.on('error', function(message) { 7 | console.log('error: ' + JSON.stringify(message, undefined, 2)); 8 | }); 9 | 10 | 11 | bluedService.on('event', function(event) { 12 | console.log('event: ' + JSON.stringify(event, undefined, 2)); 13 | }); 14 | 15 | bluedService.setup(); 16 | 17 | bluedService.sendMessage({ 18 | kCBMsgId: 1, 19 | kCBMsgArgs: { 20 | kCBMsgArgAlert: 1, 21 | kCBMsgArgName: 'node' 22 | } 23 | }); 24 | 25 | setTimeout(function() { 26 | console.log('done'); 27 | }, 5000); 28 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'binding', 5 | 'conditions': [ 6 | ['OS=="mac"', { 7 | 'sources': [ 8 | 'src/XpcConnection.cpp' 9 | ], 10 | # cflags on OS X are stupid and have to be defined like this 11 | 'defines': [ 12 | '_FILE_OFFSET_BITS=64', 13 | '_LARGEFILE_SOURCE' 14 | ], 15 | 'xcode_settings': { 16 | 'OTHER_CFLAGS': [ 17 | '-Wall', 18 | '-ObjC++' 19 | ] 20 | } 21 | }] 22 | ], 23 | "include_dirs" : [ 24 | "", 19 | "license": "MIT", 20 | "readmeFilename": "README.md", 21 | "engines": { 22 | "node": ">=0.8" 23 | }, 24 | "os": [ 25 | "darwin" 26 | ], 27 | "dependencies": { 28 | "nan": "^2.4.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Sandeep Mistry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-xpc-connection 2 | 3 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/sandeepmistry/node-xpc-connection?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | 6 | Connection binding for node.js 7 | 8 | ## Supported data types 9 | 10 | * int32/uint32 11 | * string 12 | * array 13 | * buffer 14 | * uuid 15 | * object 16 | 17 | ## Example 18 | 19 | ```javascript 20 | var XpcConnection = require('xpc-connection'); 21 | 22 | var xpcConnection = new XpcConnection(''); 23 | 24 | xpcConnection.on('error', function(message) { 25 | ... 26 | }); 27 | 28 | xpcConnection.on('event', function(event) { 29 | ... 30 | }); 31 | 32 | xpcConnection.setup(); 33 | 34 | var mesage = { 35 | ... 36 | }; 37 | 38 | xpcConnection.sendMessage(mesage); 39 | ``` 40 | 41 | ## Build Errors 42 | 43 | Before creating a new issue for build errors, please set your path to the following: 44 | 45 | ```sh 46 | /usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin 47 | ``` 48 | 49 | MacPorts and other similiar tools might be adding an incompatible compiler to your PATH (see issue [#2](https://github.com/sandeepmistry/node-xpc-connection/issues/2)) for more details. 50 | 51 | [![Analytics](https://ga-beacon.appspot.com/UA-56089547-1/sandeepmistry/node-xpc-connection?pixel)](https://github.com/igrigorik/ga-beacon) 52 | 53 | -------------------------------------------------------------------------------- /src/XpcConnection.h: -------------------------------------------------------------------------------- 1 | #ifndef ___XPC_CONNECTION_H___ 2 | #define ___XPC_CONNECTION_H___ 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | class XpcConnection : public node::ObjectWrap { 14 | 15 | public: 16 | static NAN_MODULE_INIT(Init); 17 | 18 | static NAN_METHOD(New); 19 | static NAN_METHOD(Setup); 20 | static NAN_METHOD(SendMessage); 21 | 22 | private: 23 | XpcConnection(std::string serviceName); 24 | ~XpcConnection(); 25 | 26 | static xpc_object_t ValueToXpcObject(v8::Local object); 27 | static xpc_object_t ObjectToXpcObject(v8::Local object); 28 | static xpc_object_t ArrayToXpcObject(v8::Local array); 29 | 30 | static v8::Local XpcObjectToValue(xpc_object_t xpcObject); 31 | static v8::Local XpcDictionaryToObject(xpc_object_t xpcDictionary); 32 | static v8::Local XpcArrayToArray(xpc_object_t xpcArray); 33 | 34 | static void AsyncCallback(uv_async_t* handle); 35 | static void AsyncCloseCallback(uv_async_t* handle); 36 | 37 | void setup(); 38 | void sendMessage(xpc_object_t message); 39 | void queueEvent(xpc_object_t event); 40 | void processEventQueue(); 41 | 42 | private: 43 | std::string serviceName; 44 | dispatch_queue_t dispatchQueue; 45 | xpc_connection_t xpcConnnection; 46 | 47 | Nan::Persistent This; 48 | 49 | uv_async_t* asyncHandle; 50 | uv_mutex_t eventQueueMutex; 51 | std::queue eventQueue; 52 | 53 | static Nan::Persistent constructor_template; 54 | }; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/XpcConnection.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #import 4 | 5 | #include 6 | 7 | #include "XpcConnection.h" 8 | #include 9 | 10 | using namespace v8; 11 | 12 | Nan::Persistent XpcConnection::constructor_template; 13 | 14 | NAN_MODULE_INIT(XpcConnection::Init) { 15 | Nan::HandleScope scope; 16 | 17 | Local tmpl = Nan::New(New); 18 | constructor_template.Reset(tmpl); 19 | 20 | tmpl->InstanceTemplate()->SetInternalFieldCount(1); 21 | tmpl->SetClassName(Nan::New("XpcConnection").ToLocalChecked()); 22 | 23 | Nan::SetPrototypeMethod(tmpl, "setup", Setup); 24 | Nan::SetPrototypeMethod(tmpl, "sendMessage", SendMessage); 25 | 26 | target->Set(Nan::New("XpcConnection").ToLocalChecked(), tmpl->GetFunction()); 27 | } 28 | 29 | XpcConnection::XpcConnection(std::string serviceName) : 30 | node::ObjectWrap(), 31 | serviceName(serviceName) { 32 | 33 | this->asyncHandle = new uv_async_t; 34 | 35 | uv_async_init(uv_default_loop(), this->asyncHandle, (uv_async_cb)XpcConnection::AsyncCallback); 36 | uv_mutex_init(&this->eventQueueMutex); 37 | 38 | this->asyncHandle->data = this; 39 | } 40 | 41 | XpcConnection::~XpcConnection() { 42 | uv_close((uv_handle_t*)this->asyncHandle, (uv_close_cb)XpcConnection::AsyncCloseCallback); 43 | 44 | uv_mutex_destroy(&this->eventQueueMutex); 45 | } 46 | 47 | void XpcConnection::setup() { 48 | this->dispatchQueue = dispatch_queue_create(this->serviceName.c_str(), 0); 49 | this->xpcConnnection = xpc_connection_create_mach_service(this->serviceName.c_str(), this->dispatchQueue, XPC_CONNECTION_MACH_SERVICE_PRIVILEGED); 50 | 51 | xpc_connection_set_event_handler(this->xpcConnnection, ^(xpc_object_t event) { 52 | xpc_retain(event); 53 | this->queueEvent(event); 54 | }); 55 | 56 | xpc_connection_resume(this->xpcConnnection); 57 | } 58 | 59 | void XpcConnection::sendMessage(xpc_object_t message) { 60 | xpc_connection_send_message(this->xpcConnnection, message); 61 | } 62 | 63 | void XpcConnection::queueEvent(xpc_object_t event) { 64 | 65 | uv_mutex_lock(&this->eventQueueMutex); 66 | eventQueue.push(event); 67 | uv_mutex_unlock(&eventQueueMutex); 68 | 69 | uv_async_send(this->asyncHandle); 70 | } 71 | 72 | NAN_METHOD(XpcConnection::New) { 73 | Nan::HandleScope scope; 74 | std::string serviceName = ""; 75 | 76 | if (info.Length() > 0 && info[0]->IsString()) { 77 | Nan::Utf8String arg0(info[0]); 78 | 79 | serviceName = *arg0; 80 | } 81 | 82 | XpcConnection* p = new XpcConnection(serviceName); 83 | p->Wrap(info.This()); 84 | p->This.Reset(info.This()); 85 | info.GetReturnValue().Set(info.This()); 86 | } 87 | 88 | 89 | NAN_METHOD(XpcConnection::Setup) { 90 | Nan::HandleScope scope; 91 | 92 | XpcConnection* p = node::ObjectWrap::Unwrap(info.This()); 93 | 94 | p->setup(); 95 | 96 | info.GetReturnValue().SetUndefined(); 97 | } 98 | 99 | xpc_object_t XpcConnection::ValueToXpcObject(Local value) { 100 | xpc_object_t xpcObject = NULL; 101 | 102 | if (value->IsInt32() || value->IsUint32()) { 103 | xpcObject = xpc_int64_create(value->IntegerValue()); 104 | } else if (value->IsString()) { 105 | Nan::Utf8String valueString(value); 106 | 107 | xpcObject = xpc_string_create(*valueString); 108 | } else if (value->IsArray()) { 109 | Local valueArray = Local::Cast(value); 110 | 111 | xpcObject = XpcConnection::ArrayToXpcObject(valueArray); 112 | } else if (node::Buffer::HasInstance(value)) { 113 | Local valueObject = value->ToObject(); 114 | 115 | if (valueObject->HasRealNamedProperty(Nan::New("isUuid").ToLocalChecked())) { 116 | uuid_t *uuid = (uuid_t *)node::Buffer::Data(valueObject); 117 | 118 | xpcObject = xpc_uuid_create(*uuid); 119 | } else { 120 | xpcObject = xpc_data_create(node::Buffer::Data(valueObject), node::Buffer::Length(valueObject)); 121 | } 122 | } else if (value->IsObject()) { 123 | Local valueObject = value->ToObject(); 124 | 125 | xpcObject = XpcConnection::ObjectToXpcObject(valueObject); 126 | } else { 127 | } 128 | 129 | return xpcObject; 130 | } 131 | 132 | xpc_object_t XpcConnection::ObjectToXpcObject(Local object) { 133 | xpc_object_t xpcObject = xpc_dictionary_create(NULL, NULL, 0); 134 | 135 | Local propertyNames = object->GetPropertyNames(); 136 | 137 | for(uint32_t i = 0; i < propertyNames->Length(); i++) { 138 | Local propertyName = propertyNames->Get(i); 139 | 140 | if (propertyName->IsString()) { 141 | Nan::Utf8String propertyNameString(propertyName); 142 | 143 | Local propertyValue = object->GetRealNamedProperty(propertyName->ToString()); 144 | 145 | xpc_object_t xpcValue = XpcConnection::ValueToXpcObject(propertyValue); 146 | xpc_dictionary_set_value(xpcObject, *propertyNameString, xpcValue); 147 | if (xpcValue) { 148 | xpc_release(xpcValue); 149 | } 150 | } 151 | } 152 | 153 | return xpcObject; 154 | } 155 | 156 | xpc_object_t XpcConnection::ArrayToXpcObject(Local array) { 157 | xpc_object_t xpcArray = xpc_array_create(NULL, 0); 158 | 159 | for(uint32_t i = 0; i < array->Length(); i++) { 160 | Local value = array->Get(i); 161 | 162 | xpc_object_t xpcValue = XpcConnection::ValueToXpcObject(value); 163 | xpc_array_append_value(xpcArray, xpcValue); 164 | if (xpcValue) { 165 | xpc_release(xpcValue); 166 | } 167 | } 168 | 169 | return xpcArray; 170 | } 171 | 172 | Local XpcConnection::XpcObjectToValue(xpc_object_t xpcObject) { 173 | Local value; 174 | 175 | xpc_type_t valueType = xpc_get_type(xpcObject); 176 | 177 | if (valueType == XPC_TYPE_INT64) { 178 | value = Nan::New((int32_t)xpc_int64_get_value(xpcObject)); 179 | } else if(valueType == XPC_TYPE_STRING) { 180 | value = Nan::New(xpc_string_get_string_ptr(xpcObject)).ToLocalChecked(); 181 | } else if(valueType == XPC_TYPE_DICTIONARY) { 182 | value = XpcConnection::XpcDictionaryToObject(xpcObject); 183 | } else if(valueType == XPC_TYPE_ARRAY) { 184 | value = XpcConnection::XpcArrayToArray(xpcObject); 185 | } else if(valueType == XPC_TYPE_DATA) { 186 | value = Nan::CopyBuffer((char *)xpc_data_get_bytes_ptr(xpcObject), xpc_data_get_length(xpcObject)).ToLocalChecked(); 187 | } else if(valueType == XPC_TYPE_UUID) { 188 | value = Nan::CopyBuffer((char *)xpc_uuid_get_bytes(xpcObject), sizeof(uuid_t)).ToLocalChecked(); 189 | } else { 190 | NSLog(@"XpcObjectToValue: Could not convert to value!, %@", xpcObject); 191 | } 192 | 193 | return value; 194 | } 195 | 196 | Local XpcConnection::XpcDictionaryToObject(xpc_object_t xpcDictionary) { 197 | Local object = Nan::New(); 198 | 199 | xpc_dictionary_apply(xpcDictionary, ^bool(const char *key, xpc_object_t value) { 200 | object->Set(Nan::New(key).ToLocalChecked(), XpcConnection::XpcObjectToValue(value)); 201 | 202 | return true; 203 | }); 204 | 205 | return object; 206 | } 207 | 208 | Local XpcConnection::XpcArrayToArray(xpc_object_t xpcArray) { 209 | Local array = Nan::New(); 210 | 211 | xpc_array_apply(xpcArray, ^bool(size_t index, xpc_object_t value) { 212 | array->Set(Nan::New(index), XpcConnection::XpcObjectToValue(value)); 213 | 214 | return true; 215 | }); 216 | 217 | return array; 218 | } 219 | 220 | void XpcConnection::AsyncCallback(uv_async_t* handle) { 221 | XpcConnection *xpcConnnection = (XpcConnection*)handle->data; 222 | 223 | xpcConnnection->processEventQueue(); 224 | } 225 | 226 | void XpcConnection::AsyncCloseCallback(uv_async_t* handle) { 227 | delete handle; 228 | } 229 | 230 | void XpcConnection::processEventQueue() { 231 | uv_mutex_lock(&this->eventQueueMutex); 232 | 233 | Nan::HandleScope scope; 234 | 235 | while (!this->eventQueue.empty()) { 236 | xpc_object_t event = this->eventQueue.front(); 237 | this->eventQueue.pop(); 238 | 239 | xpc_type_t eventType = xpc_get_type(event); 240 | if (eventType == XPC_TYPE_ERROR) { 241 | const char* message = "unknown"; 242 | 243 | if (event == XPC_ERROR_CONNECTION_INTERRUPTED) { 244 | message = "connection interrupted"; 245 | } else if (event == XPC_ERROR_CONNECTION_INVALID) { 246 | message = "connection invalid"; 247 | } 248 | 249 | Local argv[2] = { 250 | Nan::New("error").ToLocalChecked(), 251 | Nan::New(message).ToLocalChecked() 252 | }; 253 | 254 | Nan::MakeCallback(Nan::New(this->This), Nan::New("emit").ToLocalChecked(), 2, argv); 255 | } else if (eventType == XPC_TYPE_DICTIONARY) { 256 | Local eventObject = XpcConnection::XpcDictionaryToObject(event); 257 | 258 | Local argv[2] = { 259 | Nan::New("event").ToLocalChecked(), 260 | eventObject 261 | }; 262 | 263 | Nan::MakeCallback(Nan::New(this->This), Nan::New("emit").ToLocalChecked(), 2, argv); 264 | } 265 | 266 | xpc_release(event); 267 | } 268 | 269 | uv_mutex_unlock(&this->eventQueueMutex); 270 | } 271 | 272 | NAN_METHOD(XpcConnection::SendMessage) { 273 | Nan::HandleScope scope; 274 | XpcConnection* p = node::ObjectWrap::Unwrap(info.This()); 275 | 276 | if (info.Length() > 0) { 277 | Local arg0 = info[0]; 278 | if (arg0->IsObject()) { 279 | Local object = Local::Cast(arg0); 280 | 281 | xpc_object_t message = XpcConnection::ObjectToXpcObject(object); 282 | p->sendMessage(message); 283 | xpc_release(message); 284 | } 285 | } 286 | 287 | info.GetReturnValue().SetUndefined(); 288 | } 289 | 290 | NODE_MODULE(binding, XpcConnection::Init); 291 | --------------------------------------------------------------------------------