├── .npmignore
├── .vscode
└── settings.json
├── webpack.config.js
├── .eslintrc.js
├── tasker-imports
├── TJS_UpdateScript.tsk.xml
├── TJS_RunScript.tsk.xml
└── TJS_Development_Toggle.tsk.xml
├── package.json
├── LICENSE
├── .gitignore
├── src
├── index.js
├── router.js
└── tasker.js
├── test
├── router
│ ├── parse-caller-id.spec.js
│ └── dispatch.spec.js
└── tasker
│ └── tasker-utilities
│ └── get-locals.spec.js
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 | .vscode
2 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "eslint.packageManager": "yarn",
3 |
4 | "editor.tabSize": 2,
5 | "editor.renderWhitespace": "all",
6 |
7 | // Turns auto fix on save on or off.
8 | "eslint.autoFixOnSave": true,
9 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | mode: 'none',
5 | entry: './src/index.js',
6 | output: {
7 | filename: 'index.js',
8 | path: path.resolve(__dirname, 'dist'),
9 | libraryTarget: 'umd',
10 | library: 'tasker-js-runner',
11 | umdNamedDefine: true,
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "parser": "babel-eslint",
8 | "extends": "eslint:recommended",
9 | "parserOptions": {
10 | "sourceType": "module"
11 | },
12 | "rules": {
13 | "indent": [
14 | "error",
15 | 2
16 | ],
17 | "linebreak-style": [
18 | "error",
19 | "windows"
20 | ],
21 | "quotes": [
22 | "error",
23 | "single"
24 | ],
25 | "semi": [
26 | "error",
27 | "always"
28 | ]
29 | }
30 | };
--------------------------------------------------------------------------------
/tasker-imports/TJS_UpdateScript.tsk.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 1519533460618
4 | 1519573304814
5 | 62
6 | TJS:UpdateScript
7 | 100
8 |
9 | 129
10 | fetch(global('TJS_DEV_REMOTE'))
11 | .then(res => res.text())
12 | .then((result) => {
13 | writeFile(global('TJS_LOCAL_PATH'), result);
14 | flash('TJS: Script is updated');
15 | })
16 | .catch((err) => {
17 | flash(err.message)
18 | })
19 | .then(exit)
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/tasker-imports/TJS_RunScript.tsk.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 1514855272770
4 | 1558890701302
5 | 29
6 | TJS:RunScript
7 | 2
8 |
9 | 347
10 |
11 |
12 |
13 | %local_keys
14 |
15 |
16 | 131
17 | false
18 | %TJS_LOCAL_PATH
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tasker-js-runner",
3 | "version": "1.1.0",
4 | "description": "JavaScript for Tasker",
5 | "author": "Amos Wong ",
6 | "license": "MIT",
7 | "homepage": "https://github.com/amoshydra/tasker-js-runner",
8 | "repository": {
9 | "type": "git",
10 | "url": "git@github.com:amoshydra/tasker-js-runner.git"
11 | },
12 | "main": "dist/index.js",
13 | "module": "src/index.js",
14 | "files": [
15 | "dist",
16 | "src",
17 | "tasker-imports"
18 | ],
19 | "scripts": {
20 | "build": "cross-env NODE_ENV=production webpack",
21 | "test": "ava"
22 | },
23 | "devDependencies": {
24 | "@babel/core": "^7.4.5",
25 | "@babel/preset-env": "^7.4.5",
26 | "ava": "^1.4.1",
27 | "babel-eslint": "^10.0.1",
28 | "babel-loader": "8.0.6",
29 | "cross-env": "^5.2.0",
30 | "eslint": "^5.16.0",
31 | "esm": "^3.2.25",
32 | "sinon": "^7.3.2",
33 | "webpack": "^4.32.2",
34 | "webpack-cli": "^3.3.2"
35 | },
36 | "dependencies": {},
37 | "ava": {
38 | "require": [
39 | "esm"
40 | ]
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Amos Wong Wen Jet
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 |
3 | # Created by https://www.gitignore.io/api/node,visualstudiocode
4 |
5 | ### Node ###
6 | # Logs
7 | logs
8 | *.log
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (http://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # Typescript v1 declaration files
45 | typings/
46 |
47 | # Optional npm cache directory
48 | .npm
49 |
50 | # Optional eslint cache
51 | .eslintcache
52 |
53 | # Optional REPL history
54 | .node_repl_history
55 |
56 | # Output of 'npm pack'
57 | *.tgz
58 |
59 | # Yarn Integrity file
60 | .yarn-integrity
61 |
62 | # dotenv environment variables file
63 | .env
64 |
65 |
66 | ### VisualStudioCode ###
67 | .vscode/*
68 | !.vscode/settings.json
69 | !.vscode/tasks.json
70 | !.vscode/launch.json
71 | !.vscode/extensions.json
72 | .history
73 |
74 |
75 | # End of https://www.gitignore.io/api/node,visualstudiocode
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import { initializeTaskerJs } from './tasker';
2 | import Router from './router';
3 |
4 | window.tasker = initializeTaskerJs(window);
5 |
6 | const CONFIG = {
7 | Environment: tasker.global('TJS_ENV'),
8 | RemoteUrl: tasker.global('TJS_DEV_REMOTE'),
9 | LocalPath: tasker.global('TJS_LOCAL_PATH'),
10 | };
11 | const TASK = {
12 | RunScript: 'TJS:RunScript',
13 | };
14 |
15 | const hotReload = () => {
16 | if (CONFIG.Environment !== 'development') return Promise.resolve();
17 |
18 | return fetch(CONFIG.RemoteUrl)
19 | .then(res => res.text())
20 | .then((result) => {
21 | const existingFile = tasker.readFile(CONFIG.LocalPath);
22 |
23 | if (existingFile !== result) {
24 | tasker.writeFile(CONFIG.LocalPath, result);
25 | tasker.flash('script updated');
26 | tasker.performTask(
27 | /* Task name */ TASK.RunScript,
28 | /* Priority */tasker.local('priority'),
29 | /* par1 */ 'null',
30 | /* par2 */ JSON.stringify(tasker.locals), // Supply par2 to overwrite context
31 | );
32 | tasker.exit();
33 | }
34 |
35 | })
36 | .catch(err => tasker.flash(err.message));
37 | };
38 |
39 | export default class TaskerJS {
40 | constructor(routes) {
41 | this.router = new Router(routes, tasker);
42 |
43 | hotReload()
44 | .then(() =>
45 | this.router.dispatch(tasker.locals)
46 | .catch(err => tasker.flash(err.message))
47 | .then(() => tasker.exit())
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/router.js:
--------------------------------------------------------------------------------
1 | export const ROUTE_TYPE = {
2 | Enter: 'enter',
3 | Exit: 'exit',
4 | };
5 |
6 | export const parseCallerId = (callerId = '') => {
7 | const [callerSourceId, ...splitedRouteId] = callerId.split('=');
8 | const routeId = splitedRouteId.join('=');
9 |
10 | switch (callerSourceId) {
11 | case 'profile': {
12 | const [callerType, ...splittedCallerRoute] = routeId.split(':');
13 | return {
14 | type: callerType,
15 | route: splittedCallerRoute.join(':'),
16 | };
17 | }
18 | case 'task': return {
19 | type: ROUTE_TYPE.Enter,
20 | route: routeId,
21 | };
22 | default: return {
23 | type: ROUTE_TYPE.Enter,
24 | route: callerId,
25 | };
26 | }
27 | }
28 |
29 | export default class Router {
30 | constructor(routes, context) {
31 | this.context = context;
32 | this.routes = routes;
33 | if (!this.routes._errorHandler) {
34 | this.routes._errorHandler = {
35 | enter() {
36 | context.console.log('No route matched')
37 | },
38 | exit() {},
39 | };
40 | }
41 | }
42 |
43 | dispatch(locals) {
44 | return Promise.resolve()
45 | .then(() => {
46 | // Make route
47 | const callerId = locals.caller && locals.caller[locals.caller.length - 1];
48 | const caller = parseCallerId(callerId);
49 |
50 | // Go to route
51 | const route = this.routes[caller.route] || this.routes._errorHandler;
52 | return route[caller.type](locals, this.context);
53 | });
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/test/router/parse-caller-id.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import {
3 | parseCallerId,
4 | ROUTE_TYPE,
5 | } from '../../src/router';
6 |
7 | test('should parse Profile enter syntax', t => {
8 | const caller = parseCallerId('profile=enter:ProfileName');
9 |
10 | t.deepEqual(caller, {
11 | type: ROUTE_TYPE.Enter,
12 | route: 'ProfileName'
13 | });
14 | });
15 |
16 | test('should parse Profile enter syntax with multiple equal and colon characters', t => {
17 | const caller = parseCallerId('profile=enter:ProfileName=:=Name');
18 |
19 | t.deepEqual(caller, {
20 | type: ROUTE_TYPE.Enter,
21 | route: 'ProfileName=:=Name'
22 | });
23 | });
24 |
25 | test('should parse Profile exit syntax', t => {
26 | const caller = parseCallerId('profile=exit:ProfileName');
27 |
28 | t.deepEqual(caller, {
29 | type: ROUTE_TYPE.Exit,
30 | route: 'ProfileName'
31 | });
32 | });
33 |
34 | test('should parse Task syntax', t => {
35 | const caller = parseCallerId('task=TaskName');
36 |
37 | t.deepEqual(caller, {
38 | type: ROUTE_TYPE.Enter,
39 | route: 'TaskName'
40 | });
41 | });
42 |
43 | test('should parse UI syntax', t => {
44 | const caller = parseCallerId('ui');
45 |
46 | t.deepEqual(caller, {
47 | type: ROUTE_TYPE.Enter,
48 | route: 'ui'
49 | });
50 | });
51 |
52 | test('should parse arbitary caller syntax', t => {
53 | t.is(
54 | parseCallerId('Do something').route,
55 | 'Do something'
56 | );
57 |
58 | t.is(
59 | parseCallerId('Light:On').route,
60 | 'Light:On'
61 | );
62 |
63 | t.is(
64 | parseCallerId('NoneProfileOrTask=Name').route,
65 | 'NoneProfileOrTask=Name'
66 | );
67 | t.is(
68 | parseCallerId('NoneProfileOrTask=Enter:Name').route,
69 | 'NoneProfileOrTask=Enter:Name'
70 | );
71 | });
72 |
--------------------------------------------------------------------------------
/src/tasker.js:
--------------------------------------------------------------------------------
1 | export const taskerUtilities = {
2 | inspect: (target) => {
3 | const cache = [];
4 | return JSON.stringify(target, function(key, value) {
5 | if (typeof value === 'object' && value !== null) {
6 | if (cache.indexOf(value) !== -1) {
7 | // Circular reference found, discard key
8 | return;
9 | }
10 | // Store value in our collection
11 | cache.push(value);
12 | }
13 | return value;
14 | });
15 | },
16 |
17 | makeConsole: (context) => ({
18 | log(...params) {
19 | context.flash(
20 | params
21 | .map(param => (typeof param === 'string') ? param : taskerUtilities.inspect(param))
22 | .join(' ')
23 | );
24 | },
25 | }),
26 |
27 | getParams: (context) => {
28 | return (context.par || [])
29 | .map((rawParam) => {
30 | // Test if param is a json
31 | let parsedParam;
32 | try {
33 | parsedParam = JSON.parse(rawParam); // will fail if param is not a JSON
34 | } catch (err) {
35 | parsedParam = rawParam;
36 | }
37 | return parsedParam === 'undefined' ? undefined : parsedParam;
38 | });
39 | },
40 |
41 | getLocals: (context) => {
42 | const taskParameters = taskerUtilities.getParams(context);
43 |
44 | // Handle overriding behaviour
45 | const par2 = taskParameters[1];
46 | const overridingLocals = ((par2 && (typeof par2 === 'object')) ? taskParameters[1] : null);
47 | if (overridingLocals) {
48 | return {
49 | par: (overridingLocals.par || []),
50 | caller: (overridingLocals.caller || []),
51 | ...overridingLocals,
52 | };
53 | }
54 |
55 | // Handle merging behaviour
56 | const par1 = taskParameters[0];
57 | const parentLocals = ((par1 && (typeof par1 === 'object')) ? par1 : {}) || {};
58 | const locals = (context.local_keys || [])
59 | .reduce((acc, key) => {
60 | const keyName = key.slice(1);
61 | acc[keyName] = context.local(keyName);
62 | return acc;
63 | }, {});
64 |
65 |
66 | return ({
67 | ...locals,
68 | ...parentLocals,
69 | par: taskParameters,
70 | caller: [
71 | ...(context.caller || []),
72 | ...(parentLocals.caller || []),
73 | ],
74 | })
75 | },
76 | };
77 |
78 | export const initializeTaskerJs = (context) => {
79 | // Injecting development functions
80 | context.inspect = taskerUtilities.inspect;
81 | context.console = taskerUtilities.makeConsole(context);
82 |
83 | // Attempt to restore param from upstream
84 | context.locals = taskerUtilities.getLocals(context)
85 |
86 | return context;
87 | }
88 |
--------------------------------------------------------------------------------
/test/tasker/tasker-utilities/get-locals.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import { taskerUtilities } from '../../../src/tasker';
3 |
4 | class Context {
5 | constructor(context) {
6 | Object.keys(context).forEach(key => this[key] = context[key]);
7 | }
8 |
9 | local(name) {
10 | return this[name];
11 | }
12 | }
13 |
14 | test('should output a minimal locals containing caller and par', t => {
15 | const actual = taskerUtilities.getLocals(
16 | new Context({})
17 | );
18 |
19 | t.deepEqual(actual, {
20 | caller: [],
21 | par: [],
22 | })
23 | });
24 |
25 | test('should merge own locals with parent\'s locals', t => {
26 | const parentLocal = JSON.stringify({
27 | localA: 'localA',
28 | localB: 'localB',
29 | conflictA: 'parentConflicA',
30 | });
31 | const actual = taskerUtilities.getLocals(
32 | new Context({
33 | caller: ['caller3', 'caller2', 'caller1'],
34 | par: [
35 | {
36 | localA: 'localA',
37 | localB: 'localB',
38 | conflictA: 'parentConflicA',
39 | },
40 | ],
41 | conflictA: 'selfConflicA',
42 | })
43 | );
44 |
45 | t.deepEqual(actual, {
46 | caller: ['caller3', 'caller2', 'caller1'],
47 | par: [
48 | JSON.parse(parentLocal),
49 | ],
50 | localA: 'localA',
51 | localB: 'localB',
52 | conflictA: 'parentConflicA',
53 | })
54 | });
55 |
56 | test('should skip parent\'s locals if it is null', t => {
57 | const parentLocal = null;
58 | const actual = taskerUtilities.getLocals(
59 | new Context({
60 | caller: ['caller3', 'caller2', 'caller1'],
61 | par: [
62 | parentLocal,
63 | ],
64 | })
65 | );
66 |
67 | t.deepEqual(actual, {
68 | caller: ['caller3', 'caller2', 'caller1'],
69 | par: [
70 | null,
71 | ],
72 | })
73 | });
74 |
75 | test('should skip parent\'s locals if it is not an JSON string', t => {
76 | const parentLocal = "INVALID: PARENT";
77 | const actual = taskerUtilities.getLocals(
78 | new Context({
79 | caller: ['caller3', 'caller2', 'caller1'],
80 | par: [
81 | parentLocal,
82 | ],
83 | })
84 | );
85 |
86 | t.deepEqual(actual, {
87 | caller: ['caller3', 'caller2', 'caller1'],
88 | par: [
89 | parentLocal,
90 | ],
91 | })
92 | });
93 |
94 | test('should ignore own locals with par2 is provided', t => {
95 | const overridingLocal = JSON.stringify({
96 | caller: [
97 | 'OverridingCaller2',
98 | 'OverridingCaller1'
99 | ],
100 | });
101 | const actual = taskerUtilities.getLocals(
102 | new Context({
103 | caller: ['caller3', 'caller2', 'caller1'],
104 | par: [
105 | 'null',
106 | overridingLocal,
107 | ],
108 | })
109 | );
110 |
111 | t.deepEqual(actual, {
112 | caller: [
113 | 'OverridingCaller2',
114 | 'OverridingCaller1'
115 | ],
116 | par: [],
117 | })
118 | });
119 |
--------------------------------------------------------------------------------
/test/router/dispatch.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import sinon from 'sinon';
3 | import Router from '../../src/router';
4 |
5 | test('should dispatch routes._errorHandler.enter function given no caller', async t => {
6 | const context = global;
7 | const router = new Router({}, context);
8 |
9 | const defaultErrorHandlerEnterSpy = sinon.spy(router.routes._errorHandler, 'enter');
10 |
11 | const locals = { /* locals without caller */ };
12 | await router.dispatch(locals);
13 |
14 | t.true(defaultErrorHandlerEnterSpy.calledOnceWithExactly(locals, context));
15 | });
16 |
17 | test('should dispatch routes._errorHandler.enter function given invalid caller', async t => {
18 | const context = global;
19 | const router = new Router({}, context);
20 |
21 | const defaultErrorHandlerEnterSpy = sinon.spy(router.routes._errorHandler, 'enter');
22 |
23 | const locals = {
24 | caller: ['profile=enter:AnInvalidCallerId']
25 | };
26 | await router.dispatch(locals);
27 |
28 | t.true(defaultErrorHandlerEnterSpy.calledOnceWithExactly(locals, context));
29 | });
30 |
31 | test('should dispatch matching Profile enter caller', async t => {
32 | const context = global;
33 | const router = new Router({
34 | ValidCallerId: {
35 | enter: sinon.fake(),
36 | }
37 | }, context);
38 |
39 | const locals = {
40 | caller: ['profile=enter:ValidCallerId']
41 | };
42 | await router.dispatch(locals);
43 |
44 | t.true(router.routes.ValidCallerId.enter.calledOnceWithExactly(locals, context));
45 | });
46 |
47 | test('should dispatch matching Profile exit caller', async t => {
48 | const context = global;
49 | const router = new Router({
50 | ValidCallerId: {
51 | exit: sinon.fake(),
52 | }
53 | }, context);
54 |
55 | const locals = {
56 | caller: ['profile=exit:ValidCallerId']
57 | };
58 | await router.dispatch(locals);
59 |
60 | t.true(router.routes.ValidCallerId.exit.calledOnceWithExactly(locals, context));
61 | });
62 |
63 | test('should dispatch matching Task caller', async t => {
64 | const context = global;
65 | const router = new Router({
66 | ValidCallerId: {
67 | enter: sinon.fake(),
68 | }
69 | }, context);
70 |
71 | const locals = {
72 | caller: ['task=ValidCallerId']
73 | };
74 | await router.dispatch(locals);
75 |
76 | t.true(router.routes.ValidCallerId.enter.calledOnceWithExactly(locals, context));
77 | });
78 |
79 | test('should dispatch matching UI caller', async t => {
80 | const context = global;
81 | const router = new Router({
82 | ui: {
83 | enter: sinon.fake(),
84 | }
85 | }, context);
86 |
87 | const locals = {
88 | caller: ['ui']
89 | };
90 | await router.dispatch(locals);
91 |
92 | t.true(router.routes.ui.enter.calledOnceWithExactly(locals, context));
93 | });
94 |
95 | test('should dispatch matching arbitary caller', async t => {
96 | const context = global;
97 | const router = new Router({
98 | 'NoneProfileOrTask=Enter:Name': {
99 | enter: sinon.fake(),
100 | }
101 | }, context);
102 |
103 | const locals = {
104 | caller: ['NoneProfileOrTask=Enter:Name']
105 | };
106 | await router.dispatch(locals);
107 |
108 | t.true(router.routes['NoneProfileOrTask=Enter:Name'].enter.calledOnceWithExactly(locals, context));
109 | });
110 |
--------------------------------------------------------------------------------
/tasker-imports/TJS_Development_Toggle.tsk.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 1519560745717
4 | 1519573351396
5 | 65
6 | TJS:Development:Toggle
7 | 100
8 |
9 | 37
10 |
11 |
12 | %TJS_ENV
13 | 13
14 | development
15 |
16 |
17 |
18 |
19 | 547
20 | %TJS_ENV
21 | production
22 |
23 |
24 |
25 |
26 |
27 | 547
28 | %TJS_ENV
29 | development
30 |
31 |
32 |
33 |
34 |
35 | 548
36 | TJS: Development Mode ON
37 |
38 | Getting script from:
39 | %TJS_DEV_REMOTE
40 |
41 |
42 |
43 | 130
44 | TJS:UpdateScript
45 |
46 | %priority
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | 38
55 |
56 |
57 | 547
58 | %TJS_LOCAL_PATH
59 | Documents/tasker-js-runner.js
60 |
61 |
62 |
63 |
64 |
65 | 547
66 | %TJS_DEV_REMOTE
67 | http://192.168.0.10:8080/index.js
68 |
69 |
70 |
71 |
72 |
73 | 548
74 | TJS: Initialising Tasker-JS
75 |
76 | [Remote]
77 | %TJS_DEV_REMOTE
78 |
79 | [Local]
80 | %TJS_LOCAL_PATH
81 |
82 |
83 |
84 | 130
85 | TJS:UpdateScript
86 |
87 | %priority
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | 43
96 |
97 |
98 | %TJS_ENV
99 | 2
100 | development
101 |
102 |
103 |
104 |
105 | 547
106 | %TJS_ENV
107 | production
108 |
109 |
110 |
111 |
112 |
113 | 548
114 | TJS: Development Mode OFF
115 |
116 |
117 |
118 | 43
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tasker JS Runner
2 | - Write Tasker task as Javascript module
3 | - Map profile name to module
4 | - Auto refresh script during development mode
5 |
6 | # Guide
7 | ## Installation
8 | with npm
9 | ```
10 | npm install tasker-js-runner --save
11 | ```
12 |
13 | with yarn
14 | ```
15 | yarn add tasker-js-runner
16 | ```
17 | ### Usage
18 |
19 | #### Defining profile map `index.js`
20 | ```javascript
21 | import TaskerJs from 'tasker-js-runner';
22 |
23 | // Tasker Javascript modules
24 | import notification from './modules/notification';
25 |
26 | // Construct Tasker JS and pass in mapping information as an Object
27 | new TaskerJs({
28 | // Profile name: module
29 | 'Notification:All': notification,
30 | });
31 |
32 | ```
33 |
34 | #### Defining a module `modules/notification`
35 | A module should contain an `enter` and an `exit` function.
36 | The 2 functions will receive all the local variables from the profile's task
37 | via `locals` and a reference Tasker's global object via `tasker`.
38 |
39 | ```javascript
40 | export default {
41 | enter(locals, tasker) {
42 | // Example: Accessing local variables %anapp and %antitle from AutoNotification
43 | const content = locals.anapp + ' ' + locals.antitle;
44 |
45 | // Tasker's function can be accessed via the `tasker` object.
46 | tasker.setClip(content);
47 |
48 | // If you wish, you can also omit `tasker` by calling Tasker's function directly.
49 | // Behind the scene, `tasker` is mapped to the `window` object where the
50 | // Tasker's function live.
51 | flash('content');
52 | },
53 |
54 | exit(locals, tasker) {}
55 | };
56 | ```
57 |
58 | #### Asynchronous Execution
59 |
60 | All module functions are wrapped inside a Promise, which will gracefully exit at the end of execution.
61 | To ensure proper task exiting while running asynchronous code, always return a promise.
62 | ```javascript
63 | export default {
64 | enter(locals, tasker) {
65 |
66 | return new Promise((resolve, reject) => {
67 | setTimeout(() => {
68 | if (tasker.global('BLUE') === 'on') {
69 | resolve();
70 | } else {
71 | reject();
72 | }
73 | }, 1000);
74 | });
75 |
76 | },
77 | ...
78 | }
79 | ```
80 |
81 | Otherwise, you can also permaturely terminte the script. This will immediately stop the script from executing.
82 | ```javascript
83 | export default {
84 | enter(locals, tasker) {
85 |
86 | setTimeout(() => {
87 | tasker.exit()
88 | }, 1000);
89 |
90 | },
91 | ...
92 | }
93 | ```
94 |
95 | ## Sample project
96 | https://github.com/amoshydra/tasker-js-runner-project
97 |
98 | ## Setup on Tasker
99 |
100 | ### Installing
101 | 1. Import the 3 tasks from the [`tasker-imports` folder](https://github.com/amoshydra/tasker-js-runner/tree/master/tasker-imports) into Tasker
102 | - `TJS_Development_Toggle.tsk.xml`
103 | - `TJS_RunScript.tsk.xml`
104 | - `TJS_UpdateScript.tsk.xml`
105 | 2. Run [TJS_Development_Toggle] inside Tasker to set up the required global variables.
106 | When [TJS_Development_Toggle] is run for the first time, it will set up all the necessary Global variables for Tasker-JS to run.
107 | - `%TJS_ENV` - Control the environment Tasker-JS-Runner to run `development`/`production`.
108 | - `%TJS_DEV_REMOTE` - The remote address where the your project script (a seperate project that make use of this library) will be downloaded. You will need to change the value of this variable in this task to match the IP of your project.
109 | - `%TJS_LOCAL_PATH` - The location where the project script will be saved (default to `Documents/tasker-js-runner.js`)
110 |
111 | ### Using
112 | 1. Create a Tasker named profile (i.e. `Notification:All`) and select `TJS:RunScript` as its task.
113 | 2. Tasker-JS-Runner will detect the profile name and execute the Javascipt module that's mapped into `Notification:All` in the Profile Map (see example from [above](#defining-profile-map-indexjs)
114 |
--------------------------------------------------------------------------------