/
index.ts
130 lines (104 loc) · 3.37 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import {
ProcessScriptData,
PostMessageError,
LinearData,
ProcessSVGData,
ProcessResult,
} from 'shared-types/index';
import parseStack from './error-stack-parser';
import { svgPathProperties as SVGPathProperties } from 'svg-path-properties';
function isProcessScriptData(data: any): data is ProcessScriptData {
return data.action === 'process-script';
}
function isProcessSVGData(data: any): data is ProcessSVGData {
return data.action === 'process-svg';
}
type EasingFunc = (value: number) => unknown;
const pointsLength = 10_000;
function processScriptData(script: string): ProcessResult {
const oldGlobals = Object.keys(self);
// Using importScripts rather than eval, as it gives better stack traces.
// But also wrapping in a function, so things aren't global by default.
importScripts(
`data:text/javascript,${encodeURIComponent(`(() => {${script};})()`)}`,
);
let easingFunc: EasingFunc | undefined;
// Look for a new global
const newGlobals = new Set(Object.keys(self));
for (const key of oldGlobals) newGlobals.delete(key);
// Remove any non-functions
for (const key of newGlobals) {
// @ts-ignore
if (typeof self[key] !== 'function') newGlobals.delete(key);
}
if (newGlobals.size > 1) {
throw Error(
'Too many global functions. Found: ' + [...newGlobals].join(', '),
);
}
if (newGlobals.size === 0) {
throw Error('No global function found.');
}
const [key] = newGlobals;
// @ts-ignore
easingFunc = self[key] as EasingFunc;
return {
name: key.replace(/[A-Z]/g, (match) => '-' + match.toLowerCase()),
points: Array.from({ length: pointsLength }, (_, i) => {
const pos = i / (pointsLength - 1);
return [pos, Number(easingFunc!(pos))];
}),
duration:
'duration' in self && typeof self.duration === 'number'
? self.duration || 0
: 0,
};
}
function processSVGData(pathData: string): ProcessResult {
const parsedPath = new SVGPathProperties(pathData);
const totalLength = parsedPath.getTotalLength();
if (totalLength === 0) throw new TypeError('Path is zero length');
let lastX = -Infinity;
const points: LinearData = Array.from({ length: pointsLength }, (_, i) => {
const pos = (i / (pointsLength - 1)) * totalLength;
const point = parsedPath.getPointAtLength(pos);
// Prevent paths going back on themselves
lastX = Math.max(lastX, point.x);
return [lastX, point.y];
});
return {
name: 'custom',
points,
duration: 0,
};
}
let used = false;
onmessage = ({ data }) => {
// The scripts we're receiving are not trusted.
// Ensure we're running them on a null origin.
if (origin !== 'null') return;
if (typeof data !== 'object' || data === null) return;
if (isProcessScriptData(data) || isProcessSVGData(data)) {
const { port, script } = data;
if (used) {
const error: PostMessageError = { message: 'Worker already used' };
port.postMessage({ error });
return;
}
used = true;
try {
const result =
data.action === 'process-svg'
? processSVGData(script)
: processScriptData(script);
port.postMessage({ result });
} catch (error) {
const errorDetails: PostMessageError = {
...parseStack(error as Error),
message: (error as Error).message,
};
port.postMessage({ error: errorDetails });
throw error;
}
}
};