-
Notifications
You must be signed in to change notification settings - Fork 171
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
release/1.5.0
- Loading branch information
Showing
11 changed files
with
339 additions
and
30 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# @ice/sandbox | ||
|
||
> icestark sandbox solution. [icestark docs](https://ice.work/docs/icestark/about). | ||
[![NPM version](https://img.shields.io/npm/v/@ice/sandbox.svg?style=flat)](https://npmjs.org/package/@ice/sandbox) [![Package Quality](https://npm.packagequality.com/shield/@ice%2Fsandbox.svg)](https://packagequality.com/#?package=@ice%2Fsandbox) [![build status](https://img.shields.io/travis/ice-lab/icestark.svg?style=flat-square)](https://travis-ci.org/ice-lab/icestark) [![Test coverage](https://img.shields.io/codecov/c/github/ice-lab/icestark.svg?style=flat-square)](https://codecov.io/gh/ice-lab/icestark) [![NPM downloads](http://img.shields.io/npm/dm/@ice/sandbox.svg?style=flat)](https://npmjs.org/package/@ice/sandbox) [![David deps](https://img.shields.io/david/ice-lab/icestark.svg?style=flat-square)](https://david-dm.org/ice-lab/icestark) | ||
|
||
## Installation | ||
|
||
```bash | ||
$ npm install @ice/sandbox --save | ||
``` | ||
|
||
## Usage | ||
|
||
```js | ||
import Sandbox from '@ice/sandbox'; | ||
|
||
const sandbox = new Sandbox(); | ||
|
||
// execute scripts in sandbox | ||
sandbox.execScriptInSandbox('window.a = 1;console.log(window.a);'); | ||
|
||
// clear side effects added by sandbox, such as addEventListener, setInterval | ||
sandbox.clear(); | ||
``` | ||
|
||
## Inspiration | ||
|
||
`@ice/sandbox` is inspired by [tc39/proposal-realms](https://github.com/tc39/proposal-realms), [realms-shim](https://github.com/Agoric/realms-shim) and [qiankun sandbox](https://github.com/umijs/qiankun). | ||
|
||
## Contributors | ||
|
||
Feel free to report any questions as an [issue](https://github.com/ice-lab/icestark/issues/new), we'd love to have your helping hand on `icestark`. | ||
|
||
If you're interested in `icestark`, see [CONTRIBUTING.md](https://github.com/alibaba/ice/blob/master/.github/CONTRIBUTING.md) for more information to learn how to get started. | ||
|
||
## License | ||
|
||
[MIT](LICENSE) |
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,41 @@ | ||
{ | ||
"name": "@ice/sandbox", | ||
"version": "1.0.0", | ||
"description": "sandbox for execute scripts", | ||
"main": "lib/index.js", | ||
"scripts": { | ||
"build": "rm -rf lib && tsc", | ||
"watch": "tsc -w", | ||
"prepublishOnly": "npm run test && npm run build", | ||
"test": "NODE_ENV=unittest jest", | ||
"coverage": "codecov" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/ice-lab/icestark.git" | ||
}, | ||
"keywords": [ | ||
"sandbox", | ||
"icestark" | ||
], | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/ice-lab/icestark/issues" | ||
}, | ||
"homepage": "https://github.com/ice-lab/icestark#readme", | ||
"husky": { | ||
"hooks": { | ||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS" | ||
} | ||
}, | ||
"devDependencies": { | ||
"jest": "^25.2.7", | ||
"ts-jest": "^25.3.1", | ||
"typescript": "^3.8.3" | ||
}, | ||
"jest": { | ||
"coverageDirectory": "./coverage/", | ||
"collectCoverage": true, | ||
"preset": "ts-jest" | ||
} | ||
} |
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,141 @@ | ||
// check window contructor function, like Object Array | ||
function isConstructor(fn) { | ||
const functionStr = fn.toString(); | ||
const upperCaseRegex = /^function\s+[A-Z]/; | ||
|
||
return ( | ||
// generator function and has own prototype properties | ||
(fn.prototype && fn.prototype.constructor === fn && Object.getOwnPropertyNames(fn.prototype).length > 1) || | ||
// upper case | ||
upperCaseRegex.test(functionStr) || | ||
// ES6 class, window function do not have this case | ||
functionStr.slice(0, 5) === 'class' | ||
); | ||
} | ||
|
||
// get function from original window, such as scrollTo, parseInt | ||
function isWindowFunction(func) { | ||
return func && typeof func === 'function' && !isConstructor(func); | ||
} | ||
|
||
export default class Sandbox { | ||
private sandbox: Window; | ||
|
||
private eventListeners = {}; | ||
|
||
private timeoutIds: number[] = []; | ||
|
||
private intervalIds: number[] = []; | ||
|
||
public sandboxDisabled: boolean; | ||
|
||
constructor() { | ||
if (!window.Proxy) { | ||
console.warn('proxy sandbox is not support by current browser'); | ||
this.sandboxDisabled = true; | ||
} | ||
this.sandbox = null; | ||
} | ||
|
||
createProxySandbox() { | ||
const proxyWindow = Object.create(null) as Window; | ||
const originalWindow = window; | ||
const originalAddEventListener = window.addEventListener; | ||
const originalRemoveEventListener = window.removeEventListener; | ||
const originalSetInerval = window.setInterval; | ||
const originalSetTimeout = window.setTimeout; | ||
// hijack addEventListener | ||
proxyWindow.addEventListener = (eventName, fn, ...rest) => { | ||
const listeners = this.eventListeners[eventName] || []; | ||
listeners.push(fn); | ||
return originalAddEventListener.apply(originalWindow, [eventName, fn, ...rest]); | ||
}; | ||
// hijack removeEventListener | ||
proxyWindow.removeEventListener = (eventName, fn, ...rest) => { | ||
const listeners = this.eventListeners[eventName] || []; | ||
if (listeners.includes(fn)) { | ||
listeners.splice(listeners.indexOf(fn), 1); | ||
} | ||
return originalRemoveEventListener.apply(originalWindow, [eventName, fn, ...rest]); | ||
}; | ||
// hijack setTimeout | ||
proxyWindow.setTimeout = (...args) => { | ||
const timerId = originalSetTimeout(...args); | ||
this.timeoutIds.push(timerId); | ||
return timerId; | ||
}; | ||
// hijack setInterval | ||
proxyWindow.setInterval = (...args) => { | ||
const intervalId = originalSetInerval(...args); | ||
this.intervalIds.push(intervalId); | ||
return intervalId; | ||
}; | ||
|
||
const sandbox = new Proxy(proxyWindow, { | ||
set(target: Window, p: PropertyKey, value: any): boolean { | ||
// eslint-disable-next-line no-param-reassign | ||
target[p] = value; | ||
return true; | ||
}, | ||
get(target: Window, p: PropertyKey): any { | ||
if (p === Symbol.unscopables) { | ||
return undefined; | ||
} | ||
if (['top', 'window', 'self', 'globalThis'].includes(p as string)) { | ||
return sandbox; | ||
} | ||
const targetValue = target[p]; | ||
if (targetValue) { | ||
// case of addEventListener, removeEventListener, setTimeout, setInterval setted in sandbox | ||
return targetValue; | ||
} else { | ||
const value = originalWindow[p]; | ||
if (isWindowFunction(value)) { | ||
// fix Illegal invocation | ||
return value.bind(originalWindow); | ||
} else { | ||
// case of window.clientWidth、new window.Object() | ||
return value; | ||
} | ||
} | ||
}, | ||
has(target: Window, p: PropertyKey): boolean { | ||
return p in target || p in originalWindow; | ||
}, | ||
}); | ||
this.sandbox = sandbox; | ||
} | ||
|
||
execScriptInSandbox(script: string): void { | ||
if (!this.sandboxDisabled) { | ||
// create sandbox before exec script | ||
if (!this.sandbox) { | ||
this.createProxySandbox(); | ||
} | ||
try { | ||
const execScript = `with (sandbox) {;${script}\n}`; | ||
// eslint-disable-next-line no-new-func | ||
const code = new Function('sandbox', execScript); | ||
// run code with sandbox | ||
code(this.sandbox); | ||
} catch (error) { | ||
console.error(`error occurs when execute script in sandbox: ${error}`); | ||
throw error; | ||
} | ||
} | ||
} | ||
|
||
clear() { | ||
if (!this.sandboxDisabled) { | ||
// remove event listeners | ||
Object.keys(this.eventListeners).forEach((eventName) => { | ||
(this.eventListeners[eventName] || []).forEach(listener => { | ||
window.removeEventListener(eventName, listener); | ||
}); | ||
}); | ||
// clear timeout | ||
this.timeoutIds.forEach(id => window.clearTimeout(id)); | ||
this.intervalIds.forEach(id => window.clearInterval(id)); | ||
} | ||
} | ||
} |
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,22 @@ | ||
import Sandbox from '../src/index'; | ||
|
||
describe('sandbox', () => { | ||
const sandbox = new Sandbox(); | ||
const delay = (time) => new Promise((resolve) => setTimeout(() => resolve(), time)); | ||
|
||
test('execute script in sandbox', () => { | ||
sandbox.execScriptInSandbox('window.a = 1;expect(window.a).toBe(1);'); | ||
expect((window as any).a).toBe(undefined); | ||
}); | ||
|
||
test('capture global event', async () => { | ||
sandbox.execScriptInSandbox(` | ||
setInterval(() => {expect(1).toBe(2)}, 100); | ||
setTimeout(() => { expect(1).toBe(2)}, 100)` | ||
); | ||
sandbox.clear(); | ||
// delay 1000 ms for timeout | ||
await delay(1000); | ||
expect(true).toBe(true); | ||
}); | ||
}); |
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,14 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "es5", | ||
"jsx": "react", | ||
"experimentalDecorators": true, | ||
"declaration": true, | ||
"sourceMap": true, | ||
"skipLibCheck": true, | ||
"forceConsistentCasingInFileNames": true, | ||
"outDir": "lib" | ||
}, | ||
"include": ["src/*"], | ||
"exclude": ["node_modules"] | ||
} |
Oops, something went wrong.