-
-
Notifications
You must be signed in to change notification settings - Fork 314
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
### Fixes # <!-- Mention the issues this PR addresses --> ### Checks - [ ] Ran `yarn test-build` - [ ] Updated relevant documentations - [ ] Updated matching config options in altair-static ### Changes proposed in this pull request: <!-- Describe the changes being introduced in this PR -->
- Loading branch information
Showing
23 changed files
with
892 additions
and
314 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
packages/altair-app/src/app/modules/altair/services/pre-request/evaluator-worker.factory.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export class ScriptEvaluatorWorkerFactory { | ||
create() { | ||
return new Worker(new URL('./evaluator.worker', import.meta.url), { | ||
type: 'module', | ||
}); | ||
} | ||
} |
103 changes: 103 additions & 0 deletions
103
packages/altair-app/src/app/modules/altair/services/pre-request/evaluator.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import { debug } from '../../utils/logger'; | ||
import { ScriptEvaluatorWorkerFactory } from './evaluator-worker.factory'; | ||
import { | ||
AllScriptEventHandlers, | ||
getErrorEvent, | ||
getResponseEvent, | ||
ScriptEvent, | ||
ScriptEventData, | ||
ScriptEventHandlers, | ||
SCRIPT_INIT_EXECUTE, | ||
} from './events'; | ||
import { ScriptContextData } from './helpers'; | ||
|
||
export class ScriptEvaluator { | ||
timeout = 1000 * 60 * 5; // 5 minutes | ||
private worker?: Worker; | ||
|
||
private getWorker() { | ||
if (!this.worker) { | ||
this.worker = new ScriptEvaluatorWorkerFactory().create(); | ||
} | ||
return this.worker; | ||
} | ||
|
||
async executeScript( | ||
script: string, | ||
data: ScriptContextData, | ||
handlers: ScriptEventHandlers | ||
): Promise<ScriptContextData> { | ||
try { | ||
const worker = this.getWorker(); | ||
const result = await new Promise<ScriptContextData>((resolve, reject) => { | ||
// Handle timeout | ||
const handle = setTimeout(() => { | ||
this.killWorker(); | ||
reject(new Error('script timeout')); | ||
}, this.timeout); | ||
|
||
const allHandlers: AllScriptEventHandlers = { | ||
...handlers, | ||
executeComplete: (data: ScriptContextData) => { | ||
clearTimeout(handle); | ||
resolve(data); | ||
}, | ||
scriptError: (err: Error) => { | ||
clearTimeout(handle); | ||
reject(err); | ||
}, | ||
} as const; | ||
|
||
// loop over all the script event handlers and create a listener for each | ||
// TODO: fn is of any type here. Figure out the typing | ||
Object.entries(allHandlers).forEach(([key, fn]) => { | ||
worker.addEventListener( | ||
'message', | ||
<T extends ScriptEvent>(e: MessageEvent<ScriptEventData<T>>) => { | ||
const event = e.data; | ||
|
||
// Handle script events | ||
if (event.type === key) { | ||
debug.log(event.type, event); | ||
// TODO: handle cancelling requests | ||
const { id, args } = event.payload; | ||
(async () => { | ||
try { | ||
const res = await fn(...args); | ||
worker.postMessage({ | ||
type: getResponseEvent(key), | ||
payload: { id, response: res }, | ||
}); | ||
} catch (err) { | ||
worker.postMessage({ | ||
type: getErrorEvent(key), | ||
payload: { id, error: err }, | ||
}); | ||
} | ||
})(); | ||
} | ||
} | ||
); | ||
}); | ||
|
||
worker.onerror = (e) => { | ||
clearTimeout(handle); | ||
reject(e); | ||
}; | ||
worker.postMessage({ | ||
type: SCRIPT_INIT_EXECUTE, | ||
payload: [script, data], | ||
}); | ||
}); | ||
|
||
return result; | ||
} finally { | ||
this.killWorker(); | ||
} | ||
} | ||
|
||
private killWorker() { | ||
this.worker?.terminate(); | ||
this.worker = undefined; | ||
} | ||
} |
114 changes: 114 additions & 0 deletions
114
packages/altair-app/src/app/modules/altair/services/pre-request/evaluator.worker.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import { v4 as uuid } from 'uuid'; | ||
import { ScriptEvaluator } from './evaluator'; | ||
import { | ||
AllScriptEventHandlers, | ||
ScriptEvent, | ||
ScriptEventParameters, | ||
SCRIPT_INIT_EXECUTE, | ||
} from './events'; | ||
import { getGlobalContext, ScriptContextData } from './helpers'; | ||
|
||
onmessage = async (e) => { | ||
switch (e.data.type) { | ||
case SCRIPT_INIT_EXECUTE: | ||
try { | ||
await initExecute(e.data.payload); | ||
} catch (err) { | ||
makeCall('scriptError', err as Error); | ||
} | ||
break; | ||
} | ||
}; | ||
|
||
const workerHandlerNames = [ | ||
'setCookie', | ||
'request', | ||
'getStorageItem', | ||
'setStorageItem', | ||
] as const; | ||
export type WorkerHandlerNames = typeof workerHandlerNames[number]; | ||
|
||
const initExecute = async ( | ||
payload: Parameters<ScriptEvaluator['executeScript']> | ||
) => { | ||
const [script, data] = payload; | ||
const res = await new Promise<ScriptContextData>((resolve, reject) => { | ||
self.addEventListener('unhandledrejection', (e) => { | ||
e.preventDefault(); | ||
return reject(e.reason); | ||
}); | ||
|
||
const clonedMutableData: ScriptContextData = JSON.parse( | ||
JSON.stringify(data) | ||
); | ||
|
||
// build handlers | ||
const handlers = workerHandlerNames.reduce( | ||
<T extends WorkerHandlerNames>( | ||
acc: Pick<AllScriptEventHandlers, WorkerHandlerNames>, | ||
key: T | ||
) => { | ||
acc[key] = ((...args: ScriptEventParameters<T>) => { | ||
return makeCall(key, ...args); | ||
}) as unknown as AllScriptEventHandlers[T]; // TODO: Look into this typing issue. | ||
return acc; | ||
}, | ||
{} as Pick<AllScriptEventHandlers, WorkerHandlerNames> | ||
); | ||
|
||
const context = { | ||
altair: getGlobalContext(clonedMutableData, handlers), | ||
alert, | ||
}; | ||
|
||
const contextEntries = Object.entries(context); | ||
try { | ||
const res = function () { | ||
return eval(` | ||
(async(${contextEntries.map((e) => e[0]).join(',')}) => { | ||
${script}; | ||
return altair.data; | ||
})(...this.__ctxE.map(e => e[1])); | ||
`); | ||
}.call({ __ctxE: contextEntries }); | ||
return resolve(res); | ||
} catch (e) { | ||
return reject(e); | ||
} | ||
}); | ||
|
||
makeCall('executeComplete', res); | ||
}; | ||
|
||
const alert = (msg: string) => makeCall('alert', msg); | ||
|
||
const makeCall = <T extends ScriptEvent>( | ||
type: T, | ||
...args: Parameters<AllScriptEventHandlers[T]> | ||
) => { | ||
return new Promise<ReturnType<AllScriptEventHandlers[T]>>( | ||
(resolve, reject) => { | ||
const id = uuid(); | ||
const event = { | ||
type, | ||
payload: { id, args }, | ||
}; | ||
// TODO: cleanup listener | ||
addEventListener('message', (e) => { | ||
switch (e.data.type) { | ||
case `${type}_response`: | ||
if (e.data.payload.id !== id) { | ||
return; | ||
} | ||
return resolve(e.data.payload.response); | ||
case `${type}_error`: | ||
if (e.data.payload.id !== id) { | ||
return; | ||
} | ||
return reject(e.data.payload.error); | ||
} | ||
}); | ||
self.postMessage(event); | ||
} | ||
); | ||
}; |
Oops, something went wrong.