Spaces:
Running
Running
const BlockType = require('../../extension-support/block-type'); | |
const ArgumentType = require('../../extension-support/argument-type'); | |
const SandboxRunner = require('../../util/sandboxed-javascript-runner'); | |
const Cast = require('../../util/cast'); | |
/** | |
* Class | |
* oh yea you cant access util in the runner anymore | |
* im not adding it because im done with implementing eval in PM since it was done like 3 times | |
* @constructor | |
*/ | |
class jgJavascript { | |
constructor(runtime) { | |
/** | |
* The runtime instantiating this block package. | |
* @type {runtime} | |
*/ | |
this.runtime = runtime; | |
this.runningEditorUnsandboxed = false; | |
} | |
/** | |
* @returns {object} metadata for this extension and its blocks. | |
*/ | |
getInfo() { | |
return { | |
id: 'jgJavascript', | |
name: 'JavaScript', | |
isDynamic: true, | |
// color1: '#EFC900', look like doo doo | |
blocks: [ | |
{ | |
opcode: 'unsandbox', | |
text: 'Run Unsandboxed', | |
blockType: BlockType.BUTTON, | |
hideFromPalette: this.runningEditorUnsandboxed | |
}, | |
{ | |
opcode: 'sandbox', | |
text: 'Run Sandboxed', | |
blockType: BlockType.BUTTON, | |
hideFromPalette: !this.runningEditorUnsandboxed | |
}, | |
{ | |
opcode: 'javascriptHat', | |
text: 'when javascript [CODE] == true', | |
blockType: BlockType.HAT, | |
hideFromPalette: !this.runningEditorUnsandboxed, // this block seems to cause strange behavior because of how sandboxed eval is done | |
arguments: { | |
CODE: { | |
type: ArgumentType.STRING, | |
defaultValue: "Math.round(Math.random()) === 1" | |
} | |
} | |
}, | |
{ | |
opcode: 'javascriptStack', | |
text: 'javascript [CODE]', | |
blockType: BlockType.COMMAND, | |
arguments: { | |
CODE: { | |
type: ArgumentType.STRING, | |
defaultValue: "alert('Hello!')" | |
} | |
} | |
}, | |
{ | |
opcode: 'javascriptString', | |
text: 'javascript [CODE]', | |
blockType: BlockType.REPORTER, | |
disableMonitor: true, | |
arguments: { | |
CODE: { | |
type: ArgumentType.STRING, | |
defaultValue: "Math.random()" | |
} | |
} | |
}, | |
{ | |
opcode: 'javascriptBool', | |
text: 'javascript [CODE]', | |
blockType: BlockType.BOOLEAN, | |
disableMonitor: true, | |
arguments: { | |
CODE: { | |
type: ArgumentType.STRING, | |
defaultValue: "Math.round(Math.random()) === 1" | |
} | |
} | |
}, | |
{ | |
blockType: BlockType.LABEL, | |
text: 'You can run unsandboxed', | |
hideFromPalette: !this.runningEditorUnsandboxed | |
}, | |
{ | |
blockType: BlockType.LABEL, | |
text: 'when packaging the project.', | |
hideFromPalette: !this.runningEditorUnsandboxed | |
}, | |
{ | |
blockType: BlockType.LABEL, | |
text: '⠀', | |
hideFromPalette: !this.runningEditorUnsandboxed | |
}, | |
{ | |
blockType: BlockType.LABEL, | |
text: 'Player Options >', | |
hideFromPalette: !this.runningEditorUnsandboxed | |
}, | |
{ | |
blockType: BlockType.LABEL, | |
text: 'Remove sandbox on the JavaScript Ext.', | |
hideFromPalette: !this.runningEditorUnsandboxed | |
}, | |
] | |
}; | |
} | |
async unsandbox() { | |
const unsandbox = await this.runtime.vm.securityManager.canUnsandbox('JavaScript'); | |
if (!unsandbox) return; | |
this.runningEditorUnsandboxed = true; | |
this.runtime.vm.emitWorkspaceUpdate(); | |
} | |
sandbox() { | |
this.runningEditorUnsandboxed = false; | |
this.runtime.vm.emitWorkspaceUpdate(); | |
} | |
// util | |
evaluateCode(code, args, util, realBlockInfo) { | |
// used for packager | |
if (this.runtime.extensionRuntimeOptions.javascriptUnsandboxed === true || this.runningEditorUnsandboxed) { | |
let result; | |
try { | |
// eslint-disable-next-line no-eval | |
result = eval(code); | |
} catch (err) { | |
result = err; | |
} | |
return result; | |
} | |
// we are not packaged | |
return new Promise((resolve) => { | |
SandboxRunner.execute(code).then(result => { | |
// result is { value: any, success: boolean } | |
// in PM, we always ignore errors | |
return resolve(result.value); | |
}) | |
}) | |
} | |
// blocks | |
javascriptStack(args, util, realBlockInfo) { | |
const code = Cast.toString(args.CODE); | |
return this.evaluateCode(code, args, util, realBlockInfo); | |
} | |
javascriptString(args, util, realBlockInfo) { | |
const code = Cast.toString(args.CODE); | |
return this.evaluateCode(code, args, util, realBlockInfo); | |
} | |
javascriptBool(args, util, realBlockInfo) { | |
const code = Cast.toString(args.CODE); | |
const possiblePromise = this.evaluateCode(code, args, util, realBlockInfo); | |
if (possiblePromise && typeof possiblePromise.then === 'function') { | |
return (async () => { | |
const value = await possiblePromise; | |
return Boolean(value); // this is a JavaScript extension, we should use the JavaScript way of determining booleans | |
})(); | |
} | |
return Boolean(possiblePromise); | |
} | |
javascriptHat(...args) { | |
if (!this.runtime.extensionRuntimeOptions.javascriptUnsandboxed && !this.runningEditorUnsandboxed) { | |
return false; // we will cause issues otherwise, edging hats cause weird issues when waiting for promises each frame | |
} | |
const possiblePromise = this.javascriptBool(...args); | |
if (possiblePromise && typeof possiblePromise.then === 'function') { | |
return false; // we will cause issues otherwise, edging hats cause weird issues when waiting for promises each frame | |
} | |
return possiblePromise; | |
} | |
} | |
module.exports = jgJavascript; | |