Repository: TooTallNate/node-iOS Branch: master Commit: 955e2d23675d Files: 23 Total size: 28.3 KB Directory structure: gitextract_sem97i7t/ ├── .gitignore ├── LICENSE ├── README.md ├── build/ │ ├── .gitignore │ └── default/ │ ├── .gitignore │ └── binding.node ├── index.js ├── package.json ├── src/ │ ├── addressBook-Contact.h │ ├── addressBook-Record.h │ ├── addressBook.cc │ ├── addressBook.h │ ├── binding.cc │ ├── compatibility.h │ ├── graphicServices.cc │ ├── graphicServices.h │ ├── notifications.cc │ ├── notifications.h │ ├── telephony.cc │ └── telephony.h ├── tests/ │ ├── addressBook-test.js │ └── notification-test.js └── wscript ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .lock-wscript node_modules ================================================ FILE: LICENSE ================================================ Copyright (c) 2011 Nathan Rajlich Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ node-iOS ======== ### Native [node][Node] bindings to iOS functionality (vibrate, acceleromoter, geoservices, etc.) This module offers native node binding to Apple iOS functionality, meant for `node` running on an Apple iDevice. It exposes low-level functionality of the device, like vibrate, sending SMS messages, displaying pop-up user notifications, simulating the home and lock buttons, and a lot more planned! Considering the fact that most jailbroken devices won't have a GCC toolchain set up on their device, this repo includes a pre-built version of the module that hopefully will work on the majority of devices. I'm always looking for ideas for additional APIs to add. So if you can think of any functionality that you want exposed to JavaScript that's missing from the API below, then please don't hesitate to open an [Issue](https://github.com/TooTallNate/node-iOS/issues) requesting it! API --- ### createNotification(options[, callback]) -> undefined Creates and displays a pop-up "notification" onto the iDevice. The optional `callback` function will be called after the notification has been dismissed (through user interaction or otherwise cancelled). The most recent call to this function will be the active notification. That is, if there's already an active notification, and this function is called, then the newly created notification will take precedence over any existing notifications. So if you want to display a series of notifications, it's better invoke the next one in the previous notification's callback. ``` javascript iOS.createNotification({ header: "Title", message: "Enter your name..." }, function(err, response) { if (err) throw err; console.log(response); }); ``` The `options` Object accepts the following parameters: * `header` - A String that will be used as the header of the notification. Defaults to `null`. * `message` - A String that will be used as the message body of the notification. Defaults to `null`. * `defaultButton` - The text of the default (primary) button. Defaults to `'OK'`. To disable the default button (notification without any buttons), explicity pass `null` here. * `alternateButton` - The text of the secondary (alternate) button. Defaults to `null` (no second button). * `otherButton` - The text of the third (other) button. Defaults to `null` (no third button). * `timeout` - The timeout in seconds of the notification. Defaults to `0` for no timeout. ### AddressBook There's one global `AddressBook` instance that you may use to interact with the contents of your iDevice's central Address Book database. It's also possible to modify the Address Book by adding, editing or removing contacts or groups. ``` javascript // The singleton AddressBook namespace: iOS.AddressBook; ``` #### AddressBook.getContacts([filter,] callback) Asynchronously retrieves an Array of "Contact" instances from the Address Book database that match the given _filter_. If no _filter_ is given, then **ALL** contacts that are currently in the Address Book will be retrieved. ``` javascript iOS.AddressBook.getContacts(function(err, contacts) { if (err) // Something went wrong console.log(contacts); // [ { // firstName: 'John', // lastName: 'Doe', // numbers: { Mobile: '(555) 555-5555' } }, // ... // ] }); ``` ### vibrate() -> undefined Vibrates the iDevice shortly. This is the same as when a text message or email arrives, etc. On devices that don't vibrate, this function does nothing (no error is thrown). ### device() -> Object Returns an Object containing properties from the current [UIDevice][]. An example: ``` javascript { model: 'iPhone', localizedModel: 'iPhone', name: 'Nathan Rajlich’s iPhone', systemName: 'iPhone OS', systemVersion: '4.3.1', uniqueIdentifier: 'f1dfb3fa9f73fc9ffef4fcf3f61fff6f05ff1afb' } ``` ### sendSMS(number, message) -> Boolean Sends an [SMS][] with the specified `message` String to the specified `number`. Examples: ``` javascript iOS.sendSMS('5555555555', 'this is a text message!'); iOS.sendSMS('555-555-5555', 'another text message!'); iOS.sendSMS('(555) 555-5555', 'and one more?!'); iOS.sendSMS(5555555555, 'you may just use a Number as well'); ``` ### lockScreen() -> undefined Locks the screen of the iDevice. Same effect as pressing the top "Lock" button. ### quitTopApplication() -> undefined Quits the currently visible application, going straight to the Home screen. On a device with multitasking support, the app will still be running in the process list. Same effect as pressing the "Home" button. [Node]: http://nodejs.org [SMS]: http://en.wikipedia.org/wiki/SMS [UIDevice]: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIDevice_Class/Reference/UIDevice.html ================================================ FILE: build/.gitignore ================================================ .wafpickle-7 c4che config.log ================================================ FILE: build/default/.gitignore ================================================ src ================================================ FILE: index.js ================================================ // We can require 'autoreleasepool' and the NSAutoreleasePool for the main // (node's) thread will be automatically taken care of until the process exits require('autoreleasepool'); // Ok, so the 'AddressBook' APIs look in "$HOME/Library/AddressBook" of the current // user. The problem is that "/var/mobile" contains the REAL address book, and // "/var/root" contains no address book, so when 'node' is run as the 'root' user, // the address book bindings fail.... well that is unless we symlink to the real // address book before any JS calls are made... dirty hack but I can't find a // better way to do it (explicity setting the "HOME" var doesn't do it)... var abPath = '/var/'+process.env.USER+'/Library/AddressBook'; var mobilePath = '/var/mobile/Library/AddressBook'; if (abPath !== mobilePath) { var fs = require('fs'); if (!fs.lstatSync(abPath).isSymbolicLink()) { // Backup the original 'AddressBook' dir, just in case... fs.renameSync(abPath, abPath+'.bak'); fs.symlinkSync(mobilePath, abPath); } fs = null; } abPath = mobilePath = null; module.exports = require('./build/default/binding.node'); ================================================ FILE: package.json ================================================ { "author": "Nathan Rajlich (http://tootallnate.net)", "name": "iOS", "description": "Native node bindings to iOS functionality (vibrate, acceleromoter, geoservices, etc.)", "version": "0.0.0", "repository": { "type": "git", "url": "git://github.com/TooTallNate/node-iOS.git" }, "main": "./index.js", "directories": { "lib": "./" }, "engines": { "node": ">= 0.4.0" }, "dependencies": { "autoreleasepool": "0.0.x" }, "devDependencies": {} } ================================================ FILE: src/addressBook-Contact.h ================================================ #import "addressBook-Record.h" class Contact : public Record { public: // Person const char *firstName; const char *middleName; const char *lastName; // Organization const char *organization; const char *jobTitle; const char *department; // Phone Numbers int numNumbers; const char **numbersNames; const char **numbersValues; }; // class Contact ================================================ FILE: src/addressBook-Record.h ================================================ #import #import class Record : public node::ObjectWrap { public: ABRecordID recordId; }; // class Record ================================================ FILE: src/addressBook.cc ================================================ // http://developer.apple.com/library/ios/documentation/AddressBook/Reference/ABAddressBookRef_iPhoneOS/Reference/reference.html // http://developer.apple.com/library/ios/documentation/AddressBook/Reference/ABRecordRef_iPhoneOS/Reference/reference.html // http://developer.apple.com/library/ios/documentation/AddressBook/Reference/ABPersonRef_iPhoneOS/Reference/reference.html // http://developer.apple.com/library/ios/documentation/AddressBook/Reference/ABGroupRef_iPhoneOS/Reference/reference.html #import "addressBook.h" #include using namespace node; using namespace v8; // Set up the exports for AddressBook void AddressBook::Init(v8::Handle target) { HandleScope scope; Local ab = Object::New(); NODE_SET_METHOD(ab, "getContacts", AddressBook::GetContacts); NODE_SET_METHOD(ab, "getGroups", AddressBook::GetGroups); target->Set(String::NewSymbol("AddressBook"), ab); } // 'createNotification' begins the async notification process. The user should pass // an "options" Object and an optional callback function (to examine the results of // the notification). v8::Handle AddressBook::GetContacts(const Arguments& args) { HandleScope scope; // This is the struct that gets passed around EIO struct async_request* ar = (struct async_request*) malloc(sizeof(struct async_request)); // TODO: Add support for the search predicate //Local options = args[0]->ToObject(); // A callback function is optional (though why would you call // this if you didn't want the results?) ar->hasCb = false; int argsLen = args.Length(); if (argsLen >= 1) { Local cb = Local::Cast(args[argsLen-1]); ar->cb = Persistent::New(cb); ar->hasCb = true; } eio_custom(GetContacts_DoRequest, EIO_PRI_DEFAULT, GetContacts_AfterResponse, ar); ev_ref(EV_DEFAULT_UC); return Undefined(); } int GetContacts_DoRequest (eio_req * req) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; struct async_request* ar = (struct async_request*)req->data; ABAddressBookRef addressBook = ABAddressBookCreate(); CFArrayRef people = ABAddressBookCopyArrayOfAllPeople(addressBook); // TODO: Sort by the user's current sort preference by default, or a configurable sort CFIndex count = CFArrayGetCount(people); ar->resultsCount = count; ar->results = new Record *[count]; for (CFIndex i=0; irecordId = ABRecordGetRecordID(pRef); // FirstName NSString* firstNameStr = (NSString *)ABRecordCopyValue(pRef, kABPersonFirstNameProperty); p->firstName = firstNameStr != NULL ? [firstNameStr UTF8String] : NULL; // MiddleName NSString* middleNameStr = (NSString *)ABRecordCopyValue(pRef, kABPersonMiddleNameProperty); p->middleName = middleNameStr != NULL ? [middleNameStr UTF8String] : NULL; // LastName NSString *lastNameStr = (NSString *)ABRecordCopyValue(pRef, kABPersonLastNameProperty); p->lastName = lastNameStr != NULL ? [lastNameStr UTF8String] : NULL; // Organization NSString *organizationStr = (NSString *)ABRecordCopyValue(pRef, kABPersonOrganizationProperty); p->organization = organizationStr != NULL ? [organizationStr UTF8String] : NULL; // JobTitle NSString *jobTitleStr = (NSString *)ABRecordCopyValue(pRef, kABPersonJobTitleProperty); p->jobTitle = jobTitleStr != NULL ? [jobTitleStr UTF8String] : NULL; // Department NSString *departmentStr = (NSString *)ABRecordCopyValue(pRef, kABPersonDepartmentProperty); p->department = departmentStr != NULL ? [departmentStr UTF8String] : NULL; // PhoneNumbers ABMultiValueRef numbers = ABRecordCopyValue(pRef, kABPersonPhoneProperty); p->numNumbers = ABMultiValueGetCount(numbers); p->numbersNames = new const char *[p->numNumbers]; p->numbersValues = new const char *[p->numNumbers]; for (CFIndex j=0; j < p->numNumbers; j++) { NSString *numberName = (NSString *)ABMultiValueCopyLabelAtIndex(numbers, j); NSString *numberValue = (NSString *)ABMultiValueCopyValueAtIndex(numbers, j); if ([numberName hasPrefix:@"_$!<" ]) numberName = [numberName substringFromIndex:4 ]; if ([numberName hasSuffix:@">!$_" ]) numberName = [numberName substringToIndex: [numberName length] - 4]; p->numbersNames[j] = [numberName UTF8String]; p->numbersValues[j] = [numberValue UTF8String]; } ar->results[i] = p; } CFRelease(people); CFRelease(addressBook); [pool drain]; return 0; } int GetContacts_AfterResponse (eio_req * req) { HandleScope scope; ev_unref(EV_DEFAULT_UC); struct async_request* ar = (struct async_request*)req->data; if (ar->hasCb) { // Prepare the callback arguments Local argv[2]; argv[0] = Local::New(Null()); Local resultsArray = Array::New(ar->resultsCount); for (CFIndex i=0; i < ar->resultsCount; i++) { Contact *p = (Contact *)ar->results[i]; // TODO: Instead of Object::New(), replace this with a JavaScript // "Contact" constructor. Local curPerson = Object::New(); //curPerson->Set(String::NewSymbol("_id"), Integer::New(p->recordId)); if (p->firstName != NULL) curPerson->Set(String::NewSymbol("firstName"), String::NewSymbol( p->firstName )); if (p->middleName != NULL) curPerson->Set(String::NewSymbol("middleName"), String::NewSymbol( p->middleName )); if (p->lastName != NULL) curPerson->Set(String::NewSymbol("lastName"), String::NewSymbol( p->lastName )); if (p->organization!= NULL) curPerson->Set(String::NewSymbol("organization"), String::NewSymbol( p->organization )); if (p->jobTitle != NULL) curPerson->Set(String::NewSymbol("jobTitle"), String::NewSymbol( p->jobTitle )); if (p->department != NULL) curPerson->Set(String::NewSymbol("department"), String::NewSymbol( p->department )); // PhoneNumbers Local phoneNumbersObj = Object::New(); for (int j=0; j < p->numNumbers; j++) { phoneNumbersObj->Set(String::NewSymbol(p->numbersNames[j]), String::NewSymbol(p->numbersValues[j])); } curPerson->Set(String::NewSymbol("numbers"), phoneNumbersObj); resultsArray->Set(Integer::New(i), curPerson); delete [] p->numbersNames; delete [] p->numbersValues; delete p; } argv[1] = resultsArray; // Invoke 'le callback TryCatch try_catch; ar->cb->Call(Context::GetCurrent()->Global(), 2, argv); if (try_catch.HasCaught()) { FatalException(try_catch); } ar->cb.Dispose(); } delete [] ar->results; free(ar); return 0; } v8::Handle AddressBook::GetGroups(const Arguments& args) { HandleScope scope; return Undefined(); } ================================================ FILE: src/addressBook.h ================================================ #import #import #import #import #import #import "addressBook-Record.h" #import "addressBook-Contact.h" int GetContacts_DoRequest (eio_req *); int GetContacts_AfterResponse (eio_req *); struct async_request { v8::Persistent cb; bool hasCb; CFIndex resultsCount; // 'results' is an array of pointers to "Record" instances Record **results; }; class AddressBook { public: static void Init(v8::Handle target); static v8::Handle GetContacts(const v8::Arguments& args); static v8::Handle GetGroups(const v8::Arguments& args); static v8::Handle Save(const v8::Arguments& args); }; ================================================ FILE: src/binding.cc ================================================ #import #import #import #import #import "addressBook.h" #import "graphicServices.h" #import "notifications.h" #import "telephony.h" #import "compatibility.h" // ...meh using namespace node; using namespace v8; class Binding { public: static void Init(v8::Handle target) { HandleScope scope; UIDevice *aDevice = [UIDevice currentDevice]; [aDevice beginGeneratingDeviceOrientationNotifications]; [aDevice setBatteryMonitoringEnabled:YES]; NODE_SET_METHOD(target, "vibrate", Vibrate); NODE_SET_METHOD(target, "device", Device); NODE_SET_METHOD(target, "sendSMS", Telephony::SendSMS); } static v8::Handle Vibrate(const Arguments& args) { AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); return Undefined(); } static v8::Handle Device(const Arguments& args) { HandleScope scope; Local result = Object::New(); UIDevice *aDevice = [UIDevice currentDevice]; result->Set(String::NewSymbol("batteryLevel"), Number::New([aDevice batteryLevel])); result->Set(String::NewSymbol("batteryState"), Integer::New([aDevice batteryState])); result->Set(String::NewSymbol("model"), String::NewSymbol([[aDevice model] UTF8String])); result->Set(String::NewSymbol("localizedModel"), String::NewSymbol([[aDevice localizedModel] UTF8String])); result->Set(String::NewSymbol("orientation"), Integer::New([aDevice orientation])); result->Set(String::NewSymbol("name"), String::NewSymbol([[aDevice name] UTF8String])); result->Set(String::NewSymbol("systemName"), String::NewSymbol([[aDevice systemName] UTF8String])); result->Set(String::NewSymbol("systemVersion"), String::NewSymbol([[aDevice systemVersion] UTF8String])); result->Set(String::NewSymbol("uniqueIdentifier"), String::NewSymbol([[aDevice uniqueIdentifier] UTF8String])); return scope.Close(result); } }; extern "C" { static void init (v8::Handle target) { Binding::Init(target); AddressBook::Init(target); GraphicServices::Init(target); Notifications::Init(target); } NODE_MODULE(binding, init); } ================================================ FILE: src/compatibility.h ================================================ #import // Add newer API stuff. This shouldn't be needed, unfortunately // it seems as though the 'gcc' from Cydia for iOS (my version at least) // doesn't support either the 'IPHONEOS_DEPLOYMENT_TARGET' env var nor // the '-miphoneos-version-min' compiler flag... lame... typedef enum { UIDeviceBatteryStateUnknown, UIDeviceBatteryStateUnplugged, // on battery, discharging UIDeviceBatteryStateCharging, // plugged in, less than 100% UIDeviceBatteryStateFull, // plugged in, at 100% } UIDeviceBatteryState; // available in iPhone 3.0 @interface UIDevice () - (void) setOrientation:(UIInterfaceOrientation)orientation; @property(getter=isBatteryMonitoringEnabled) BOOL batteryMonitoringEnabled; @property(readonly) UIDeviceBatteryState batteryState; @property(readonly) float batteryLevel; @end ================================================ FILE: src/graphicServices.cc ================================================ #import "graphicServices.h" using namespace node; using namespace v8; void GraphicServices::Init(v8::Handle target) { HandleScope scope; NODE_SET_METHOD(target, "lockScreen", LockScreen); NODE_SET_METHOD(target, "quitTopApplication", QuitTopApplication); } /* Same effect as pressing the top "Lock" button on your iDevice. No arguments required. */ v8::Handle GraphicServices::LockScreen(const Arguments& args) { HandleScope scope; GSEventLockDevice(); GSEventLockDevice(); return scope.Close(Undefined()); } /* Quits the currently visible App. No arguments required. */ v8::Handle GraphicServices::QuitTopApplication(const Arguments& args) { HandleScope scope; GSEventQuitTopApplication(); return scope.Close(Undefined()); } ================================================ FILE: src/graphicServices.h ================================================ #import #import #import /* Private, undocumented APIs */ //void GSEventLockDevice(); //void GSEventQuitTopApplication(); class GraphicServices { public: static void Init(v8::Handle target); static v8::Handle LockScreen(const v8::Arguments& args); static v8::Handle QuitTopApplication(const v8::Arguments& args); }; ================================================ FILE: src/notifications.cc ================================================ #import "notifications.h" using namespace node; using namespace v8; // Initialize the notification-related exports void Notifications::Init(v8::Handle target) { HandleScope scope; NODE_SET_METHOD(target, "createNotification", Notifications::createNotification); } // From v8's 'shell.cc' const char* ToCString(const v8::String::Utf8Value& value) { return *value ? *value : ""; } // 'createNotification' begins the async notification process. The user should pass // an "options" Object and an optional callback function (to examine the results of // the notification). v8::Handle Notifications::createNotification(const Arguments& args) { HandleScope scope; if (args.Length() < 1) { return ThrowException(Exception::TypeError(String::New("An 'options' Object is required"))); } Local options = args[0]->ToObject(); // This is the struct that gets passed around EIO notification_request* nr = (notification_request*) malloc(sizeof(struct notification_request)); CFOptionFlags flags = 0; flags |= kCFUserNotificationPlainAlertLevel; // 'dict' will contain the processed 'options', so the alert message is formatted the way the user asked CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); // This is always set. It appears to be needed for daemon processes on iOS... CFDictionaryAddValue(dict, kCFUserNotificationAlertTopMostKey, kCFBooleanTrue); if (options->Has(String::NewSymbol("header"))) { String::Utf8Value headerStr(options->Get(String::NewSymbol("header"))); CFDictionaryAddValue(dict, kCFUserNotificationAlertHeaderKey, CFStringCreateWithCString(NULL, ToCString(headerStr), kCFStringEncodingUTF8) ); } if (options->Has(String::NewSymbol("message"))) { String::Utf8Value messageStr(options->Get(String::NewSymbol("message"))); CFDictionaryAddValue(dict, kCFUserNotificationAlertMessageKey, CFStringCreateWithCString(NULL, ToCString(messageStr), kCFStringEncodingUTF8) ); } if (options->Has(String::NewSymbol("defaultButton"))) { Local defaultButtonVal = options->Get(String::NewSymbol("defaultButton")); if (defaultButtonVal->IsNull()) { flags |= kCFUserNotificationNoDefaultButtonFlag; } else { String::Utf8Value firstButtonStr(defaultButtonVal); CFDictionaryAddValue(dict, kCFUserNotificationDefaultButtonTitleKey, CFStringCreateWithCString(NULL, ToCString(firstButtonStr), kCFStringEncodingUTF8) ); } } if (options->Has(String::NewSymbol("alternateButton"))) { String::Utf8Value secondButtonStr(options->Get(String::NewSymbol("alternateButton"))); CFDictionaryAddValue(dict, kCFUserNotificationAlternateButtonTitleKey, CFStringCreateWithCString(NULL, ToCString(secondButtonStr), kCFStringEncodingUTF8) ); } if (options->Has(String::NewSymbol("otherButton"))) { String::Utf8Value thirdButtonStr(options->Get(String::NewSymbol("otherButton"))); CFDictionaryAddValue(dict, kCFUserNotificationOtherButtonTitleKey, CFStringCreateWithCString(NULL, ToCString(thirdButtonStr), kCFStringEncodingUTF8) ); } CFTimeInterval timeout = 0; // 0 = disabled, by default if (options->Has(String::NewSymbol("timeout"))) { timeout = options->Get(String::NewSymbol("timeout"))->Int32Value(); } CFUserNotificationRef notif = CFUserNotificationCreate(NULL, timeout, flags, &nr->error, dict); nr->notif = notif; nr->hasCb = false; if (args.Length() >= 2) { Local cb = Local::Cast(args[1]); nr->cb = Persistent::New(cb); nr->hasCb = true; } eio_custom(CreateNotification_WaitForResponse, EIO_PRI_DEFAULT, CreateNotification_AfterResponse, nr); ev_ref(EV_DEFAULT_UC); return Undefined(); } // This is the function that gets called on the thread pool. It blocks for as long as // the notification is active on the iDevice. int CreateNotification_WaitForResponse (eio_req * req) { struct notification_request * nr = (struct notification_request *)req->data; req->result = CFUserNotificationReceiveResponse(nr->notif, 0, &nr->options); return 0; } // This function gets called on node's main thread after the notification has been // dismissed. This function collects the results of the dismissal into a 'results' // Object that gets passed to the JS callback function. int CreateNotification_AfterResponse (eio_req * req) { HandleScope scope; ev_unref(EV_DEFAULT_UC); struct notification_request * nr = (struct notification_request *)req->data; if (nr->hasCb) { // Prepare the callback arguments Local argv[2]; argv[0] = Local::New(Null()); Local results = Object::New(); results->Set(String::NewSymbol("result"), Integer::New( req->result )); results->Set(String::NewSymbol("error"), Integer::New( nr->error )); results->Set(String::NewSymbol("options"), Integer::New( nr->options )); argv[1] = results; // Invoke 'le callback TryCatch try_catch; nr->cb->Call(Context::GetCurrent()->Global(), 2, argv); if (try_catch.HasCaught()) { FatalException(try_catch); } nr->cb.Dispose(); } free(nr); return 0; } ================================================ FILE: src/notifications.h ================================================ #import #import #import #import #import #import int CreateNotification_WaitForResponse (eio_req *); int CreateNotification_AfterResponse (eio_req *); struct notification_request { CFOptionFlags options; CFUserNotificationRef notif; SInt32 error; v8::Persistent cb; bool hasCb; }; class Notifications { public: static void Init(v8::Handle target); static v8::Handle createNotification(const v8::Arguments& args); }; ================================================ FILE: src/telephony.cc ================================================ #import "telephony.h" using namespace node; using namespace v8; //extern id CTTelephonyCenterGetDefault(); //extern void CTTelephonyCenterAddObserver(void*,id,CFNotificationCallback,NSString*,void*,int); //static void callback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) { // NSLog(@"Name: %@", name); //} //v8::Handle Telephony::Init(const Arguments& args) { // id ct = CTTelephonyCenterGetDefault(); // CTTelephonyCenterAddObserver(ct, NULL, callback, NULL, NULL, CFNotificationSuspensionBehaviorHold); // return Undefined(); //} // Sends an SMS. First argument should be the number, second arg should be the message. // TODO: All support for a 'sent' callback or something... I'm not sure if this is possible... v8::Handle Telephony::SendSMS(const Arguments& args) { HandleScope scope; if (args.Length() != 2) { return ThrowException(Exception::TypeError( String::New("\"sendSMS\" requires two String arguments, a number and the message"))); } NSString* number = [NSString stringWithUTF8String: *String::Utf8Value(args[0]->ToString()) ]; NSString* message = [NSString stringWithUTF8String: *String::Utf8Value(args[1]->ToString()) ]; BOOL success = [[CTMessageCenter sharedMessageCenter] sendSMSWithText:message serviceCenter:nil toAddress:number]; return scope.Close(v8::Boolean::New(success)); } ================================================ FILE: src/telephony.h ================================================ #import #import #import #import #import class Telephony { public: static v8::Handle Init(const v8::Arguments& args); static v8::Handle SendSMS(const v8::Arguments& args); }; ================================================ FILE: tests/addressBook-test.js ================================================ var iOS = require('../'); // Get an Array of all the Contacts in the address book iOS.AddressBook.getContacts(function(err, results) { if (err) throw err; console.log(results); console.log(results.length); }); ================================================ FILE: tests/notification-test.js ================================================ var iOS = require('../'); iOS.createNotification({ message: 'This message will have NO buttons!', defaultButton: null, timeout: 3 }, function(err, res) { console.log(arguments); iOS.createNotification({ header: 'test header', message: 'This is a message!!', defaultButton: 'first', alternateButton: 'second' }, console.log); }); ================================================ FILE: wscript ================================================ def set_options(opt): opt.tool_options("compiler_cxx") def configure(conf): conf.check_tool("compiler_cxx") conf.check_tool("node_addon") def build(bld): bld.env["IPHONEOS_DEPLOYMENT_TARGET"] = "3.0" obj = bld.new_task_gen("cxx", "shlib", "node_addon") obj.cxxflags = ["-g", "-D_FILE_OFFSET_BITS=64", "-D_LARGEFILE_SOURCE", "-Wall", "-ObjC++", "-F/System/Library/PrivateFrameworks"] obj.ldflags = ["-F/System/Library/PrivateFrameworks", "-mmacosx-version-min=10.5"] obj.framework = ['Foundation', 'AddressBook', 'AudioToolbox', 'UIKit', 'CoreFoundation', 'CoreTelephony', 'GraphicsServices'] obj.target = "binding" obj.source = ["src/binding.cc", "src/addressBook.cc", "src/graphicServices.cc", "src/notifications.cc", "src/telephony.cc"]