summaryrefslogtreecommitdiff
path: root/packages/idb-bridge
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2021-02-24 01:28:02 +0100
committerFlorian Dold <florian@dold.me>2021-02-24 01:28:02 +0100
commit3a336799a08564ddc91713a397707b01ae0425c5 (patch)
treea7de2de7f12787c8f359495170d414ec53f11c09 /packages/idb-bridge
parent627ae6958ef9b98fc16f129fdf95435a5e95b738 (diff)
downloadwallet-core-3a336799a08564ddc91713a397707b01ae0425c5.tar.gz
wallet-core-3a336799a08564ddc91713a397707b01ae0425c5.tar.bz2
wallet-core-3a336799a08564ddc91713a397707b01ae0425c5.zip
idb: custom structured clone, don't rely on typeson anymore
Diffstat (limited to 'packages/idb-bridge')
-rw-r--r--packages/idb-bridge/package.json4
-rw-r--r--packages/idb-bridge/src/index.ts2
-rw-r--r--packages/idb-bridge/src/util/structuredClone.ts227
3 files changed, 228 insertions, 5 deletions
diff --git a/packages/idb-bridge/package.json b/packages/idb-bridge/package.json
index 9db293b52..52bc872da 100644
--- a/packages/idb-bridge/package.json
+++ b/packages/idb-bridge/package.json
@@ -28,9 +28,7 @@
"typescript": "^4.1.3"
},
"dependencies": {
- "tslib": "^2.1.0",
- "typeson": "^5.18.2",
- "typeson-registry": "^1.0.0-alpha.38"
+ "tslib": "^2.1.0"
},
"ava": {
"require": [
diff --git a/packages/idb-bridge/src/index.ts b/packages/idb-bridge/src/index.ts
index 2cb8bcf15..a0bd8b86d 100644
--- a/packages/idb-bridge/src/index.ts
+++ b/packages/idb-bridge/src/index.ts
@@ -115,3 +115,5 @@ export function shimIndexedDB(factory: BridgeIDBFactory): void {
}
export * from "./idbtypes";
+
+export * from "./util/structuredClone"; \ No newline at end of file
diff --git a/packages/idb-bridge/src/util/structuredClone.ts b/packages/idb-bridge/src/util/structuredClone.ts
index 9bbeb7151..1c96d7430 100644
--- a/packages/idb-bridge/src/util/structuredClone.ts
+++ b/packages/idb-bridge/src/util/structuredClone.ts
@@ -18,15 +18,238 @@
import Typeson from "typeson";
// @ts-ignore
import structuredCloningThrowing from "typeson-registry/dist/presets/structured-cloning-throwing";
+import { DataCloneError } from "./errors";
const TSON = new Typeson().register(structuredCloningThrowing);
+const { toString: toStr } = {};
+const hasOwn = {}.hasOwnProperty;
+const getProto = Object.getPrototypeOf;
+const fnToString = hasOwn.toString;
+
+function toStringTag(val: any) {
+ return toStr.call(val).slice(8, -1);
+}
+
+function hasConstructorOf(a: any, b: any) {
+ if (!a || typeof a !== "object") {
+ return false;
+ }
+ const proto = getProto(a);
+ if (!proto) {
+ return b === null;
+ }
+ const Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
+ if (typeof Ctor !== "function") {
+ return b === null;
+ }
+ if (b === Ctor) {
+ return true;
+ }
+ if (b !== null && fnToString.call(Ctor) === fnToString.call(b)) {
+ return true;
+ }
+ return false;
+}
+
+/**
+ *
+ * @param {any} val
+ * @returns {boolean}
+ */
+function isPlainObject(val: any): boolean {
+ if (!val || toStringTag(val) !== "Object") {
+ return false;
+ }
+
+ const proto = getProto(val);
+ if (!proto) {
+ // `Object.create(null)`
+ return true;
+ }
+
+ return hasConstructorOf(val, Object);
+}
+
+function isUserObject(val: any): boolean {
+ if (!val || toStringTag(val) !== "Object") {
+ return false;
+ }
+
+ const proto = getProto(val);
+ if (!proto) {
+ // `Object.create(null)`
+ return true;
+ }
+ return hasConstructorOf(val, Object) || isUserObject(proto);
+}
+
+function isRegExp(val: any): boolean {
+ return toStringTag(val) === "RegExp";
+}
+
+function internalEncapsulate(
+ val: any,
+ outRoot: any,
+ path: string[],
+ memo: Map<any, string[]>,
+ types: Array<[string[], string]>,
+): any {
+ const memoPath = memo.get(val);
+ if (memoPath) {
+ types.push([path, "ref"]);
+ return memoPath;
+ }
+ if (val === null) {
+ return null;
+ }
+ if (val === undefined) {
+ types.push([path, "undef"]);
+ return 0;
+ }
+ if (Array.isArray(val)) {
+ memo.set(val, path);
+ const outArr: any[] = [];
+ let special = false;
+ for (const x in val) {
+ const n = Number(x);
+ if (n < 0 || n >= val.length || Number.isNaN(n)) {
+ special = true;
+ break;
+ }
+ }
+ if (special) {
+ types.push([path, "array"]);
+ }
+ for (const x in val) {
+ const p = [...path, x];
+ outArr[x] = internalEncapsulate(val[x], outRoot, p, memo, types);
+ }
+ return outArr;
+ }
+ if (val instanceof Date) {
+ types.push([path, "date"]);
+ return val.getTime();
+ }
+ if (isUserObject(val) || isPlainObject(val)) {
+ memo.set(val, path);
+ const outObj: any = {};
+ for (const x in val) {
+ const p = [...path, x];
+ outObj[x] = internalEncapsulate(val[x], outRoot, p, memo, types);
+ }
+ return outObj;
+ }
+ if (typeof val === "bigint") {
+ types.push([path, "bigint"]);
+ return val.toString();
+ }
+ if (typeof val === "boolean") {
+ return val;
+ }
+ if (typeof val === "number") {
+ return val;
+ }
+ if (typeof val === "string") {
+ return val;
+ }
+ throw Error();
+}
+
+/**
+ * Encapsulate a cloneable value into a plain JSON object.
+ */
export function structuredEncapsulate(val: any): any {
- return TSON.encapsulate(val);
+ const outRoot = {};
+ const types: Array<[string[], string]> = [];
+ let res;
+ try {
+ res = internalEncapsulate(val, outRoot, [], new Map(), types);
+ } catch (e) {
+ throw new DataCloneError();
+ }
+ if (res === null) {
+ return res;
+ }
+ // We need to further encapsulate the outer layer
+ if (
+ Array.isArray(res) ||
+ typeof res !== "object" ||
+ "$" in res ||
+ "$types" in res
+ ) {
+ res = { $: res };
+ }
+ if (types.length > 0) {
+ res["$types"] = types;
+ }
+ return res;
+}
+
+export function internalStructuredRevive(val: any): any {
+ val = JSON.parse(JSON.stringify(val));
+ if (val === null) {
+ return null;
+ }
+ if (typeof val === "number") {
+ return val;
+ }
+ if (typeof val === "string") {
+ return val;
+ }
+ if (!isPlainObject(val)) {
+ throw Error();
+ }
+ let types = val.$types ?? [];
+ delete val.$types;
+ let outRoot: any;
+ if ("$" in val) {
+ outRoot = val.$;
+ } else {
+ outRoot = val;
+ }
+ function mutatePath(path: string[], f: (x: any) => any): void {
+ if (path.length == 0) {
+ outRoot = f(outRoot);
+ return;
+ }
+ let obj = outRoot;
+ for (let i = 0; i < path.length - 1; i++) {
+ const n = path[i];
+ if (!(n in obj)) {
+ obj[n] = {};
+ }
+ obj = obj[n];
+ }
+ const last = path[path.length - 1];
+ obj[last] = f(obj[last]);
+ }
+ for (const [path, type] of types) {
+ if (type === "bigint") {
+ mutatePath(path, (x) => BigInt(x));
+ } else if (type === "array") {
+ mutatePath(path, (x) => {
+ const newArr: any = [];
+ for (const k in x) {
+ newArr[k] = x[k];
+ }
+ return newArr;
+ });
+ } else if (type === "date") {
+ mutatePath(path, (x) => new Date(x));
+ } else {
+ throw Error("type not implemented");
+ }
+ }
+ return outRoot;
}
export function structuredRevive(val: any): any {
- return TSON.revive(val);
+ try {
+ return internalStructuredRevive(val);
+ } catch (e) {
+ throw new DataCloneError();
+ }
}
/**