├── .gitattributes ├── .gitignore ├── API.md ├── LICENSE ├── README.md ├── lib ├── activitymanager.js ├── message.js ├── method.js ├── service.js └── subscription.js ├── oss-pkg-info.yaml ├── package.json ├── reference ├── helloworld_enyo.js └── helloworld_raw.js ├── sample ├── com.example.activityexample │ ├── activityexample.js │ └── package.json └── com.example.helloworld │ ├── helloclient.js │ ├── helloworld_webos_service.js │ ├── package.json │ └── services.json ├── scripts └── make_template.sh └── service ├── com.example.activityexample.json ├── com.example.activityexample.service ├── com.example.helloclient.json ├── com.example.helloclient.service ├── com.example.helloworld.json ├── com.example.helloworld.service └── com.example.helloworld2.service /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | #Shell scripts - preserve LF line ending, even on Windows 5 | *.sh text eol=lf 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac crap 2 | .DS_Store 3 | 4 | # our built npm package 5 | webos-service*.tgz 6 | 7 | # cygwin crash files 8 | *.stackdump 9 | 10 | # gedit backups 11 | *~ 12 | 13 | #built ares-generate template file 14 | webos-service-config.zip 15 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | {toc} 2 | h2. Purpose 3 | 4 | The webos-service module for Node.js provides an interface to the system bus, wrapped in familiar Node.js idioms. 5 | 6 | h2. How do I get it? 7 | You can get the source via 8 | {{git clone https://github.com/webosose/nodejs-module-webos-service.git}} 9 | 10 | 11 | There are a couple of sample services in the git repository, which may be useful. 12 | 13 | h2. Example 14 | {code:lang=javascript|title=helloworld.js} 15 | // simple service, based on webos-service API 16 | 17 | var Service = require('webos-service'); 18 | 19 | var service = new Service("com.example.helloworld"); 20 | 21 | service.register("hello", function(message) { 22 | message.respond({ 23 | greeting: "Hello, World!" 24 | }); 25 | }); 26 | {code} 27 | This registers a service (luna://com.example.helloworld/hello) on both the public and private buses, which responds to a request with a "Hello, World\!" message. 28 | 29 | h2. API reference 30 | 31 | h3. loading the webos-service library 32 | 33 | *var Service = require("webos-service")* 34 | This loads the webos-service module. The only thing exported from the webos-service module is the Service constructor. 35 | 36 | h3. Service object 37 | 38 | *var service = new Service(busID)* 39 | Creates a Service object, which contains handles for both the public and private buses. 40 | * busId is a string containing the bus address of the service, e.g. "com.example.helloworld" 41 | 42 | *service.activityManager* - the ActivityManager proxy object for this service (see below). 43 | 44 | *service.busId* - the busId used when creating the Service 45 | 46 | *service.call(uri, arguments, function callback(message)\{...})* 47 | This sends a one-shot request to another service. The request is sent on the Private bus if this is a "privileged" service, otherwise, it goes out on the Public bus. 48 | * The {{uri}} parameter is the bus address of the service to send to, for example: luna://com.webos.service.wifi/status 49 | * The {{arguments}} parameter is a JSON-compatible object which is encoded by the library and sent as part of the request 50 | * The {{callback}} function is called with a single parameter, which is a {{Message}} (described below) 51 | 52 | *service.isIdPrivileged(id)* - Checks to see if the given id is a "privileged" sender, e.g. com.lge.*. This could be used to return different responses to queries from system services vs. third-party services. 53 | 54 | *service.register(methodName, \[function request(message)\{...}], \[function cancel(message)\{...}])* 55 | Registers a method for the service, on both the public and private buses. When a request is made for that method, the callback function will be called. The callback gets one argument, which is a {{Message}} object (see below). 56 | * methodName is the name of the method to register. You can group methods in categories by putting a category at the front of the methodName, separated by "/" characters, e.g. 57 | {code} 58 | service.register("/config/setup", function callback(message)\{...}); 59 | {code} 60 | This function returns a {{Method}} object (see below), which emits the {{request}} and {{cancel}} events. If the {{request}} and {{cancel}} arguments are provided, they're bound to the "request" and "cancel" events, respectively. 61 | 62 | *service.subscribe(uri, arguments)* 63 | This sends a subscription request to another service, for services that support it. The request is sent on the Private bus if this is a "privileged" service, otherwise, it goes out on the Public bus. The {{uri}} and {{arguments}} are the same as {{call}}, above. This function returns a {{Subscription}} object (see below), which emits events as responses come back from the other service. 64 | 65 | *service.subscriptions* - all of the {{Message}}s currently subscribed to this service, indexed by their LS2 uniqueToken. 66 | 67 | h5. Obscure API 68 | *Service.privateHandle* - Provides access to the private bus handle. 69 | *Service.publicHandle* - Provides access to the public bus handle. 70 | You could use these to send a message on the Public bus, even if you're a privileged service, for example. 71 | *service.registerPrivate(methodName, \[function request(message)\{...}], \[function cancel(message)\{...}])* 72 | Registers a method for the service, on *only* the private bus. This would be useful for methods that would only be called by system services or applications. 73 | 74 | h3. Message object 75 | {{Message}} objects are used to represent messages coming in from other services or applications. 76 | 77 | *message.cancel()* - this sends a "cancel" message to the sender, which indicates no more responses will be coming. This is normally only used for subscribed calls. Single-shot calls do not require a "cancel" response. 78 | 79 | *message.category* - the category of the method that was called. 80 | 81 | *message.isSubscription* - this is set to {{true}} if {{"subscribe": true}} is included in the payload, which indicates the sender wants to subscribe. 82 | 83 | *message.method* - the name of the method that was called. This is the part of the service URI that's before the method name. 84 | 85 | *message.payload* - the payload (or arguments) sent with the message. This is JSON-parsed from the string sent with the message. If the parsing fails (some services *do not* properly JSON-encode their responses), then the response text will be in the "responseText" property of the payload. 86 | 87 | *message.respond(payload)* - this sends a response to the requester. 88 | * The {{payload}} object will be JSON-encoded before being sent. Every response should include a "returnValue" property, which is set to {{true}} for success replies, and {{false}} for errors. 89 | 90 | *message.sender* - the applicationID or busID of the message sender, depending on whether it was sent from an app or another service 91 | 92 | *message.uniqueToken* - a string which uniquely identifies this request. It is guaranteed to be unique within any one run of the service. If you need to track service requests, this is probably the token you want to use. This corresponds to the Native LS2 API "uniqueToken" 93 | 94 | h5. Obscure API 95 | *message.ls2Message* - a copy of the "palmbus" Message object. This might be useful for accessing API that hasn't been made available in webos-service yet. 96 | 97 | *message.token* - this is a "friendly" version of the message token serial number that can be used to correlate requests to ls-monitor output. In particular, this token is *not* guaranteed to be unique to a particular request (different clients might use the same token). This is generally only useful in debugging output. 98 | 99 | h3. Method object 100 | *var method = service.register(methodName\[, requestCallback]\[, cancelCallback])* 101 | Creates a {{Method}} object, which is an EventEmitter that emits the "request" and "cancel" methods. 102 | 103 | *event "request"* - this event is emitted when a message is sent to the registered method. The even handler is passed the {{Message}} object corresponding to the request. 104 | 105 | *event "cancel"* - this event is emitted when a sender of a message indicates that it is no longer interested in receiving replies. This event is only emitted for subscribed messages. 106 | 107 | h3. Subscription object 108 | *var subscription = service.subscribe(uri, payload)* 109 | this creates a Subscription object, representing a request to {{uri}}. 110 | * The {{uri}} is the complete URI for the service method, e.g. luna://com.webos.service.wifi/status 111 | * The {{payload}} is an object, which is JSON-encoded before sending 112 | 113 | *subscription.on("response", function callback(message)\{...})* 114 | A {{response}} event is sent every time the other service sends a response. The callback receives a single {{Message}} parameter. 115 | 116 | *subscription.on("cancel", function callback(message)\{...})* 117 | The {{cancel}} response indicates that the other service has canceled that subscription. It is a good idea to remove any references to the subscription at that time, so that the message can be garbage-collected. 118 | 119 | *subscription.cancel()* 120 | Sends a "cancel" message to the other service., indicating that you no longer wish to receive responses. 121 | 122 | h3. ActivityManager object 123 | *var activityManager = service.activityManager;* 124 | This object represents a proxy to the ActivityManager LS2 service (com.webos.service.activitymanager), and also provides a timer that's used to control a service's lifetime. 125 | 126 | *activityManager.create("name", callback)* 127 | Creates an activity with a reasonable set of default properties, for immediate execution by the service. The service will not exit due to timeout while an activity is active. Note that the webos-service library creates a new activity for each request that comes in, so you don't need to create your own for simple cases. Your callback will be called with the new activity as an argument. 128 | 129 | *activityManager.create(activitySpecification, callback)* 130 | Creates an activity with the given activity specification. This is useful when you want to create an activity with a callback, to cause your service to be executed at a later time. See [How to use ActivityManager for dynamic services] for more information. Your callback will be called with the new activity as an argument. 131 | 132 | *activityManager.complete(activity, options, callback)* 133 | This "completes" the activity. The webos-service library automatically completes the activity associated with a particular message when you call respond() on it. 134 | 135 | You might call "complete" explicitly if you wanted to specify options to the complete operation, for example, to restart the activity, or change the triggers or schedule associated with the activity. 136 | 137 | h2. Handling Subscriptions 138 | 139 | h3. Client-side subscriptions 140 | On the client (requester) side, subscriptions are handled by the {{Subscription}} object. In most cases, you merely need to do something like this: 141 | {code} 142 | var Service = require('webos-service'); 143 | var service = new Service("com.webos.service.test"); 144 | var sub = service.subscribe("luna://com.webos.service.connection/status", {"subscribe": true}); 145 | sub.on("response", function(message) { 146 | //do something with the subscription 147 | }); 148 | {code} 149 | 150 | h3. Service-side subscriptions 151 | The library offers some built-in support for services that would like to support subscriptions. If a {{Method}} has a "cancel" handler, then it's considered to be subscribable. The library automatically tracks subscription requests, registering them with LS2 to ensure that "cancel" events are delivered properly. 152 | 153 | Your "request" handler for the {{Method}} should check the {{Message}}'s "isSubscription" property, to determine whether a subscription has been requested. In most cases, you'll want to add subscribed messages to an array or Object hash, in order to keep track of them when it's time to update them later. 154 | 155 | Here's a partial example, from the helloworld sample in the source 156 | {code} 157 | var subscriptions = {}; 158 | // EventEmitter-based API for subscriptions 159 | // note that the previous examples are actually using this API as well, they're 160 | // just setting a "request" handler implicitly 161 | var heartbeat = service.register("heartbeat2"); 162 | heartbeat.on("request", function(message) { 163 | message.respond({event: "beat"}); // initial response 164 | if (message.isSubscription) { 165 | subscriptions[message.uniqueToken] = message; //add message to "subscriptions" 166 | if (!interval) { 167 | createInterval(); // launch some async process 168 | } 169 | } 170 | }); 171 | heartbeat.on("cancel", function(message) { 172 | delete subscriptions[message.uniqueToken]; // remove message from "subscriptions" 173 | var keys = Object.keys(subscriptions); 174 | if (keys.length === 0) { // count the remaining subscriptions 175 | console.log("no more subscriptions, canceling interval"); 176 | clearInterval(interval); // don't do work in the background when there are no subscriptions 177 | interval = undefined; 178 | } 179 | }); 180 | {code} 181 | 182 | h3. Frequently asked Questions (FAQ) and troubleshooting hints 183 | See this article on [JavaScript services FAQ & troubleshooting hints] 184 | 185 | (this document is also available in the source repository at ssh://gpro.lgsvl.com/webos-pro/nodejs-module-webos-service) 186 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | nodejs-module-webos-service 2 | =========================== 3 | 4 | Summary 5 | ------- 6 | A low-level library for node.js services on Webos 7 | 8 | Description 9 | ----------- 10 | 11 | ###Objectives 12 | 1. A minimalist library for services on WebOS 13 | 2. Not a framework, just helper functions - don't lock developers into a single app design 14 | 15 | ###Things in mojoservice that need re-evaluation 16 | * Dependency between JSON-formatted services.json, and the names of controllers - why have the same information in two places, and does it make sense to have "magic" connection between service names and class names? 17 | * Command/idle timeouts - was a significant source of confusion 18 | * Futures - some people loved them, some hated them 19 | * Using mojoloader instead of require() 20 | 21 | ###Things we definitely want to keep/expand from Mojoservice 22 | * "info" and "quit" commands - great for testing, probably don't need to have them be __info and __quit, though 23 | * (optional?) support of schemas for input validation 24 | 25 | ###Service bootstrap process 26 | A service application is launched by the service bus when a request comes in that targets that service, and the service is not already running. 27 | 28 | The service hub determines which services are available by parsing the files in: 29 | /usr/share/dbus-1/services 30 | /var/palm/ls2/services/pub 31 | /usr/share/dbus-1/system_services 32 | /var/palm/ls2/services/prv 33 | 34 | These files are windows-style INI files, which should have a .service extension. The contents should look like this: 35 | [D-BUS Service] 36 | Name=com.webos.service.contacts 37 | Exec=/usr/bin/run-js-service -n /usr/palm/services/com.webos.service.contacts 38 | 39 | ###Roles files 40 | Every service needs roles files, located in: 41 | 42 | /usr/share/ls2/roles/pub 43 | /usr/share/ls2/roles/prv 44 | /var/palm/ls2/roles/pub 45 | /var/palm/ls2/roles/prv 46 | 47 | for public and private bus configurations. Note that even services that don't need private bus access usually still have to provide a private role file - the system-provided services and apps will try to contact the service over the private bus. 48 | 49 | These are JSON-formatted files, and will contain a set of "roles", which are the bus addresses listened on, and permissions for those roles (which other bus clients can make requests to this service) 50 | 51 | ###Weird behavior/bugs 52 | ls-control scan-services will tell hubd to look for new .services files to identify launchable services 53 | making a request via luna-send -i then closing luna-send doesn't seem to close the subscription. 54 | 55 | luna-bus will reject requests for a service if it doesn't have a .service file, even if a program has successfully-registered a handle. The error message in this case is "service does not exist". 56 | 57 | ###Development setup 58 | On Windows: 59 | Use OpenSSH for Windows (http://sourceforge.net/projects/sshwindows/) I tried PuTTY, but couldn't get pscp to work reliably, which is important for automating the build/update process. 60 | 61 | ###Installing NPM on the emulator 62 | Until we get a more-recent version of Node.js on the emulator, you'll need to install NPM manually. Copy the npm-install.sh file to the emulator via scp, and run it to install NPM. 63 | 64 | # Copyright and License Information 65 | 66 | Copyright (c) 2012-2018 LG Electronics, Inc. 67 | 68 | Licensed under the Apache License, Version 2.0 (the "License"); 69 | you may not use this file except in compliance with the License. 70 | You may obtain a copy of the License at 71 | 72 | http://www.apache.org/licenses/LICENSE-2.0 73 | 74 | Unless required by applicable law or agreed to in writing, software 75 | distributed under the License is distributed on an "AS IS" BASIS, 76 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 77 | See the License for the specific language governing permissions and 78 | limitations under the License. 79 | 80 | SPDX-License-Identifier: Apache-2.0 81 | -------------------------------------------------------------------------------- /lib/activitymanager.js: -------------------------------------------------------------------------------- 1 | //activitymanager.js 2 | //activity tracking and ActivityManager integration 3 | var pmlog = require("pmloglib"); 4 | var console = new pmlog.Console("webos-service"); 5 | 6 | var activityManagerURI = "luna://com.webos.service.activitymanager"; 7 | var instance; 8 | function ActivityManager(service, idleTimeout) { 9 | // Create only one ApplicationManager, so we only have one timeout 10 | //TODO: refactor this so that we aren't using a "Singleton" ActivityManager instance 11 | if (instance) { 12 | return instance; 13 | } 14 | this._idleTimer = null; 15 | this._activities = {}; 16 | this._counter=1; 17 | if (process.argv.indexOf("--disable-timeouts") !== -1) { 18 | this.exitOnTimeout = false; 19 | } else { 20 | this.exitOnTimeout = true; 21 | } 22 | this.useDummyActivity = -1 !== process.argv.indexOf("--disable-activity-creation"); 23 | this._dummyActivityId = 1; 24 | this.idleTimeout = idleTimeout || 5; //seconds 25 | this._startTimer(); 26 | this.service = service; 27 | if (!global.unified_service) { 28 | instance = this; 29 | } 30 | } 31 | 32 | ActivityManager.prototype._add = function(id, activity) { 33 | this._stopTimer(); 34 | if (this._activities[id]) { 35 | console.warn('Activity "'+id+'" already started'); 36 | } else { 37 | this._activities[id] = activity; 38 | if (global.unified_service) 39 | global.unified_service.increaseActivity(); 40 | } 41 | }; 42 | 43 | ActivityManager.prototype._remove = function(id) { 44 | if (!this._activities[id]) { 45 | console.warn('Activity "'+id+'" not started'); 46 | } else { 47 | this._activities[id].cancel(); 48 | delete this._activities[id]; 49 | if (global.unified_service) 50 | global.unified_service.decreaseActivity(); 51 | } 52 | if (Object.keys(this._activities).length == 0) { 53 | this._startTimer(); 54 | if (global.unified_service) 55 | global.unified_service.enterIdle(); 56 | } 57 | }; 58 | 59 | ActivityManager.prototype.create = function(spec, callback) { 60 | if (typeof spec === "string") { 61 | if (this.useDummyActivity) 62 | return this._createDummy(spec, callback); 63 | else 64 | return this._createInternal(spec, callback); 65 | } else { 66 | return this._createActual(spec, callback); 67 | } 68 | }; 69 | 70 | ActivityManager.prototype._createDummy = function(jobId, callback) { 71 | // _createInternal step 72 | jobId += this._counter++; 73 | console.log("Creating dummy activity for " + jobId); 74 | var activitySpec = { 75 | isDummyActivity: true, 76 | activity: { name: jobId } 77 | }; 78 | 79 | // _createActual step 80 | var activityId = "dummy_" + this._dummyActivityId++; 81 | activitySpec.activityId = activityId; 82 | console.log("ActivityId = " + activityId); 83 | this._add(activityId, { cancel: function() {} }); 84 | if (callback) { 85 | callback(activitySpec); 86 | } 87 | }; 88 | 89 | ActivityManager.prototype._createInternal = function(jobId, callback) { 90 | jobId += this._counter++; 91 | console.log("Creating activity for "+jobId); 92 | var activitySpec = { 93 | "activity": { 94 | "name": jobId, //this needs to be unique, per service 95 | "description": "activity created for "+jobId, //required 96 | "type": { 97 | "foreground": true, // can use foreground or background, or set individual properties (see Activity Specification below, for details) 98 | "persist": false, // this activity will be persistent across reboots 99 | "explicit": true // this activity *must* be completed or cancelled explicitly, or it will be re-launched until it does 100 | } 101 | }, 102 | "start": true, // start the activity immediately when its requirements (if any) are met 103 | "replace": true, // if an activity with the same name already exists, replace it 104 | "subscribe": true // if "subscribe" is false, the activity needs to be adopted immediately, or it gets canceled 105 | }; 106 | return this._createActual(activitySpec, callback); 107 | }; 108 | 109 | ActivityManager.prototype._createActual = function(activitySpec, callback) { 110 | var activityId; 111 | var activityManager = this; 112 | if (activitySpec.subscribe) { 113 | var createSub = this.service.subscribe("luna://com.webos.service.activitymanager/create", activitySpec); 114 | createSub.on("response", function(reply) { 115 | var payload = reply.payload; 116 | if (!payload.returnValue) { 117 | console.error("Activity creation failed: "+payload.errorText); 118 | if (callback) { 119 | callback(payload); 120 | } 121 | } else { 122 | if (!reply.payload.event) { 123 | activityId = reply.payload.activityId; 124 | activitySpec.activityId = activityId; 125 | console.log("ActivityId = "+activityId); 126 | activityManager._add(activityId, createSub); 127 | if (callback) { 128 | callback(activitySpec); 129 | } 130 | } else if (reply.payload.event == "complete") { 131 | console.log("activity complete, cancelling subscription"); 132 | createSub.cancel(); 133 | } 134 | } 135 | }); 136 | } else { 137 | // not a subscription - just create the activity 138 | this.service.call("luna://com.webos.service.activitymanager/create", activitySpec, function(response) { 139 | var payload = response.payload; 140 | if (!payload.returnValue) { 141 | console.error("Activity creation failed: "+payload.errorText); 142 | if (callback) { 143 | callback(payload); 144 | } 145 | } else { 146 | if (callback) { 147 | activityId = payload.activityId; 148 | activitySpec.activityId = activityId; 149 | callback(activitySpec); 150 | } 151 | } 152 | }); 153 | } 154 | }; 155 | 156 | ActivityManager.prototype.adopt = function(activity, callback) { 157 | var id = activity.activityId; 158 | var adoptSub = this.service.subscribe(activityManagerURI+"/adopt", {activityId: id, subscribe: true, wait: false}); 159 | var activityManager = this; 160 | adoptSub.on("response", function(message) { 161 | if (message.payload.adopted) { 162 | console.log("adopted " + id); 163 | activityManager._add(id, adoptSub); 164 | if (callback) { 165 | callback(message); 166 | } 167 | } else if (!message.payload.returnValue) { 168 | console.error("Adopt of "+id+" failed"); 169 | //TODO: maybe do something different if this fails? call an errorback? 170 | if (callback) { 171 | callback(message); 172 | } 173 | } 174 | }); 175 | }; 176 | 177 | ActivityManager.prototype.complete = function(activity, options, callback) { 178 | console.log("completing: "+JSON.stringify(activity)); 179 | // make "options" optional 180 | if (typeof options === "function" && callback === undefined) { 181 | callback = options; 182 | } 183 | if (options === undefined) { 184 | options = {}; 185 | } 186 | // only complete an activity once 187 | if (activity.completed) { 188 | return false; 189 | } else { 190 | activity.completed = true; 191 | } 192 | var activityId = activity.activityId; 193 | 194 | if (activity.isDummyActivity) { 195 | this._remove(activityId); 196 | if (callback) { 197 | callback(activity); 198 | } 199 | return; 200 | } 201 | 202 | var activityManager = this; 203 | var params = {activityId: activityId}; 204 | Object.keys(options).forEach(function(key) { 205 | params[key] = options[key]; 206 | }); 207 | console.log("completing with params: "+JSON.stringify(params)); 208 | this.service.call(activityManagerURI+"/complete", params, function(message) { 209 | if (!message.payload.returnValue) { 210 | console.error("Failed to complete "+activityId+", error: "+message.payload.errorText); 211 | } 212 | activityManager._remove(activityId); 213 | if (callback) { 214 | callback(activity); 215 | } 216 | }); 217 | }; 218 | 219 | ActivityManager.prototype._startTimer = function() { 220 | if (this._idleTimer) { 221 | console.log("idle timer already started, ignoring"); 222 | return; 223 | } 224 | var that = this; 225 | this._idleTimer = setTimeout(function() { 226 | if (that.exitOnTimeout) { 227 | console.log("no active activities, exiting"); 228 | if (!global.unified_service) { 229 | process.exit(0); 230 | } else { 231 | that.service.cleanupUnified(); 232 | } 233 | } else { 234 | console.log("no active activities, would exit, but timeout is disabled"); 235 | } 236 | }, this.idleTimeout * 1000); 237 | }; 238 | 239 | ActivityManager.prototype._stopTimer = function() { 240 | if (!this._idleTimer) { 241 | console.log("idle timer already stopped, ignoring"); 242 | return; 243 | } 244 | clearTimeout(this._idleTimer); 245 | this._idleTimer = null; 246 | }; 247 | 248 | module.exports = ActivityManager; 249 | -------------------------------------------------------------------------------- /lib/message.js: -------------------------------------------------------------------------------- 1 | //message.js - a wrapper for the palmbus message type 2 | var pmlog = require("pmloglib"); 3 | var console = new pmlog.Console("webos-service"); 4 | 5 | /* Message constructor 6 | * takes two arguments, a palmbus message object, and the handle it was received on 7 | * third argument is the activityManager instance to use 8 | */ 9 | function Message(message, handle, activityManager, service) { 10 | this.category = message.category(); 11 | this.method = message.method(); 12 | this.isSubscription = message.isSubscription(); 13 | this.uniqueToken = message.uniqueToken(); 14 | this.token = message.token(); 15 | try { 16 | this.payload = JSON.parse(message.payload()); 17 | } catch (e) { 18 | console.error("badly-formatted message payload"); 19 | console.error("error: " + e); 20 | if (e.stack) console.error(e.stack); 21 | console.error("payload: " + message.payload()); 22 | this.payload = {badPayload: message.payload()}; 23 | } 24 | if (message.applicationID() !== "") { 25 | // split WAM's 'pid' off of the appId 26 | this.sender = message.applicationID().split(" ")[0]; 27 | } else { 28 | this.sender = message.senderServiceName(); 29 | } 30 | this.ls2Message = message; 31 | this.handle = handle; 32 | this.activityManager = activityManager; 33 | this.service = service; 34 | } 35 | 36 | //* respond to a message, with a JSON-compatible object 37 | Message.prototype.respond = function(response) { 38 | var returnValue = true; 39 | if (typeof response !== "object" || response === null) { 40 | throw("response must be an object"); 41 | } 42 | var r = {}; 43 | for (var k in response) { 44 | r[k] = response[k]; 45 | } 46 | if (r.returnValue === undefined) { 47 | if (r.errorCode || r.errorText) { 48 | r.returnValue = false; 49 | if (!r.errorCode) { 50 | r.errorCode = -1; 51 | } 52 | if (!r.errorText) { 53 | r.errorText = "no error message provided"; 54 | } 55 | } else { 56 | r.returnValue = true; 57 | } 58 | } 59 | if (! this.ls2Message.respond(JSON.stringify(r))) { 60 | console.error("ERROR: ls2Message.respond() returned false"); 61 | returnValue = false; 62 | } 63 | if (!this.isSubscription) { 64 | this.activityManager.complete(this.activity, function(activityManagerRespnse) { 65 | console.log("completion callback"); 66 | }); 67 | } 68 | return returnValue; 69 | }; 70 | 71 | //* inform this client that no more responses are coming 72 | Message.prototype.cancel = function(response) { 73 | if (this.isSubscription) { 74 | this.service.cancelSubscription(this.handle, this.ls2Message); 75 | var r = {}; 76 | if (typeof response === "object" && response !== null) { 77 | for (var k in response) { 78 | r[k] = response[k]; 79 | } 80 | } 81 | else if (response !== undefined) { 82 | throw("response must be an object"); 83 | } 84 | r.subscribed = false; 85 | this.respond(r); 86 | } 87 | }; 88 | 89 | module.exports = Message; 90 | -------------------------------------------------------------------------------- /lib/method.js: -------------------------------------------------------------------------------- 1 | //method.js 2 | var pmlog = require("pmloglib"); 3 | var console = new pmlog.Console("webos-service"); 4 | 5 | var events = require('events'); 6 | var util = require("util"); 7 | /* Method constructor 8 | * takes two arguments 9 | * methodName is the name of the method registered 10 | * description is a JSON-format description of the method 11 | */ 12 | function Method(methodName, description) { 13 | events.EventEmitter.call(this); 14 | this.name = methodName; 15 | this.description = description; 16 | } 17 | util.inherits(Method, events.EventEmitter); 18 | module.exports = Method; 19 | -------------------------------------------------------------------------------- /lib/service.js: -------------------------------------------------------------------------------- 1 | // service.js: webos-service main module 2 | // Copyright (c) 2013-2018 LG Electronics, Inc. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // SPDX-License-Identifier: Apache-2.0 17 | /*jshint esversion: 6 */ 18 | var pmlog = require("pmloglib"); 19 | var console = new pmlog.Console("webos-service"); 20 | 21 | // requires 22 | var palmbus = require("palmbus"); 23 | var path = require("path"); 24 | var fs = require("fs"); 25 | 26 | // library modules 27 | var Message = require("./message"); 28 | var Subscription = require("./subscription"); 29 | var Method = require("./method"); 30 | var ActivityManager = require("./activitymanager"); 31 | 32 | // Proxy path.exists to detect all places of usage of the deprecated and already removed function 33 | var deprecated_path_exists = path.exists; 34 | path.exists=function (path, existsCb) { 35 | fs.stat(path, function (err, stats) { 36 | existsCb(!err); 37 | }); 38 | console.error('Deprecated path.exists used. Emulated with fs.stat. Please, update ASAP.'); 39 | }; 40 | 41 | /* Service constructor 42 | * first parameter is the bus ID to register 43 | * second, optional parameter for the ActivityManager instance to use 44 | * third parameter is an "options" object, which supports the following keys: 45 | * noBuiltinMethods: set to "true" to prevent registering built-in methods (info & quit) 46 | * idleTimer: recommended idle time in seconds to exit 47 | */ 48 | function Service(busId, activityManager, options) { 49 | var self = this; 50 | process.nextTick(function() { 51 | // I'm just here to ensure the main event loop doesn't exit before polling (see GF-13126) 52 | }); 53 | this.busId = busId; 54 | 55 | try { 56 | this.handle = new palmbus.Handle(busId); 57 | this.handle.addListener("request", function(message) { 58 | self._dispatch(self.handle, message); 59 | }); 60 | this.handle.addListener("cancel", function(message) { 61 | self.cancelSubscription(self.handle, message); 62 | }); 63 | this.sendingHandle = this.handle; 64 | this.useACG = true; 65 | } 66 | catch(ex) { 67 | console.error('Deprecated security model is used. Please update configuration for ACG security model.'); 68 | this.privateHandle = new palmbus.Handle(busId, false); 69 | this.privateHandle.addListener("request", function(message) { 70 | self._dispatch(self.privateHandle, message); 71 | }); 72 | this.privateHandle.addListener("cancel", function(message) { 73 | self.cancelSubscription(self.privateHandle, message); 74 | }); 75 | this.publicHandle = new palmbus.Handle(busId, true); 76 | this.publicHandle.addListener("request", function(message) { 77 | self._dispatch(self.publicHandle, message); 78 | }); 79 | this.publicHandle.addListener("cancel", function(message) { 80 | self.cancelSubscription(self.publicHandle, message); 81 | }); 82 | 83 | if (this.idIsPrivileged(this.busId)) { 84 | this.sendingHandle = this.privateHandle; 85 | } else { 86 | this.sendingHandle = this.publicHandle; 87 | } 88 | this.useACG = false; 89 | } 90 | 91 | this.methods = {}; 92 | this.handlers = {}; 93 | this.cancelHandlers = {}; 94 | this.subscriptions = {}; 95 | this.hasPublicMethods = false; 96 | if (global.unified_service) { 97 | this.__serviceMainUnified = global.unified_service.serviceMain; 98 | } 99 | // set the "ps" process name 100 | if (!global.unified_service) { 101 | if (process.setName) { 102 | // Palm-modified Node.js 0.4 103 | process.setName(busId); 104 | } else { 105 | process.title = busId; 106 | } 107 | } 108 | if (options && options.noBuiltinMethods) { 109 | this.noBuiltinMethods = true; 110 | } else { 111 | this._registerBuiltInMethods(true); 112 | } 113 | if (options && options.idleTimer) { 114 | this.idleTimer = options.idleTimer; 115 | } else { 116 | this.idleTimer = 5; 117 | } 118 | if (activityManager) { 119 | this.activityManager = activityManager; 120 | } else { 121 | this.activityManager = new ActivityManager(this, this.idleTimer); 122 | } 123 | } 124 | 125 | Service.prototype.cleanupUnified = function() { 126 | if (global.unified_service && !this.cleanupUnifiedDone) { 127 | this.cleanupUnifiedDone = true; 128 | if (this.useACG) { 129 | this.handle.removeAllListeners(); 130 | this.handle.unregister(); 131 | } 132 | else { 133 | this.privateHandle.removeAllListeners(); 134 | this.privateHandle.unregister(); 135 | this.publicHandle.removeAllListeners(); 136 | this.publicHandle.unregister(); 137 | } 138 | this.activityManager._stopTimer(); 139 | global.unified_service.unrequire(this.__serviceMainUnified); 140 | } 141 | }; 142 | 143 | Service.prototype._dispatch = function(handle, ls2Message) { 144 | var message = new Message(ls2Message, handle, this.activityManager, this); 145 | var category = message.category; 146 | var method = message.method; 147 | //console.log("category:", category, " method:", method); 148 | if (this.methods[category]) { 149 | var methodObject = this.methods[category][method]; 150 | if (methodObject) { 151 | if (message.isSubscription) { 152 | var cancelListeners = methodObject.listeners("cancel"); 153 | if (cancelListeners.length === 0) { 154 | console.warn('a client attempted to add a subscription for '+method+' which has no "cancel" handler'); 155 | console.warn('ignoring...'); 156 | } else { 157 | this.subscriptions[message.uniqueToken] = message; 158 | handle.subscriptionAdd(message.uniqueToken, message.ls2Message); 159 | } 160 | } 161 | if (message.payload.$activity) { 162 | var activity = message.payload.$activity; 163 | console.log("adopting activity" + JSON.stringify(activity)); 164 | this.activityManager.adopt(activity, function(response) { 165 | if (response.payload.returnValue) { 166 | console.log("setting message.activity to " + JSON.stringify(activity)); 167 | message.activity = activity; 168 | methodObject.emit("request", message); 169 | } else { 170 | console.error("Activity Adopt failed: "+response.payload.errorText); 171 | } 172 | }); 173 | } else { 174 | console.log("creating activity"); 175 | this.activityManager.create(method, function(activity) { 176 | console.log("created activity "+JSON.stringify(activity)); 177 | message.activity = activity; 178 | methodObject.emit("request", message); 179 | }); 180 | } 181 | } else { 182 | console.error("No method for category ", category, ", method ", method); 183 | } 184 | } else { 185 | console.error("No methods for category ", category); 186 | } 187 | }; 188 | 189 | /* @protected 190 | * handler to remove subscriptions 191 | */ 192 | Service.prototype.cancelSubscription = function(handle, ls2Message) { 193 | var id = ls2Message.uniqueToken(); 194 | if (this.subscriptions[id]) { 195 | var message = this.subscriptions[id]; 196 | var category = message.category; 197 | var method = message.method; 198 | 199 | //console.log("Cancelling subscription "+id); 200 | delete this.subscriptions[id]; 201 | this.activityManager.complete(message.activity, function(activity) { 202 | //TODO: Do something here, maybe delay "cancel" event? 203 | }); 204 | if (this.methods[category]) { 205 | var methodObject = this.methods[category][method]; 206 | if (methodObject) { 207 | methodObject.emit("cancel", message); 208 | } 209 | } 210 | } else { 211 | console.log("Attempt to cancel unknown subscription "+id); 212 | } 213 | }; 214 | 215 | /* Register a method on both buses 216 | * callback is called with a Message object 217 | */ 218 | Service.prototype.register = function(name, requestCallback, cancelCallback, description) { 219 | if (!this.hasPublicMethods) { 220 | this.hasPublicMethods = true; 221 | if (!this.noBuiltinMethods) { 222 | this._registerBuiltInMethods(false); 223 | } 224 | } 225 | 226 | if (this.useACG) { 227 | return this._register(undefined, name, requestCallback, cancelCallback, description); 228 | } 229 | else { 230 | return this._register(true, name, requestCallback, cancelCallback, description) && 231 | this._register(false, name, requestCallback, cancelCallback, description); 232 | } 233 | }; 234 | 235 | /* Register a method on the private bus ONLY 236 | * callback is called with a Message object 237 | */ 238 | Service.prototype.registerPrivate = function(name, requestCallback, cancelCallback, description) { 239 | return this._register(true, name, requestCallback, cancelCallback, description); 240 | }; 241 | 242 | Service.prototype._register = function(privateBus, name, requestCallback, cancelCallback, description) { 243 | //console.log("registering method:'"+name+"'"); 244 | var category; 245 | var methodName; 246 | var lastSlash = name.lastIndexOf("/"); 247 | if (lastSlash == -1) { 248 | category = "/"; 249 | methodName = name; 250 | } else { 251 | category = name.slice(0, lastSlash) || "/"; 252 | methodName = name.slice(lastSlash+1); 253 | } 254 | 255 | if (category.charAt(0) != "/") { 256 | console.warn("method category "+category+" should start with '/', adding one for you..."); 257 | category = "/" + category; 258 | } 259 | if (!this.methods[category]) { 260 | this.methods[category] = {}; 261 | } 262 | var method = this.methods[category][methodName]; 263 | // if method doesn't exist, create it 264 | if (!method) { 265 | method = new Method(methodName, description); 266 | this.methods[category][methodName] = method; 267 | if (requestCallback) { 268 | method.on("request", requestCallback); 269 | } 270 | if (cancelCallback) { 271 | method.on("cancel", cancelCallback); 272 | } 273 | } 274 | if (this.useACG) { 275 | this.handle.registerMethod(category, methodName); 276 | method.privateBusOnly = false; 277 | } 278 | else { 279 | if (privateBus) { 280 | this.privateHandle.registerMethod(category, methodName); 281 | method.privateBusOnly = true; // Note: this means you have to register private, then public 282 | } else { 283 | this.publicHandle.registerMethod(category, methodName); 284 | method.privateBusOnly = false; 285 | } 286 | } 287 | return method; 288 | }; 289 | 290 | Service.prototype._registerBuiltInMethods = function(privateBus) { 291 | this._register(privateBus, "quit", this.quit.bind(this), undefined, { 292 | description: "quits the service", 293 | arguments: "[none]" 294 | }); 295 | this._register(privateBus, "info", this.info.bind(this), undefined, { 296 | description: "returns information about the service", 297 | arguments: "[none]" 298 | }); 299 | }; 300 | 301 | /* Determine if a particular id is "privileged", and allowed to send 302 | * on the private bus 303 | */ 304 | Service.prototype.idIsPrivileged = function(id) { 305 | var specials = [ 306 | "com.palm.", 307 | "com.lge.", 308 | 'com.webos.' 309 | ]; 310 | for (var i=0; i < specials.length; i++) { 311 | // only matches if the id *starts* with the special domains 312 | if (id.indexOf(specials[i]) === 0) { 313 | return true; 314 | } 315 | } 316 | return false; 317 | }; 318 | 319 | /* Call a service on the bus 320 | * The args parameter is a JSON-compatible object 321 | * The callback gets passed a Message object 322 | */ 323 | Service.prototype.call = function(...params) { 324 | if (params.length !== 3 && params.length !== 4) 325 | throw("wrong arguments"); 326 | 327 | var uri = params[0]; 328 | var args = params[1]; 329 | var callback = (params.length === 3) ? params[2] : params[3]; 330 | var sessionId = (params.length === 4) ? params[2] : 'undefined'; 331 | 332 | if (typeof args !== "object") 333 | throw("payload must be an object"); 334 | 335 | var handle = this.sendingHandle; 336 | var request; 337 | if (sessionId === 'undefined') 338 | request = handle.call(uri, JSON.stringify(args)); 339 | else 340 | request = handle.callSession(uri, JSON.stringify(args), sessionId); 341 | 342 | request.addListener("response", function(msg) { 343 | if (callback) { 344 | callback(new Message(msg, handle)); 345 | } 346 | }); 347 | }; 348 | 349 | /* Subscribe to a service on the bus 350 | * The args parameter is a JSON-compatible object 351 | * Returns a Subscription object which raises events when responses come in 352 | */ 353 | Service.prototype.subscribe = function(...params) { 354 | if (params.length !== 2 && params.length !== 3) 355 | throw("wrong arguments"); 356 | 357 | var uri = params[0]; 358 | var args = params[1]; 359 | var sessionId = (params.length === 3) ? params[2] : 'undefined'; 360 | 361 | if (typeof args !== "object") { 362 | throw("args must be an object"); 363 | } 364 | 365 | return new Subscription(this.sendingHandle, uri, args, sessionId); 366 | }; 367 | 368 | /* Quit the service 369 | * 370 | */ 371 | Service.prototype.quit = function(message) { 372 | // If there are no public methods, don't allow "quit" from public bus 373 | if (!this.hasPublicMethods) { 374 | if (message.handle != this.privateHandle) { 375 | message.respond({returnValue: false, errorText: 'The "quit" method is not supported on the Public bus'}); 376 | return; 377 | } 378 | } 379 | message.respond({status: "quitting"}); 380 | var that = this; 381 | // Why is there a 50ms timeout? Because the actual sending of the response is 382 | // asynchronous, and process.nextTick() was not *quite* enough time 383 | // It would be nice to have a callback on the JS side for "message sent" 384 | setTimeout(function() { 385 | if (!global.unified_service) { 386 | process.exit(0); 387 | } else { 388 | that.cleanupUnified(); 389 | } 390 | }, 50); 391 | }; 392 | 393 | /* Provide some usage information for the service 394 | * 395 | */ 396 | Service.prototype.info = function(message) { 397 | var info = {}; 398 | var categories = Object.keys(this.methods); 399 | for (var i=0; i < categories.length; i++) { 400 | var category = categories[i]; 401 | //console.log("Category: "+category); 402 | var methods = Object.keys(this.methods[category]); 403 | for (var j=0; j < methods.length; j++) { 404 | var methodName = methods[j]; 405 | var method = this.methods[category][methodName]; 406 | //console.log("Method: "+method); 407 | var methodPath = path.join(category, methodName); 408 | //console.log("method:" + methodPath+" privateBusOnly: " + method.privateBusOnly); 409 | if (!method.privateBusOnly || (message.handle == this.privateHandle) ) { 410 | info[methodPath] = method.description||{}; 411 | } 412 | } 413 | } 414 | message.respond({commands: info}); 415 | }; 416 | 417 | module.exports = Service; 418 | -------------------------------------------------------------------------------- /lib/subscription.js: -------------------------------------------------------------------------------- 1 | //subscription.js 2 | var pmlog = require("pmloglib"); 3 | var console = new pmlog.Console("webos-service"); 4 | 5 | var events = require("events"); 6 | var util = require("util"); 7 | var Message = require("./message"); 8 | //* Subscription is an EventEmitter wrapper for subscribed LS2 calls 9 | function Subscription(handle, uri, args, sessionId) { 10 | events.EventEmitter.call(this); 11 | this.uri = uri; 12 | this.args = args; 13 | this.handle = handle; 14 | if (sessionId == 'undefined') 15 | this.request = handle.subscribe(uri, JSON.stringify(args)); 16 | else 17 | this.request = handle.subscribeSession(uri, JSON.stringify(args), sessionId); 18 | 19 | var self = this; 20 | this.request.addListener("response", function(msg) { 21 | var payload; 22 | try { 23 | payload = JSON.parse(msg.payload()); 24 | } 25 | catch (e) { 26 | console.error("badly-formatted message payload"); 27 | console.error("error: " + e); 28 | if (e.stack) console.error(e.stack); 29 | console.error("payload: " + msg.payload()); 30 | payload = { 31 | subscribed: false, 32 | returnValue: false, 33 | errorText: msg.payload(), 34 | badPayload: msg.payload() 35 | }; 36 | } 37 | 38 | if (payload.subscribed === false) { 39 | self.request.cancel(); 40 | self.emit("cancel", new Message(msg, handle)); 41 | } 42 | else { 43 | self.emit("response", new Message(msg, handle)); 44 | } 45 | }); 46 | this.request.addListener("cancel", function(msg) { 47 | self.emit("cancel", new Message(msg, handle)); 48 | }); 49 | } 50 | util.inherits(Subscription, events.EventEmitter); 51 | 52 | //* stop receiving responses 53 | Subscription.prototype.cancel = function() { 54 | this.request.cancel(); 55 | }; 56 | 57 | module.exports = Subscription; 58 | -------------------------------------------------------------------------------- /oss-pkg-info.yaml: -------------------------------------------------------------------------------- 1 | Open Source Software Package: 2 | - name: webosOSE-nodejs-module-webos-service 3 | source: git://github.com/webosose/nodejs-module-webos-service 4 | license: 5 | - Apache-2.0 6 | - BSD 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webos-service", 3 | "version": "0.0.1", 4 | "description": "Services library for Open WebOS", 5 | "main": "lib/service.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/mbessey/webos-service.git" 12 | }, 13 | "keywords": [ 14 | "webos", 15 | "service", 16 | "services", 17 | "openwebos" 18 | ], 19 | "author": "LG Electronics", 20 | "license": "Apache-2.0", 21 | "readmeFilename": "README.md" 22 | } 23 | -------------------------------------------------------------------------------- /reference/helloworld_enyo.js: -------------------------------------------------------------------------------- 1 | // helloworld_enyo.js 2 | // Helloworld service, in Enyo 3 | 4 | enyo.kind({ 5 | name: "HelloWorld", 6 | address: "com.example.helloworld", 7 | kind: "Service", 8 | components: [ 9 | ], 10 | events: { 11 | } 12 | }); 13 | 14 | new HelloWorld().start(); 15 | -------------------------------------------------------------------------------- /reference/helloworld_raw.js: -------------------------------------------------------------------------------- 1 | 2 | var path = require("path"); 3 | var fs = require("fs"); 4 | //load the palmbus extension 5 | var palmbus = require("palmbus"); 6 | console.log("starting"); 7 | function load_roles(name) { 8 | var rolesDirs = ["/usr/share/ls2", "/var/palm/ls2"]; 9 | var i; 10 | var dir; 11 | 12 | for (i=0; i < rolesDirs.length; i++) { 13 | dir = rolesDirs[i]; 14 | if (fs.existsSync(dir+"/roles/pub/"+name+".json")) { 15 | break; 16 | } 17 | } 18 | console.log("pushing roles files from ", dir); 19 | var publicRolePath = dir+"/roles/pub/"+name+".json"; 20 | var privateRolePath = dir+"/roles/prv/"+name+".json"; 21 | //console.info("registering public"); 22 | var publicHandle = new palmbus.Handle(null, true); 23 | //console.info("pushing public role "+publicRolePath); 24 | publicHandle.pushRole(publicRolePath); 25 | //console.info("registering private"); 26 | var privateHandle = new palmbus.Handle(null, false); 27 | //console.info("pushing private role "+privateRolePath); 28 | privateHandle.pushRole(privateRolePath); 29 | } 30 | load_roles("com.example.helloworld"); 31 | console.log("Registering public & private handles"); 32 | var publicHandle = new palmbus.Handle("com.example.helloworld", true); 33 | var privateHandle = new palmbus.Handle("com.example.helloworld", false); 34 | var handles = [publicHandle, privateHandle]; 35 | 36 | function makePrintRequestHandler(handle) { 37 | return function(message) { 38 | var category = message.category(); 39 | var method = message.method(); 40 | var token = message.uniqueToken(); 41 | console.log("Request received - category %s, method %s, token %s, token %s", category, method, token); 42 | }; 43 | } 44 | 45 | var x = 0; 46 | var intervals = {}; 47 | 48 | function makeHandler(handle) { 49 | return function(message) { 50 | request(handle, message); 51 | }; 52 | } 53 | 54 | function request(handle, msg) { 55 | var token = msg.uniqueToken(); 56 | var category = msg.category(); 57 | var method = msg.method(); 58 | if (msg.isSubscription()) { 59 | handle.subscriptionAdd(token, msg); 60 | console.log("adding subscription for "+category+"/"+method+" token "+token); 61 | var interval = setInterval(function(){ 62 | msg.respond(JSON.stringify({beat: x++})); 63 | }, 1000); 64 | intervals[token] = interval; 65 | } 66 | } 67 | 68 | function cancel(msg) { 69 | var token = msg.uniqueToken(); 70 | console.log("cancelling "+token); 71 | clearInterval(intervals[token]); 72 | intervals[token] = undefined; 73 | } 74 | 75 | console.log("Registering methods"); 76 | for (var i=0;i <2; i++) { 77 | var h = handles[i]; 78 | h.addListener("request", makePrintRequestHandler(h)); 79 | h.registerMethod("/","heartbeat"); 80 | h.addListener("request", makeHandler(h)); 81 | h.addListener("cancel", cancel); 82 | } 83 | -------------------------------------------------------------------------------- /sample/com.example.activityexample/activityexample.js: -------------------------------------------------------------------------------- 1 | //activitysamples.js 2 | var Service = require("webos-service"); 3 | var PmLog = require("pmloglib"); 4 | 5 | var context = new PmLog.Context("com.example.activityexample"); 6 | var service = new Service("com.example.activityexample"); 7 | var activityManagerUri = "luna://com.webos.service.activitymanager"; 8 | 9 | service.register("createAlarm", function(message) { 10 | var activitySpec = { 11 | "activity": { 12 | "name": "Alarm", //this needs to be unique, per service 13 | "description": "periodic activity for activityexample", //required 14 | "type": { 15 | "foreground": true, // can use foreground or background, or set individual properties (see Activity Specification below, for details) 16 | }, 17 | "persist": false, // this activity will be persistent across reboots 18 | "explicit": true, // this activity *must* be completed or cancelled explicitly, or it will be re-launched until it does 19 | "callback": { // what service to call when this activity starts 20 | "method": "luna://com.example.activityexample/alarmFired", // URI to service 21 | "params": { // parameters/arguments to pass to service 22 | fired: true 23 | } 24 | }, 25 | "schedule": { 26 | 27 | } 28 | }, 29 | "start": true, // start the activity immediately when its requirements (if any) are met 30 | "replace": true, // if an activity with the same name already exists, replace it 31 | "subscribe": false // if "subscribe" is false, the activity needs to be adopted immediately, or it gets canceled 32 | }; 33 | service.activityManager.create(activitySpec, function(activity) { 34 | var activityId = activity.activityId; 35 | console.log("ActivityId = "+activityId); 36 | message.respond({msg: "Created activity "+activityId}); 37 | }); 38 | }); 39 | 40 | service.register("createTimer", function(message) { 41 | var activitySpec = { 42 | "activity": { 43 | "name": "Timer", //this needs to be unique, per service 44 | "description": "periodic activity for activityexample", //required 45 | "type": { 46 | "foreground": true, // can use foreground or background, or set individual properties (see Activity Specification below, for details) 47 | }, 48 | "persist": false, // this activity will be persistent across reboots 49 | "explicit": true, // this activity *must* be completed or cancelled explicitly, or it will be re-launched until it does 50 | "callback": { // what service to call when this activity starts 51 | "method": "luna://com.example.activityexample/timerFired", // URI to service 52 | "params": { // parameters/arguments to pass to service 53 | fired: true 54 | } 55 | }, 56 | "schedule": { 57 | interval: "5m" 58 | } 59 | }, 60 | "start": true, // start the activity immediately when its requirements (if any) are met 61 | "replace": true, // if an activity with the same name already exists, replace it 62 | "subscribe": false // if "subscribe" is false, the activity needs to be adopted immediately, or it gets canceled 63 | }; 64 | service.activityManager.create(activitySpec, function(activity) { 65 | var activityId = activity.activityId; 66 | console.log("ActivityId = "+activityId); 67 | message.respond({msg: "Created activity "+activityId}); 68 | }); 69 | }); 70 | 71 | service.register("cancelActivity", function(message) { 72 | if (!message.payload.activityId) { 73 | message.respond({returnValue: false, errorText: "activityId must be specified"}); 74 | return; 75 | } 76 | var args = { 77 | activityId: message.payload.activityId, 78 | restart: false 79 | }; 80 | service.activityManager.complete(args); 81 | message.respond({status: "cancelling"}); 82 | }); 83 | 84 | service.register("timerFired", function(message) { 85 | if (!message.payload.$activity) { 86 | message.respond({returnValue: false, errorText: "$activity must be specified"}); 87 | return; 88 | } 89 | var activity = message.payload.$activity; 90 | console.log("timer fired, resetting"); 91 | var options = { 92 | restart: true 93 | }; 94 | service.activityManager.complete(activity, options, function(reply) { 95 | console.log("activityId "+activity.activityId+" completed/restarted."); 96 | message.respond({message: "activityId "+activity.activityId+" completed, and restarted."}); 97 | }); 98 | }); 99 | 100 | service.register("alarmFired", function(message) { 101 | if (!message.payload.$activity) { 102 | message.respond({returnValue: false, errorText: "$activity must be specified"}); 103 | return; 104 | } 105 | var activity = message.payload.$activity; 106 | var args = { 107 | restart: false 108 | }; 109 | service.activityManager.complete(activity, options, function(message) { 110 | console.log("activityId "+activityId+" completed."); 111 | message.respond({message: "activityId "+activityId+" completed."}); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /sample/com.example.activityexample/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.example.activityexample", 3 | "version": "1.0.0", 4 | "description": "ActivityManager examples for webos-service", 5 | "main": "activityexample.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Mark Bessey", 10 | "license": "Apache" 11 | } 12 | -------------------------------------------------------------------------------- /sample/com.example.helloworld/helloclient.js: -------------------------------------------------------------------------------- 1 | // helloclient.js 2 | // Subscribe & cancel subscription to helloworld's heartbeat service 3 | var Service = require('webos-service'); 4 | 5 | // Register com.example.helloworld, on both buses 6 | var service = new Service("com.example.helloclient"); 7 | service.activityManager.create("KeepAlive"); // no callback, so we'll never be able to stop this activity... 8 | console.log("simple call"); 9 | service.call("luna://com.example.helloworld/hello", {}, function(message) { 10 | console.log("message payload: " + JSON.stringify(message.payload)); 11 | service.call("luna://com.example.helloworld/increment", {}, function(message) { 12 | var cnt = message.payload.count; 13 | console.log("count: "+cnt); 14 | service.call("luna://com.example.helloworld/getCount", {}, function(message) { 15 | if (message.payload.count != cnt) { 16 | throw("bad count "+message.payload.count); 17 | } 18 | console.log("count verified"); 19 | var count = 0; 20 | var max = 10; 21 | console.log("subscription - cancel after "+max+" responses"); 22 | var sub = service.subscribe("luna://com.example.helloworld/heartbeat", {subscribe: true}); 23 | sub.addListener("response", function(msg) { 24 | console.log(JSON.stringify(msg.payload)); 25 | if (++count >= max) { 26 | console.log("cancelling subscription"); 27 | // sub.cancel(); 28 | service.call("luna://com.example.helloworld/cancel_subscriptions", {}); 29 | console.log("testing not including a callback - CHECK LOG FOR ERRORS"); 30 | service.call("luna://com.example.helloworld/hello", {}); 31 | setTimeout(function(){ 32 | console.log("exiting..."); 33 | process.exit(0); 34 | }, 100); 35 | } 36 | }); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /sample/com.example.helloworld/helloworld_webos_service.js: -------------------------------------------------------------------------------- 1 | // helloworld_webos_service.js 2 | // simple service, based on low-level Palmbus API 3 | 4 | var Service = require('webos-service'); 5 | 6 | // Register com.example.helloworld, on both buses 7 | var service = new Service("com.example.helloworld"); 8 | var greeting = "Hello, World!"; 9 | 10 | // a method that always returns the same value 11 | service.register("hello", function(message) { 12 | console.log("In hello callback"); 13 | message.respond({ 14 | returnValue: true, 15 | message: greeting 16 | }); 17 | }); 18 | 19 | // set some state in the service 20 | service.register("config/setGreeting", function(message) { 21 | console.log("In setGreeting callback"); 22 | if (message.payload.greeting) { 23 | greeting = message.payload.greeting; 24 | } else { 25 | message.respond({ 26 | returnValue: false, 27 | errorText: "argument 'greeting' is required", 28 | errorCode: 1 29 | }); 30 | } 31 | message.respond({ 32 | returnValue: true, 33 | greeting: greeting 34 | }); 35 | }); 36 | 37 | // call another service 38 | service.register("locale", function(message) { 39 | console.log("locale callback"); 40 | service.call("luna://com.webos.settingsservice/getSystemSettings", {"key":"localeInfo"}, function(m2) { 41 | var response = "You appear to have your locale set to: " + m2.payload.settings.localeInfo.locales.UI; 42 | console.log(response); 43 | message.respond({message: response}); 44 | }); 45 | }); 46 | 47 | // handle subscription requests 48 | var interval; 49 | var subscriptions = {}; 50 | var x = 1; 51 | function createInterval() { 52 | if (interval) { 53 | return; 54 | } 55 | console.log("create new interval"); 56 | interval = setInterval(function() { 57 | sendResponses(); 58 | }, 1000); 59 | } 60 | 61 | // send responses to each subscribed client 62 | function sendResponses() { 63 | console.log("Sending responses, subscription count="+Object.keys(subscriptions).length); 64 | for (var i in subscriptions) { 65 | if (subscriptions.hasOwnProperty(i)) { 66 | var s = subscriptions[i]; 67 | s.respond({ 68 | returnValue: true, 69 | event: "beat "+x 70 | }); 71 | } 72 | } 73 | x++; 74 | } 75 | 76 | // listen for requests, and handle subscriptions via implicit event handlers in call 77 | // to register 78 | service.register("heartbeat", function(message) { 79 | var uniqueToken = message.uniqueToken; 80 | console.log("heartbeat callback, uniqueToken: "+uniqueToken+", token: "+message.token); 81 | message.respond({event: "beat"}); 82 | if (message.isSubscription) { 83 | subscriptions[uniqueToken] = message; 84 | if (!interval) { 85 | createInterval(); 86 | } 87 | } 88 | }, 89 | function(message) { 90 | var uniqueToken = message.uniqueToken; 91 | console.log("Canceled " + uniqueToken); 92 | delete subscriptions[uniqueToken]; 93 | var keys = Object.keys(subscriptions); 94 | if (keys.length === 0) { 95 | console.log("no more subscriptions, canceling interval"); 96 | clearInterval(interval); 97 | interval = undefined; 98 | } 99 | }); 100 | 101 | // a method that cancels subscriptions from service side 102 | service.register("cancel_subscriptions", function(message) { 103 | console.log("canceling all subscriptions to heartbeat"); 104 | for (var i in subscriptions) { 105 | if (subscriptions.hasOwnProperty(i)) { 106 | var s = subscriptions[i]; 107 | s.cancel({message: "sorry"}); 108 | } 109 | } 110 | message.respond({returnValue: true}); 111 | }); 112 | 113 | // EventEmitter-based API for subscriptions 114 | // note that the previous examples are actually using this API as well, they're 115 | // just setting a "request" handler implicitly 116 | var heartbeat2 = service.register("heartbeat2"); 117 | heartbeat2.on("request", function(message) { 118 | console.log("heartbeat callback"); 119 | message.respond({event: "beat"}); 120 | if (message.isSubscription) { 121 | subscriptions[message.uniqueToken] = message; 122 | if (!interval) { 123 | createInterval(); 124 | } 125 | } 126 | }); 127 | heartbeat2.on("cancel", function(message) { 128 | console.log("Canceled " + message.uniqueToken); 129 | delete subscriptions[message.uniqueToken]; 130 | var keys = Object.keys(subscriptions); 131 | if (keys.length === 0) { 132 | console.log("no more subscriptions, canceling interval"); 133 | clearInterval(interval); 134 | interval = undefined; 135 | } 136 | }); 137 | 138 | service.register("ping", function(message) { 139 | console.log("Ping! setting up activity"); 140 | var activitySpec = { 141 | "activity": { 142 | "name": "My Activity", //this needs to be unique, per service 143 | "description": "do something", //required 144 | "background": true, // can use foreground or background, or set individual properties (see Activity Specification below, for details) 145 | "persist": true, // this activity will be persistent across reboots 146 | "explicit": true, // this activity *must* be completed or cancelled explicitly, or it will be re-launched until it does 147 | "callback": { // what service to call when this activity starts 148 | "method": "luna://com.example.helloworld/pong", // URI to service 149 | "params": { // parameters/arguments to pass to service 150 | } 151 | } 152 | }, 153 | "start": true, // start the activity immediately when its requirements (if any) are met 154 | "replace": true, // if an activity with the same name already exists, replace it 155 | "subscribe": false // if "subscribe" is false, the activity needs to be adopted immediately, or it gets canceled 156 | }; 157 | service.call("luna://com.webos.service.activitymanager/create", activitySpec, function(reply) { 158 | var activityId = reply.payload.activityId; 159 | console.log("ActivityId = "+activityId); 160 | message.respond({msg: "Created activity "+activityId}); 161 | }); 162 | }); 163 | 164 | service.register("pong", function(message) { 165 | console.log("Pong!"); 166 | console.log(message.payload); 167 | message.respond({message: "Pong"}); 168 | }); 169 | 170 | service.register("/do/re/me", function(message) { 171 | message.respond({verses:[ 172 | {doe: "a deer, a female deer"}, 173 | {ray: "a drop of golden sun"}, 174 | {me: "a name I call myself"} 175 | ]}); 176 | }); 177 | 178 | var count = 0; 179 | service.register("increment", function(message) { 180 | count++; 181 | message.respond({ 182 | count: count 183 | }); 184 | }); 185 | 186 | service.register("getCount", function(message) { 187 | message.respond({ 188 | count: count 189 | }); 190 | }); 191 | 192 | var service2 = new Service("com.example.helloworld2"); 193 | service2.registerPrivate("hello", function(message) { 194 | message.respond({msg: "hello"}); 195 | }); 196 | -------------------------------------------------------------------------------- /sample/com.example.helloworld/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.example.helloworld", 3 | "version": "1.0.0", 4 | "description": "Helloworld service", 5 | "main": "helloworld_webos_service.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "BSD" 11 | } 12 | -------------------------------------------------------------------------------- /sample/com.example.helloworld/services.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "com.example.helloworld", 3 | "description": "HelloWorld Service", 4 | "services": [ { 5 | "name": "com.example.helloworld", 6 | "description": "HelloWorld Service", 7 | "commands": [ { 8 | "name": "hello", 9 | "public": true 10 | }, 11 | { 12 | "name": "config/setGreeting", 13 | "public": true 14 | }, 15 | { 16 | "name": "locale", 17 | "public": true 18 | }, 19 | { 20 | "name": "heartbeat", 21 | "public": true 22 | }, 23 | { 24 | "name": "heartbeat2", 25 | "public": true 26 | }, 27 | { 28 | "name": "ping", 29 | "public": true 30 | }, 31 | { 32 | "name": "pong", 33 | "public": true 34 | }, 35 | { 36 | "name": "do/re/me", 37 | "public": true 38 | }, 39 | { 40 | "name": "cancel_subscriptions", 41 | "public": true 42 | }] 43 | }, 44 | { 45 | "name": "com.example.helloworld2", 46 | "description": "Second service in the same program", 47 | "commands": [ { 48 | "name": "hello", 49 | "public": true 50 | }] 51 | } ] 52 | } 53 | -------------------------------------------------------------------------------- /scripts/make_template.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | zip -j webos-service-config.zip sample/com.example.helloworld/* -x \*helloclient.js 3 | -------------------------------------------------------------------------------- /service/com.example.activityexample.json: -------------------------------------------------------------------------------- 1 | { 2 | "role": { 3 | "allowedNames": ["com.example.activityexample"], 4 | "type": "regular", 5 | "exeName": "js" 6 | }, 7 | "permissions": [{ 8 | "inbound": ["*"], 9 | "outbound": ["*"], 10 | "service": "com.example.activityexample" 11 | }] 12 | } 13 | -------------------------------------------------------------------------------- /service/com.example.activityexample.service: -------------------------------------------------------------------------------- 1 | [D-BUS Service] 2 | Name=com.example.activityexample 3 | Exec=run-js-service /usr/palm/services/com.example.activityexample 4 | -------------------------------------------------------------------------------- /service/com.example.helloclient.json: -------------------------------------------------------------------------------- 1 | { 2 | "role": { 3 | "allowedNames": ["com.example.helloclient"], 4 | "type": "regular", 5 | "exeName": "js" 6 | }, 7 | "permissions": [{ 8 | "inbound": ["*"], 9 | "outbound": ["*"], 10 | "service": "com.example.helloclient" 11 | }] 12 | } 13 | -------------------------------------------------------------------------------- /service/com.example.helloclient.service: -------------------------------------------------------------------------------- 1 | [D-BUS Service] 2 | Name=com.example.helloclient 3 | Exec=NODE_PATH=/usr/palm/nodejs node /home/root/node_modules/webos-service/sample/helloclient.js 4 | -------------------------------------------------------------------------------- /service/com.example.helloworld.json: -------------------------------------------------------------------------------- 1 | { 2 | "role": { 3 | "allowedNames": ["com.example.helloworld", "com.example.helloworld2"], 4 | "type": "regular", 5 | "exeName": "js" 6 | }, 7 | "permissions": [{ 8 | "inbound": ["*"], 9 | "outbound": ["*"], 10 | "service": "com.example.helloworld" 11 | }, 12 | { 13 | "inbound": ["*"], 14 | "outbound": ["*"], 15 | "service": "com.example.helloworld2" 16 | }] 17 | } 18 | -------------------------------------------------------------------------------- /service/com.example.helloworld.service: -------------------------------------------------------------------------------- 1 | [D-BUS Service] 2 | Name=com.example.helloworld 3 | Exec=run-js-service /usr/palm/services/com.example.helloworld 4 | -------------------------------------------------------------------------------- /service/com.example.helloworld2.service: -------------------------------------------------------------------------------- 1 | [D-BUS Service] 2 | Name=com.example.helloworld2 3 | Exec=run-js-service /usr/palm/services/com.example.helloworld 4 | --------------------------------------------------------------------------------