@dronedeploy/rocos-js-sdk 3.0.0-alpha.6 → 3.0.1-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/helpers/getURLSearchParams.d.ts +2 -0
- package/helpers/getURLSearchParams.js +9 -0
- package/helpers/getURLSearchParams.spec.d.ts +1 -0
- package/helpers/getURLSearchParams.spec.js +19 -0
- package/helpers/index.d.ts +2 -0
- package/helpers/index.js +2 -0
- package/helpers/websandbox/connection.d.ts +72 -0
- package/helpers/websandbox/connection.js +140 -0
- package/helpers/websandbox/frame/frame.d.ts +12 -0
- package/helpers/websandbox/frame/frame.js +22 -0
- package/helpers/websandbox/frame/frame.source.d.ts +2 -0
- package/helpers/websandbox/frame/frame.source.js +4 -0
- package/helpers/websandbox/frame/index.d.ts +2 -0
- package/helpers/websandbox/frame/index.js +2 -0
- package/helpers/websandbox/frame/worker/index.d.ts +2 -0
- package/helpers/websandbox/frame/worker/index.js +2 -0
- package/helpers/websandbox/frame/worker/manager.d.ts +13 -0
- package/helpers/websandbox/frame/worker/manager.js +59 -0
- package/helpers/websandbox/frame/worker/types.d.ts +11 -0
- package/helpers/websandbox/frame/worker/types.js +1 -0
- package/helpers/websandbox/frame/worker/worker.d.ts +1 -0
- package/helpers/websandbox/frame/worker/worker.js +74 -0
- package/helpers/websandbox/frame/worker/worker.source.d.ts +2 -0
- package/helpers/websandbox/frame/worker/worker.source.js +4 -0
- package/helpers/websandbox/index.d.ts +2 -0
- package/helpers/websandbox/index.js +2 -0
- package/helpers/websandbox/sandbox.d.ts +48 -0
- package/helpers/websandbox/sandbox.js +116 -0
- package/helpers/websandbox/types.d.ts +13 -0
- package/helpers/websandbox/types.js +1 -0
- package/package.json +4 -2
- package/services/AssetStorageService.js +1 -1
- package/services/BaseServiceAbstract.d.ts +1 -1
- package/services/BaseServiceAbstract.js +2 -6
- package/services/TelemetryService.spec.js +2 -2
@@ -0,0 +1,9 @@
|
|
1
|
+
export const getURLSearchParams = (params) => {
|
2
|
+
if (params instanceof URLSearchParams)
|
3
|
+
return params;
|
4
|
+
const stringParams = Object.entries(params ?? {}).reduce((acc, [key, value]) => {
|
5
|
+
acc[key] = value.toString();
|
6
|
+
return acc;
|
7
|
+
}, {});
|
8
|
+
return new URLSearchParams(stringParams);
|
9
|
+
};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import { getURLSearchParams } from './getURLSearchParams';
|
2
|
+
describe('getURLSearchParams', () => {
|
3
|
+
it('Should return the original param if URLSearchParams', () => {
|
4
|
+
const param = new URLSearchParams();
|
5
|
+
const searchParam = getURLSearchParams(param);
|
6
|
+
expect(searchParam).toBe(param);
|
7
|
+
});
|
8
|
+
it('Should a new URLSearchParams ', () => {
|
9
|
+
const param = {
|
10
|
+
foo: 'bar',
|
11
|
+
bar: true,
|
12
|
+
foobar: 123,
|
13
|
+
};
|
14
|
+
const searchParam = getURLSearchParams(param);
|
15
|
+
expect(searchParam.get('foo')).toBe('bar');
|
16
|
+
expect(searchParam.get('bar')).toBe('true');
|
17
|
+
expect(searchParam.get('foobar')).toBe('123');
|
18
|
+
});
|
19
|
+
});
|
package/helpers/index.d.ts
CHANGED
package/helpers/index.js
CHANGED
@@ -0,0 +1,72 @@
|
|
1
|
+
import { APIDeclaration } from './types';
|
2
|
+
export interface ConnectionOptions {
|
3
|
+
allowedSenderOrigin?: string;
|
4
|
+
debugMode: boolean;
|
5
|
+
}
|
6
|
+
type ListenerCallback = (e: MessageEvent<Message>) => void;
|
7
|
+
declare enum MessageType {
|
8
|
+
RESPONSE = "response",
|
9
|
+
MESSAGE = "message"
|
10
|
+
}
|
11
|
+
interface BaseMessageData {
|
12
|
+
type: MessageType;
|
13
|
+
callId: string;
|
14
|
+
}
|
15
|
+
interface ResponseData extends BaseMessageData {
|
16
|
+
type: MessageType.RESPONSE;
|
17
|
+
success: boolean;
|
18
|
+
result: unknown;
|
19
|
+
}
|
20
|
+
interface MessageData extends BaseMessageData {
|
21
|
+
type: MessageType.MESSAGE;
|
22
|
+
methodName: string;
|
23
|
+
arguments: unknown[];
|
24
|
+
}
|
25
|
+
type Message = MessageData | ResponseData;
|
26
|
+
export default class Connection<Local extends APIDeclaration<Local>, Remote extends APIDeclaration<Remote>> {
|
27
|
+
private readonly name;
|
28
|
+
private incrementalID;
|
29
|
+
private readonly options;
|
30
|
+
private readonly postMessageInternal;
|
31
|
+
private callbacks;
|
32
|
+
private serviceMethods;
|
33
|
+
constructor(name: string, postMessage: typeof window.postMessage, methods: Local, registerOnMessageListener: (listener: ListenerCallback) => void, options?: Partial<ConnectionOptions>);
|
34
|
+
/** Update the local methods available to the remote
|
35
|
+
*
|
36
|
+
* @param methods - methods to expose to the remote
|
37
|
+
*/
|
38
|
+
defineMethods(methods: Local): void;
|
39
|
+
/** Call a remote method
|
40
|
+
*
|
41
|
+
* returns a promise that resolves when the remote responds
|
42
|
+
*
|
43
|
+
* @param name - name of remote method to call
|
44
|
+
* @param args - arguments to pass to remote method
|
45
|
+
*/
|
46
|
+
callRemoteMethod<Method extends keyof Remote>(name: Method, ...args: Parameters<Remote[Method]>): Promise<ReturnType<Remote[Method]>>;
|
47
|
+
private onMessageListener;
|
48
|
+
private callLocalMethod;
|
49
|
+
/** Respond to remote call
|
50
|
+
*
|
51
|
+
* @param id - remote call ID
|
52
|
+
* @param result - result to pass to calling function
|
53
|
+
* @param success - whether the call was successful
|
54
|
+
*/
|
55
|
+
private responseOtherSide;
|
56
|
+
/** Store a callback to be called when the remote responds
|
57
|
+
*
|
58
|
+
* @param success - callback to be called on success
|
59
|
+
* @param failure - callback to be called on failure
|
60
|
+
*/
|
61
|
+
private registerCallback;
|
62
|
+
/** Calls and deletes stored callback
|
63
|
+
*
|
64
|
+
* @param callId - ID of callback to call
|
65
|
+
* @param success - whether the call was successful
|
66
|
+
* @param result - result of remote call
|
67
|
+
*/
|
68
|
+
private popCallback;
|
69
|
+
private postMessage;
|
70
|
+
private log;
|
71
|
+
}
|
72
|
+
export {};
|
@@ -0,0 +1,140 @@
|
|
1
|
+
var MessageType;
|
2
|
+
(function (MessageType) {
|
3
|
+
MessageType["RESPONSE"] = "response";
|
4
|
+
MessageType["MESSAGE"] = "message";
|
5
|
+
})(MessageType || (MessageType = {}));
|
6
|
+
const defaultOptions = {
|
7
|
+
allowedSenderOrigin: undefined,
|
8
|
+
debugMode: false,
|
9
|
+
};
|
10
|
+
export default class Connection {
|
11
|
+
constructor(name, postMessage, methods, registerOnMessageListener, options = {}) {
|
12
|
+
this.callbacks = new Map();
|
13
|
+
this.serviceMethods = new Map();
|
14
|
+
this.name = name;
|
15
|
+
this.options = { ...defaultOptions, ...options };
|
16
|
+
this.log('Created connection w/ allowedOrigin:', this.options.allowedSenderOrigin);
|
17
|
+
this.serviceMethods = new Map(Object.entries(methods));
|
18
|
+
// Assign a random starting ID to this connection
|
19
|
+
const [id] = crypto.getRandomValues(new Uint32Array(1));
|
20
|
+
this.incrementalID = id;
|
21
|
+
this.postMessageInternal = postMessage;
|
22
|
+
registerOnMessageListener((e) => this.onMessageListener(e));
|
23
|
+
}
|
24
|
+
/** Update the local methods available to the remote
|
25
|
+
*
|
26
|
+
* @param methods - methods to expose to the remote
|
27
|
+
*/
|
28
|
+
defineMethods(methods) {
|
29
|
+
this.serviceMethods = new Map(Object.entries(methods));
|
30
|
+
}
|
31
|
+
/** Call a remote method
|
32
|
+
*
|
33
|
+
* returns a promise that resolves when the remote responds
|
34
|
+
*
|
35
|
+
* @param name - name of remote method to call
|
36
|
+
* @param args - arguments to pass to remote method
|
37
|
+
*/
|
38
|
+
callRemoteMethod(name, ...args) {
|
39
|
+
this.log('Calling Remote Method', {
|
40
|
+
name,
|
41
|
+
args,
|
42
|
+
});
|
43
|
+
return new Promise((resolve, reject) => {
|
44
|
+
const id = this.registerCallback(resolve, reject);
|
45
|
+
this.postMessage({
|
46
|
+
callId: id,
|
47
|
+
type: MessageType.MESSAGE,
|
48
|
+
methodName: name,
|
49
|
+
arguments: args,
|
50
|
+
});
|
51
|
+
});
|
52
|
+
}
|
53
|
+
onMessageListener(e) {
|
54
|
+
this.log('Received message', e);
|
55
|
+
const { data } = e;
|
56
|
+
const { allowedSenderOrigin } = this.options;
|
57
|
+
if (allowedSenderOrigin && e.origin !== allowedSenderOrigin) {
|
58
|
+
console.warn(`Received message from invalid origin: ${e.origin}`);
|
59
|
+
}
|
60
|
+
switch (data.type) {
|
61
|
+
case MessageType.RESPONSE:
|
62
|
+
this.popCallback(data.callId, data.success, data.result);
|
63
|
+
return;
|
64
|
+
case MessageType.MESSAGE:
|
65
|
+
this.callLocalMethod(data.methodName, data.arguments)
|
66
|
+
.then((res) => this.responseOtherSide(data.callId, res))
|
67
|
+
.catch((err) => this.responseOtherSide(data.callId, err, false));
|
68
|
+
}
|
69
|
+
}
|
70
|
+
async callLocalMethod(methodName, args) {
|
71
|
+
this.log('calling local method', methodName, args);
|
72
|
+
const method = this.serviceMethods.get(methodName);
|
73
|
+
if (!method) {
|
74
|
+
throw new Error(`service method ${methodName} not found`);
|
75
|
+
}
|
76
|
+
return method(...args);
|
77
|
+
}
|
78
|
+
/** Respond to remote call
|
79
|
+
*
|
80
|
+
* @param id - remote call ID
|
81
|
+
* @param result - result to pass to calling function
|
82
|
+
* @param success - whether the call was successful
|
83
|
+
*/
|
84
|
+
responseOtherSide(id, result, success = true) {
|
85
|
+
this.log('responding to remote call', { id, result, success });
|
86
|
+
const doPost = (result) => {
|
87
|
+
this.postMessage({
|
88
|
+
callId: id,
|
89
|
+
type: MessageType.RESPONSE,
|
90
|
+
success,
|
91
|
+
result,
|
92
|
+
});
|
93
|
+
};
|
94
|
+
try {
|
95
|
+
doPost(result);
|
96
|
+
}
|
97
|
+
catch (err) {
|
98
|
+
if (err instanceof DOMException) {
|
99
|
+
doPost(JSON.parse(JSON.stringify(result)));
|
100
|
+
}
|
101
|
+
}
|
102
|
+
}
|
103
|
+
/** Store a callback to be called when the remote responds
|
104
|
+
*
|
105
|
+
* @param success - callback to be called on success
|
106
|
+
* @param failure - callback to be called on failure
|
107
|
+
*/
|
108
|
+
registerCallback(success, failure) {
|
109
|
+
const id = (++this.incrementalID).toString();
|
110
|
+
this.log('registering callback for id', id);
|
111
|
+
this.callbacks.set(id, { success, failure });
|
112
|
+
return id;
|
113
|
+
}
|
114
|
+
/** Calls and deletes stored callback
|
115
|
+
*
|
116
|
+
* @param callId - ID of callback to call
|
117
|
+
* @param success - whether the call was successful
|
118
|
+
* @param result - result of remote call
|
119
|
+
*/
|
120
|
+
popCallback(callId, success, result) {
|
121
|
+
this.log('calling callback for id', callId, { success, result });
|
122
|
+
const callbacks = this.callbacks.get(callId);
|
123
|
+
if (success) {
|
124
|
+
callbacks?.success(result);
|
125
|
+
}
|
126
|
+
else {
|
127
|
+
callbacks?.failure(result);
|
128
|
+
}
|
129
|
+
this.callbacks.delete(callId);
|
130
|
+
}
|
131
|
+
postMessage(data, targetOrigin = '*') {
|
132
|
+
this.log('sending message', { data, targetOrigin });
|
133
|
+
this.postMessageInternal(data, targetOrigin);
|
134
|
+
}
|
135
|
+
log(...args) {
|
136
|
+
if (this.options.debugMode) {
|
137
|
+
console.debug(`[${this.name}]`, ...args);
|
138
|
+
}
|
139
|
+
}
|
140
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { Task } from '../types';
|
2
|
+
export interface FrameMethods {
|
3
|
+
startTask: (task: Task) => unknown;
|
4
|
+
}
|
5
|
+
declare class Frame {
|
6
|
+
private connection;
|
7
|
+
private workerManager;
|
8
|
+
constructor();
|
9
|
+
private runCode;
|
10
|
+
}
|
11
|
+
declare const frame: Frame;
|
12
|
+
export default frame;
|
@@ -0,0 +1,22 @@
|
|
1
|
+
// this file is bundled via webpack and included in the iframe source
|
2
|
+
import Connection from '../connection';
|
3
|
+
import WorkerManager from './worker';
|
4
|
+
class Frame {
|
5
|
+
constructor() {
|
6
|
+
const isDebugMode = !!window?.debugMode;
|
7
|
+
this.connection = new Connection('FRAME', window.parent.postMessage.bind(window.parent), {
|
8
|
+
startTask: this.runCode.bind(this),
|
9
|
+
}, (listener) => {
|
10
|
+
window.addEventListener('message', listener);
|
11
|
+
}, {
|
12
|
+
debugMode: isDebugMode,
|
13
|
+
});
|
14
|
+
void this.connection.callRemoteMethod('iframeInitialised');
|
15
|
+
this.workerManager = new WorkerManager();
|
16
|
+
}
|
17
|
+
async runCode(task) {
|
18
|
+
return this.workerManager.execute(task);
|
19
|
+
}
|
20
|
+
}
|
21
|
+
const frame = new Frame();
|
22
|
+
export default frame;
|
@@ -0,0 +1,4 @@
|
|
1
|
+
// Auto-generated file
|
2
|
+
/* eslint-disable */
|
3
|
+
const source = "(()=>{\"use strict\";var e={880:(e,t)=>{var s;Object.defineProperty(t,\"__esModule\",{value:!0}),function(e){e.RESPONSE=\"response\",e.MESSAGE=\"message\"}(s||(s={}));const r={allowedSenderOrigin:void 0,debugMode:!1};t.default=class{constructor(e,t,s,o,n={}){this.callbacks=new Map,this.serviceMethods=new Map,this.name=e,this.options={...r,...n},this.log(\"Created connection w/ allowedOrigin:\",this.options.allowedSenderOrigin),this.serviceMethods=new Map(Object.entries(s));const[i]=crypto.getRandomValues(new Uint32Array(1));this.incrementalID=i,this.postMessageInternal=t,o((e=>this.onMessageListener(e)))}defineMethods(e){this.serviceMethods=new Map(Object.entries(e))}callRemoteMethod(e,...t){return this.log(\"Calling Remote Method\",{name:e,args:t}),new Promise(((r,o)=>{const n=this.registerCallback(r,o);this.postMessage({callId:n,type:s.MESSAGE,methodName:e,arguments:t})}))}onMessageListener(e){this.log(\"Received message\",e);const{data:t}=e,{allowedSenderOrigin:r}=this.options;switch(r&&e.origin!==r&&console.warn(`Received message from invalid origin: ${e.origin}`),t.type){case s.RESPONSE:return void this.popCallback(t.callId,t.success,t.result);case s.MESSAGE:this.callLocalMethod(t.methodName,t.arguments).then((e=>this.responseOtherSide(t.callId,e))).catch((e=>this.responseOtherSide(t.callId,e,!1)))}}async callLocalMethod(e,t){this.log(\"calling local method\",e,t);const s=this.serviceMethods.get(e);if(!s)throw new Error(`service method ${e} not found`);return s(...t)}responseOtherSide(e,t,r=!0){this.log(\"responding to remote call\",{id:e,result:t,success:r});const o=t=>{this.postMessage({callId:e,type:s.RESPONSE,success:r,result:t})};try{o(t)}catch(e){e instanceof DOMException&&o(JSON.parse(JSON.stringify(t)))}}registerCallback(e,t){const s=(++this.incrementalID).toString();return this.log(\"registering callback for id\",s),this.callbacks.set(s,{success:e,failure:t}),s}popCallback(e,t,s){this.log(\"calling callback for id\",e,{success:t,result:s});const r=this.callbacks.get(e);t?r?.success(s):r?.failure(s),this.callbacks.delete(e)}postMessage(e,t=\"*\"){this.log(\"sending message\",{data:e,targetOrigin:t}),this.postMessageInternal(e,t)}log(...e){this.options.debugMode&&console.debug(`[${this.name}]`,...e)}}},306:function(e,t,s){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,\"__esModule\",{value:!0});const o=r(s(880)),n=r(s(54)),i=new class{constructor(){const e=!!window?.debugMode;this.connection=new o.default(\"FRAME\",window.parent.postMessage.bind(window.parent),{startTask:this.runCode.bind(this)},(e=>{window.addEventListener(\"message\",e)}),{debugMode:e}),this.connection.callRemoteMethod(\"iframeInitialised\"),this.workerManager=new n.default}async runCode(e){return this.workerManager.execute(e)}};t.default=i},54:function(e,t,s){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,\"__esModule\",{value:!0});const o=r(s(420));t.default=o.default},420:function(e,t,s){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,\"__esModule\",{value:!0});const o=r(s(967));t.default=class{constructor(){this.worker=this.createWorker()}async execute(e){const t={...e,contextVariable:e.contextVariable??\"ctx\"},s=this.runTask(t),r=this.timeout(e.timeout??1e3);return Promise.race([s,r]).finally((()=>{this.removeMessageListener()}))}terminate(){this.removeMessageListener(),this.worker.terminate(),this.worker=this.createWorker()}runTask(e){return new Promise(((t,s)=>{const r=r=>{const{data:o}=r;o.id===e.id&&(this.stopTimer(),o.success?t(o.result):s(o.result))};this.worker.addEventListener(\"message\",r),this.removeMessageListener=()=>this.worker.removeEventListener(\"message\",r),this.worker.postMessage(e)}))}timeout(e){return new Promise(((t,s)=>{this.timerId=window.setTimeout((()=>{this.terminate(),s(new Error(\"maximum execution time exceeded\"))}),e)}))}stopTimer(){clearTimeout(this.timerId)}createWorker(){const e=URL.createObjectURL(new Blob([o.default],{type:\"application/javascript\"}));return new Worker(e)}removeMessageListener(){}}},967:(e,t)=>{Object.defineProperty(t,\"__esModule\",{value:!0}),t.default='(()=>{\"use strict\";(()=>{const e=[\"TEMPORARY\",\"PERSISTENT\",\"console\",\"self\",\"onmessage\",\"postMessage\",\"global\",\"allowed\",\"Array\",\"Boolean\",\"Date\",\"Function\",\"Number\",\"Object\",\"RegExp\",\"String\",\"Error\",\"EvalError\",\"RangeError\",\"ReferenceError\",\"SyntaxError\",\"TypeError\",\"URIError\",\"decodeURI\",\"decodeURIComponent\",\"encodeURI\",\"encodeURIComponent\",\"isFinite\",\"isNaN\",\"parseFloat\",\"parseInt\",\"Infinity\",\"JSON\",\"Math\",\"NaN\",\"undefined\"];[...Object.getOwnPropertyNames(self),...Object.getOwnPropertyNames(self.__proto__)].forEach((r=>{e.includes(r)||Object.defineProperty(self,r,{get:()=>{throw new Error(`Security Exception: cannot access ${r}`)},set:()=>{throw new Error(`Security Exception: cannot set ${r}`)},configurable:!1})})),onmessage=e=>{let r,t=!0;try{r=Function(e.data.contextVariable,`\"use strict\";return (${e.data.code});`)(e.data.context)}catch(e){r=e,t=!1}const o={id:e.data.id,result:r,success:t};postMessage(o)}})()})();'}},t={};!function s(r){var o=t[r];if(void 0!==o)return o.exports;var n=t[r]={exports:{}};return e[r].call(n.exports,n,n.exports,s),n.exports}(306)})();";
|
4
|
+
export default source;
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { Task } from '../../types';
|
2
|
+
export default class Manager {
|
3
|
+
private worker;
|
4
|
+
private timerId?;
|
5
|
+
constructor();
|
6
|
+
execute(task: Task): Promise<unknown>;
|
7
|
+
terminate(): void;
|
8
|
+
private runTask;
|
9
|
+
private timeout;
|
10
|
+
private stopTimer;
|
11
|
+
private createWorker;
|
12
|
+
private removeMessageListener;
|
13
|
+
}
|
@@ -0,0 +1,59 @@
|
|
1
|
+
import workerSource from './worker.source';
|
2
|
+
export default class Manager {
|
3
|
+
constructor() {
|
4
|
+
this.worker = this.createWorker();
|
5
|
+
}
|
6
|
+
async execute(task) {
|
7
|
+
const workerTask = {
|
8
|
+
...task,
|
9
|
+
contextVariable: task.contextVariable ?? 'ctx',
|
10
|
+
};
|
11
|
+
const result = this.runTask(workerTask);
|
12
|
+
const timeout = this.timeout(task.timeout ?? 1000);
|
13
|
+
return Promise.race([result, timeout]).finally(() => {
|
14
|
+
this.removeMessageListener();
|
15
|
+
});
|
16
|
+
}
|
17
|
+
terminate() {
|
18
|
+
this.removeMessageListener();
|
19
|
+
this.worker.terminate();
|
20
|
+
this.worker = this.createWorker();
|
21
|
+
}
|
22
|
+
runTask(task) {
|
23
|
+
return new Promise((resolve, reject) => {
|
24
|
+
const listener = (event) => {
|
25
|
+
const { data } = event;
|
26
|
+
if (data.id === task.id) {
|
27
|
+
this.stopTimer();
|
28
|
+
if (data.success) {
|
29
|
+
resolve(data.result);
|
30
|
+
}
|
31
|
+
else {
|
32
|
+
reject(data.result);
|
33
|
+
}
|
34
|
+
}
|
35
|
+
};
|
36
|
+
this.worker.addEventListener('message', listener);
|
37
|
+
this.removeMessageListener = () => this.worker.removeEventListener('message', listener);
|
38
|
+
this.worker.postMessage(task);
|
39
|
+
});
|
40
|
+
}
|
41
|
+
timeout(timeoutMs) {
|
42
|
+
return new Promise((_, reject) => {
|
43
|
+
this.timerId = window.setTimeout(() => {
|
44
|
+
this.terminate();
|
45
|
+
reject(new Error('maximum execution time exceeded'));
|
46
|
+
}, timeoutMs);
|
47
|
+
});
|
48
|
+
}
|
49
|
+
stopTimer() {
|
50
|
+
clearTimeout(this.timerId);
|
51
|
+
}
|
52
|
+
createWorker() {
|
53
|
+
const blob = URL.createObjectURL(new Blob([workerSource], { type: 'application/javascript' }));
|
54
|
+
return new Worker(blob);
|
55
|
+
}
|
56
|
+
removeMessageListener() {
|
57
|
+
// replaced by constructor
|
58
|
+
}
|
59
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1,74 @@
|
|
1
|
+
const WHITELIST = [
|
2
|
+
'TEMPORARY',
|
3
|
+
'PERSISTENT',
|
4
|
+
'console',
|
5
|
+
'self',
|
6
|
+
'onmessage',
|
7
|
+
'postMessage',
|
8
|
+
'global',
|
9
|
+
'allowed',
|
10
|
+
'Array',
|
11
|
+
'Boolean',
|
12
|
+
'Date',
|
13
|
+
'Function',
|
14
|
+
'Number',
|
15
|
+
'Object',
|
16
|
+
'RegExp',
|
17
|
+
'String',
|
18
|
+
'Error',
|
19
|
+
'EvalError',
|
20
|
+
'RangeError',
|
21
|
+
'ReferenceError',
|
22
|
+
'SyntaxError',
|
23
|
+
'TypeError',
|
24
|
+
'URIError',
|
25
|
+
'decodeURI',
|
26
|
+
'decodeURIComponent',
|
27
|
+
'encodeURI',
|
28
|
+
'encodeURIComponent',
|
29
|
+
'isFinite',
|
30
|
+
'isNaN',
|
31
|
+
'parseFloat',
|
32
|
+
'parseInt',
|
33
|
+
'Infinity',
|
34
|
+
'JSON',
|
35
|
+
'Math',
|
36
|
+
'NaN',
|
37
|
+
'undefined',
|
38
|
+
];
|
39
|
+
const windowProps = Object.getOwnPropertyNames(self);
|
40
|
+
const protoProps = Object.getOwnPropertyNames(self.__proto__);
|
41
|
+
const props = [...windowProps, ...protoProps];
|
42
|
+
props.forEach((prop) => {
|
43
|
+
if (!WHITELIST.includes(prop)) {
|
44
|
+
Object.defineProperty(self, prop, {
|
45
|
+
get: () => {
|
46
|
+
throw new Error(`Security Exception: cannot access ${prop}`);
|
47
|
+
},
|
48
|
+
set: () => {
|
49
|
+
throw new Error(`Security Exception: cannot set ${prop}`);
|
50
|
+
},
|
51
|
+
configurable: false,
|
52
|
+
});
|
53
|
+
}
|
54
|
+
});
|
55
|
+
// Parses and calls function string with args
|
56
|
+
onmessage = (event) => {
|
57
|
+
let result;
|
58
|
+
let success = true;
|
59
|
+
try {
|
60
|
+
const fn = Function(event.data.contextVariable, `"use strict";return (${event.data.code});`);
|
61
|
+
result = fn(event.data.context);
|
62
|
+
}
|
63
|
+
catch (e) {
|
64
|
+
result = e;
|
65
|
+
success = false;
|
66
|
+
}
|
67
|
+
const message = {
|
68
|
+
id: event.data.id,
|
69
|
+
result,
|
70
|
+
success,
|
71
|
+
};
|
72
|
+
postMessage(message);
|
73
|
+
};
|
74
|
+
export {};
|
@@ -0,0 +1,4 @@
|
|
1
|
+
// Auto-generated file
|
2
|
+
/* eslint-disable */
|
3
|
+
const source = "(()=>{\"use strict\";(()=>{const e=[\"TEMPORARY\",\"PERSISTENT\",\"console\",\"self\",\"onmessage\",\"postMessage\",\"global\",\"allowed\",\"Array\",\"Boolean\",\"Date\",\"Function\",\"Number\",\"Object\",\"RegExp\",\"String\",\"Error\",\"EvalError\",\"RangeError\",\"ReferenceError\",\"SyntaxError\",\"TypeError\",\"URIError\",\"decodeURI\",\"decodeURIComponent\",\"encodeURI\",\"encodeURIComponent\",\"isFinite\",\"isNaN\",\"parseFloat\",\"parseInt\",\"Infinity\",\"JSON\",\"Math\",\"NaN\",\"undefined\"];[...Object.getOwnPropertyNames(self),...Object.getOwnPropertyNames(self.__proto__)].forEach((r=>{e.includes(r)||Object.defineProperty(self,r,{get:()=>{throw new Error(`Security Exception: cannot access ${r}`)},set:()=>{throw new Error(`Security Exception: cannot set ${r}`)},configurable:!1})})),onmessage=e=>{let r,t=!0;try{r=Function(e.data.contextVariable,`\"use strict\";return (${e.data.code});`)(e.data.context)}catch(e){r=e,t=!1}const o={id:e.data.id,result:r,success:t};postMessage(o)}})()})();";
|
4
|
+
export default source;
|
@@ -0,0 +1,48 @@
|
|
1
|
+
export interface SandboxOptions extends Required<RunCodeOptions> {
|
2
|
+
/** The selector or element to append the iframe to */
|
3
|
+
frameContainer: string | Element;
|
4
|
+
/** Whether to enable verbose logging */
|
5
|
+
debugMode: boolean;
|
6
|
+
}
|
7
|
+
export interface RunCodeOptions {
|
8
|
+
/** The name of the variable to use for the context object */
|
9
|
+
contextVariable?: string;
|
10
|
+
timeout?: {
|
11
|
+
/** The minimum time to wait for a task to complete
|
12
|
+
*
|
13
|
+
* The worker will attempt to kill the task after this time.
|
14
|
+
*/
|
15
|
+
minimumMs: number;
|
16
|
+
/** The maximum time to wait for a task to complete
|
17
|
+
*
|
18
|
+
* The sandbox will be destroyed and rebuilt after this time.
|
19
|
+
*/
|
20
|
+
maximumMs: number;
|
21
|
+
};
|
22
|
+
}
|
23
|
+
export declare const BaseOptions: SandboxOptions;
|
24
|
+
export interface SandboxMethods {
|
25
|
+
iframeInitialised: () => void;
|
26
|
+
}
|
27
|
+
export default class WebSandbox {
|
28
|
+
private options;
|
29
|
+
private frame;
|
30
|
+
private connection?;
|
31
|
+
initialised: Promise<WebSandbox>;
|
32
|
+
static new(options?: Partial<SandboxOptions>): Promise<WebSandbox>;
|
33
|
+
private constructor();
|
34
|
+
/** Runs code in the sandbox. Can be either a string or a function */
|
35
|
+
run<T = unknown>(code: string | (() => T), context?: unknown, options?: RunCodeOptions): Promise<T>;
|
36
|
+
/** Destroys the sandbox.
|
37
|
+
*
|
38
|
+
* This will remove the iframe from the DOM and remove the message listener.
|
39
|
+
*/
|
40
|
+
destroy(): void;
|
41
|
+
private initialise;
|
42
|
+
private destroyAndRebuild;
|
43
|
+
private removeMessageListener;
|
44
|
+
private runFunction;
|
45
|
+
private runCode;
|
46
|
+
private createFrame;
|
47
|
+
private prepareFrameContent;
|
48
|
+
}
|
@@ -0,0 +1,116 @@
|
|
1
|
+
import Connection from './connection';
|
2
|
+
import frameSource from './frame';
|
3
|
+
import { generateUUID } from '../generateUUID';
|
4
|
+
export const BaseOptions = {
|
5
|
+
frameContainer: 'body',
|
6
|
+
debugMode: false,
|
7
|
+
contextVariable: 'ctx',
|
8
|
+
timeout: {
|
9
|
+
minimumMs: 500,
|
10
|
+
maximumMs: 1000,
|
11
|
+
},
|
12
|
+
};
|
13
|
+
export default class WebSandbox {
|
14
|
+
static async new(options = {}) {
|
15
|
+
return new WebSandbox(options).initialised;
|
16
|
+
}
|
17
|
+
constructor(options) {
|
18
|
+
this.options = { ...BaseOptions, ...options };
|
19
|
+
this.frame = this.createFrame();
|
20
|
+
this.initialised = this.initialise().then(() => this);
|
21
|
+
}
|
22
|
+
/** Runs code in the sandbox. Can be either a string or a function */
|
23
|
+
async run(code, context, options) {
|
24
|
+
if (typeof code === 'function') {
|
25
|
+
return this.runFunction(code, context);
|
26
|
+
}
|
27
|
+
return this.runCode(code, context, options);
|
28
|
+
}
|
29
|
+
/** Destroys the sandbox.
|
30
|
+
*
|
31
|
+
* This will remove the iframe from the DOM and remove the message listener.
|
32
|
+
*/
|
33
|
+
destroy() {
|
34
|
+
this.frame.remove();
|
35
|
+
this.removeMessageListener();
|
36
|
+
}
|
37
|
+
initialise() {
|
38
|
+
return new Promise((resolve) => {
|
39
|
+
this.connection = new Connection('SANDBOX', this.frame.contentWindow.postMessage.bind(this.frame.contentWindow), {
|
40
|
+
iframeInitialised: () => resolve(),
|
41
|
+
}, (listener) => {
|
42
|
+
const sourceCheckListener = (event) => {
|
43
|
+
if (event.source !== this.frame.contentWindow) {
|
44
|
+
return;
|
45
|
+
}
|
46
|
+
return listener(event);
|
47
|
+
};
|
48
|
+
window.addEventListener('message', sourceCheckListener);
|
49
|
+
this.removeMessageListener = () => window.removeEventListener('message', sourceCheckListener);
|
50
|
+
}, { allowedSenderOrigin: 'null', debugMode: this.options.debugMode });
|
51
|
+
});
|
52
|
+
}
|
53
|
+
async destroyAndRebuild() {
|
54
|
+
this.destroy();
|
55
|
+
this.frame = this.createFrame();
|
56
|
+
await this.initialise();
|
57
|
+
}
|
58
|
+
removeMessageListener() {
|
59
|
+
// replaced by constructor
|
60
|
+
}
|
61
|
+
async runFunction(fn, context, options) {
|
62
|
+
return this.runCode(`(${fn.toString()})()`, context, options);
|
63
|
+
}
|
64
|
+
async runCode(code, context, options) {
|
65
|
+
if (!this.connection) {
|
66
|
+
throw new Error('sandbox not initialised');
|
67
|
+
}
|
68
|
+
let timerId;
|
69
|
+
const timeout = new Promise((_, reject) => {
|
70
|
+
setTimeout(() => {
|
71
|
+
this.destroyAndRebuild().finally(() => reject(new Error('sandbox timed out')));
|
72
|
+
}, options?.timeout?.maximumMs ?? this.options.timeout.maximumMs);
|
73
|
+
});
|
74
|
+
const taskId = generateUUID();
|
75
|
+
const task = await this.connection.callRemoteMethod('startTask', {
|
76
|
+
id: taskId,
|
77
|
+
code,
|
78
|
+
context,
|
79
|
+
timeout: this.options.timeout.minimumMs,
|
80
|
+
contextVariable: options?.contextVariable ?? this.options.contextVariable,
|
81
|
+
});
|
82
|
+
return Promise.race([task, timeout]).finally(() => {
|
83
|
+
clearTimeout(timerId);
|
84
|
+
});
|
85
|
+
}
|
86
|
+
createFrame() {
|
87
|
+
const containerSelector = this.options.frameContainer;
|
88
|
+
const container = typeof containerSelector === 'string' ? document.querySelector(containerSelector) : containerSelector;
|
89
|
+
if (!container) {
|
90
|
+
throw new Error('unable to find container for sandbox');
|
91
|
+
}
|
92
|
+
const iframe = document.createElement('iframe');
|
93
|
+
iframe.sandbox.add('allow-scripts');
|
94
|
+
iframe.srcdoc = this.prepareFrameContent();
|
95
|
+
iframe.style.display = 'none';
|
96
|
+
container.appendChild(iframe);
|
97
|
+
return iframe;
|
98
|
+
}
|
99
|
+
prepareFrameContent() {
|
100
|
+
let script = frameSource;
|
101
|
+
if (this.options.debugMode) {
|
102
|
+
script = `window.debugMode=true;${script}`;
|
103
|
+
}
|
104
|
+
return `
|
105
|
+
<!DOCTYPE html>
|
106
|
+
<html>
|
107
|
+
<head>
|
108
|
+
<meta charset="UTF-8">
|
109
|
+
<script>
|
110
|
+
${script}
|
111
|
+
</script>
|
112
|
+
</head>
|
113
|
+
</html>
|
114
|
+
`;
|
115
|
+
}
|
116
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
type APIMethod = (...args: any[]) => unknown;
|
2
|
+
export type API = Map<string, APIMethod>;
|
3
|
+
export type APIDeclaration<T> = {
|
4
|
+
[K in keyof T]: T[K] extends APIMethod ? T[K] : never;
|
5
|
+
};
|
6
|
+
export interface Task {
|
7
|
+
id: string;
|
8
|
+
code: string;
|
9
|
+
context?: unknown;
|
10
|
+
timeout: number;
|
11
|
+
contextVariable?: string;
|
12
|
+
}
|
13
|
+
export {};
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@dronedeploy/rocos-js-sdk",
|
3
|
-
"version": "3.0.
|
3
|
+
"version": "3.0.1-alpha",
|
4
4
|
"description": "Javascript SDK for rocos",
|
5
5
|
"main": "index.js",
|
6
6
|
"types": "index.d.ts",
|
@@ -23,5 +23,7 @@
|
|
23
23
|
"rxjs": "^6.6.6 || ^7.0.0"
|
24
24
|
},
|
25
25
|
"repository": {},
|
26
|
-
"private": false
|
26
|
+
"private": false,
|
27
|
+
"type": "module",
|
28
|
+
"sideEffects": false
|
27
29
|
}
|
@@ -110,7 +110,7 @@ export class AssetStorageService extends BaseServiceAbstract {
|
|
110
110
|
*/
|
111
111
|
async listMissionAssets(projectId, assetIdList) {
|
112
112
|
const searchParams = new URLSearchParams();
|
113
|
-
assetIdList.forEach((assetId) => searchParams.append('
|
113
|
+
assetIdList.forEach((assetId) => searchParams.append('assetID', assetId));
|
114
114
|
return this.callGet(formatServiceUrl(API_PROJECT_MISSION_ASSETS_PATH_URL, { url: this.config.url, projectId }, this.config.insecure), `Failed to get asset for ${projectId}, assetIdList ${assetIdList}.`, searchParams);
|
115
115
|
}
|
116
116
|
/**
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { IRocosSDKConfig, RocosError } from '../models';
|
2
|
+
import { QueryParams } from '../helpers';
|
2
3
|
import { Logger } from 'loglevel';
|
3
|
-
type QueryParams = Record<string, string | number | boolean> | URLSearchParams;
|
4
4
|
type ResponseType = 'json' | 'blob' | 'stream' | 'text' | 'raw';
|
5
5
|
interface RequestConfig {
|
6
6
|
/**
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { RocosError } from '../models';
|
2
|
+
import { getURLSearchParams } from '../helpers';
|
2
3
|
import { RocosStore } from '../store/RocosStore';
|
3
4
|
class HttpError extends Error {
|
4
5
|
constructor(response) {
|
@@ -26,12 +27,7 @@ export class BaseServiceAbstract {
|
|
26
27
|
async call(url, method, options) {
|
27
28
|
const { errorMessage, config, payload, params } = options;
|
28
29
|
try {
|
29
|
-
|
30
|
-
const stringParams = Object.entries(params ?? {}).reduce((acc, [key, value]) => {
|
31
|
-
acc[key] = value.toString();
|
32
|
-
return acc;
|
33
|
-
}, {});
|
34
|
-
const formattedUrl = params ? `${url}?${new URLSearchParams(stringParams)}` : url;
|
30
|
+
const formattedUrl = params ? `${url}?${getURLSearchParams(params)}` : url;
|
35
31
|
const defaultHeaders = {};
|
36
32
|
if (!config?.public) {
|
37
33
|
const token = await RocosStore.getSDKInstance(this.config).getAuthService().getToken();
|
@@ -1,6 +1,6 @@
|
|
1
|
-
import { TelemetryService } from './TelemetryService';
|
2
|
-
import { delay, from, lastValueFrom, NEVER, take, throwError } from 'rxjs';
|
3
1
|
import { CallsignStatus } from '../models';
|
2
|
+
import { NEVER, delay, from, lastValueFrom, take, throwError } from 'rxjs';
|
3
|
+
import { TelemetryService } from './TelemetryService';
|
4
4
|
describe('TelemetryService', () => {
|
5
5
|
describe('getRobotStatusChanges', () => {
|
6
6
|
it('should emit unknown to begin with', async () => {
|