@dronedeploy/rocos-js-sdk 3.0.0-alpha.8 → 3.0.1-alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. package/helpers/getURLSearchParams.d.ts +2 -0
  2. package/helpers/getURLSearchParams.js +9 -0
  3. package/helpers/getURLSearchParams.spec.d.ts +1 -0
  4. package/helpers/getURLSearchParams.spec.js +19 -0
  5. package/helpers/index.d.ts +2 -0
  6. package/helpers/index.js +2 -0
  7. package/helpers/websandbox/connection.d.ts +72 -0
  8. package/helpers/websandbox/connection.js +140 -0
  9. package/helpers/websandbox/frame/frame.d.ts +12 -0
  10. package/helpers/websandbox/frame/frame.js +22 -0
  11. package/helpers/websandbox/frame/frame.source.d.ts +2 -0
  12. package/helpers/websandbox/frame/frame.source.js +4 -0
  13. package/helpers/websandbox/frame/index.d.ts +2 -0
  14. package/helpers/websandbox/frame/index.js +2 -0
  15. package/helpers/websandbox/frame/worker/index.d.ts +2 -0
  16. package/helpers/websandbox/frame/worker/index.js +2 -0
  17. package/helpers/websandbox/frame/worker/manager.d.ts +13 -0
  18. package/helpers/websandbox/frame/worker/manager.js +59 -0
  19. package/helpers/websandbox/frame/worker/types.d.ts +11 -0
  20. package/helpers/websandbox/frame/worker/types.js +1 -0
  21. package/helpers/websandbox/frame/worker/worker.d.ts +1 -0
  22. package/helpers/websandbox/frame/worker/worker.js +74 -0
  23. package/helpers/websandbox/frame/worker/worker.source.d.ts +2 -0
  24. package/helpers/websandbox/frame/worker/worker.source.js +4 -0
  25. package/helpers/websandbox/index.d.ts +2 -0
  26. package/helpers/websandbox/index.js +2 -0
  27. package/helpers/websandbox/sandbox.d.ts +48 -0
  28. package/helpers/websandbox/sandbox.js +116 -0
  29. package/helpers/websandbox/types.d.ts +13 -0
  30. package/helpers/websandbox/types.js +1 -0
  31. package/package.json +1 -1
  32. package/services/AssetStorageService.js +1 -1
  33. package/services/BaseServiceAbstract.d.ts +1 -1
  34. package/services/BaseServiceAbstract.js +2 -6
  35. package/services/TelemetryService.spec.js +2 -2
@@ -0,0 +1,2 @@
1
+ export type QueryParams = Record<string, string | number | boolean> | URLSearchParams;
2
+ export declare const getURLSearchParams: (params: QueryParams) => URLSearchParams;
@@ -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
+ });
@@ -1,3 +1,5 @@
1
1
  export * from './average';
2
2
  export * from './standardDeviation';
3
3
  export * from './generateUUID';
4
+ export * from './getURLSearchParams';
5
+ export * from './websandbox';
package/helpers/index.js CHANGED
@@ -1,3 +1,5 @@
1
1
  export * from './average';
2
2
  export * from './standardDeviation';
3
3
  export * from './generateUUID';
4
+ export * from './getURLSearchParams';
5
+ export * from './websandbox';
@@ -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,2 @@
1
+ declare const source: string;
2
+ export default source;
@@ -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,2 @@
1
+ import source from './frame.source';
2
+ export default source;
@@ -0,0 +1,2 @@
1
+ import source from './frame.source';
2
+ export default source;
@@ -0,0 +1,2 @@
1
+ import Manager from './manager';
2
+ export default Manager;
@@ -0,0 +1,2 @@
1
+ import Manager from './manager';
2
+ export default Manager;
@@ -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,11 @@
1
+ export interface WorkerResult {
2
+ id: string;
3
+ result: unknown;
4
+ success: boolean;
5
+ }
6
+ export interface WorkerTask {
7
+ id: string;
8
+ code: string;
9
+ contextVariable: string;
10
+ context?: unknown;
11
+ }
@@ -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,2 @@
1
+ declare const source: string;
2
+ export default source;
@@ -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,2 @@
1
+ import WebSandbox from './sandbox';
2
+ export { WebSandbox };
@@ -0,0 +1,2 @@
1
+ import WebSandbox from './sandbox';
2
+ export { WebSandbox };
@@ -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.0-alpha.8",
3
+ "version": "3.0.1-alpha",
4
4
  "description": "Javascript SDK for rocos",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -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('assetId', assetId));
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
- // change all params to string
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 () => {