diff --git a/package-lock.json b/package-lock.json index 76366a2..4c82c87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,13 +10,181 @@ "hasInstallScript": true, "license": "MIT", "devDependencies": { + "@rollup/plugin-commonjs": "^24.0.1", + "@rollup/plugin-node-resolve": "^15.0.1", + "@rollup/plugin-typescript": "^11.0.0", + "@types/isomorphic-fetch": "^0.0.36", "babel-cli": "^6.26.0", "babel-preset-es2015": "^6.24.1", "browserify": "^17.0.0", "isomorphic-fetch": "^3.0.0", - "mocha": "^10.0.0" + "mocha": "^10.0.0", + "rollup": "^3.19.1", + "tslib": "^2.5.0" } }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "24.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.1.tgz", + "integrity": "sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "glob": "^8.0.3", + "is-reference": "1.2.1", + "magic-string": "^0.27.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz", + "integrity": "sha512-ReY88T7JhJjeRVbfCyNj+NXAG3IIsVMsX9b5/9jC98dRP8/yxlZdz7mHZbHk5zHr24wZZICS5AcXsFZAXYUQEg==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.0", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-typescript": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.0.0.tgz", + "integrity": "sha512-goPyCWBiimk1iJgSTgsehFD5OOFHiAknrRJjqFCudcW8JtWiBlK284Xnn4flqMqg6YAjVG/EE+3aVzrL5qNSzQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.14.0||^3.0.0", + "tslib": "*", + "typescript": ">=3.7.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "tslib": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", + "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@types/estree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", + "dev": true + }, + "node_modules/@types/isomorphic-fetch": { + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.36.tgz", + "integrity": "sha512-ulw4d+vW1HKn4oErSmNN2HYEcHGq0N1C5exlrMM0CRqX1UUpFhGb5lwiom5j9KN3LBJJDLRmYIZz1ghm7FIzZw==", + "dev": true + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, "node_modules/@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", @@ -1080,6 +1248,18 @@ "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", "dev": true }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", @@ -1238,6 +1418,12 @@ "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "dev": true }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1382,6 +1568,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deepmerge": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", + "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", @@ -1614,6 +1809,12 @@ "node": ">=0.8.0" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, "node_modules/esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", @@ -2974,6 +3175,21 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-callable": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", @@ -3105,6 +3321,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, "node_modules/is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -3174,6 +3396,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -3486,6 +3717,18 @@ "loose-envify": "cli.js" } }, + "node_modules/magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -4558,6 +4801,36 @@ "inherits": "^2.0.1" } }, + "node_modules/rollup": { + "version": "3.19.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.19.1.tgz", + "integrity": "sha512-lAbrdN7neYCg/8WaoWn/ckzCtz+jr70GFfYdlf50OF7387HTg+wiuiqJRFYawwSPpqfqDNYqK7smY/ks2iAudg==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -4974,6 +5247,12 @@ "node": ">=0.10.0" } }, + "node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "dev": true + }, "node_modules/tty-browserify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", @@ -4986,6 +5265,20 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "dev": true }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/umd": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", @@ -5289,6 +5582,112 @@ } }, "dependencies": { + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@rollup/plugin-commonjs": { + "version": "24.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.1.tgz", + "integrity": "sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "glob": "^8.0.3", + "is-reference": "1.2.1", + "magic-string": "^0.27.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "@rollup/plugin-node-resolve": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz", + "integrity": "sha512-ReY88T7JhJjeRVbfCyNj+NXAG3IIsVMsX9b5/9jC98dRP8/yxlZdz7mHZbHk5zHr24wZZICS5AcXsFZAXYUQEg==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.0", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + } + }, + "@rollup/plugin-typescript": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.0.0.tgz", + "integrity": "sha512-goPyCWBiimk1iJgSTgsehFD5OOFHiAknrRJjqFCudcW8JtWiBlK284Xnn4flqMqg6YAjVG/EE+3aVzrL5qNSzQ==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.0.1", + "resolve": "^1.22.1" + } + }, + "@rollup/pluginutils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", + "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + } + }, + "@types/estree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", + "dev": true + }, + "@types/isomorphic-fetch": { + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.36.tgz", + "integrity": "sha512-ulw4d+vW1HKn4oErSmNN2HYEcHGq0N1C5exlrMM0CRqX1UUpFhGb5lwiom5j9KN3LBJJDLRmYIZz1ghm7FIzZw==", + "dev": true + }, + "@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, "@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", @@ -6269,6 +6668,12 @@ "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", "dev": true }, + "builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true + }, "builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", @@ -6407,6 +6812,12 @@ "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "dev": true }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -6540,6 +6951,12 @@ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true }, + "deepmerge": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", + "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==", + "dev": true + }, "define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", @@ -6735,6 +7152,12 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, "esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", @@ -7839,6 +8262,15 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, + "is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "requires": { + "builtin-modules": "^3.3.0" + } + }, "is-callable": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", @@ -7928,6 +8360,12 @@ "is-extglob": "^1.0.0" } }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, "is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -7973,6 +8411,15 @@ "dev": true, "optional": true }, + "is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "requires": { + "@types/estree": "*" + } + }, "is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -8197,6 +8644,15 @@ "js-tokens": "^3.0.0" } }, + "magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.13" + } + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -9038,6 +9494,24 @@ "inherits": "^2.0.1" } }, + "rollup": { + "version": "3.19.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.19.1.tgz", + "integrity": "sha512-lAbrdN7neYCg/8WaoWn/ckzCtz+jr70GFfYdlf50OF7387HTg+wiuiqJRFYawwSPpqfqDNYqK7smY/ks2iAudg==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + }, + "dependencies": { + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + } + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -9381,6 +9855,12 @@ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "dev": true + }, "tty-browserify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", @@ -9393,6 +9873,13 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "dev": true }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "peer": true + }, "umd": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", diff --git a/package.json b/package.json index 6a013d8..bc11ddd 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,16 @@ }, "homepage": "https://github.com/NetsBlox/client-auth#readme", "devDependencies": { + "@rollup/plugin-commonjs": "^24.0.1", + "@rollup/plugin-node-resolve": "^15.0.1", + "@rollup/plugin-typescript": "^11.0.0", + "@types/isomorphic-fetch": "^0.0.36", "babel-cli": "^6.26.0", "babel-preset-es2015": "^6.24.1", "browserify": "^17.0.0", "isomorphic-fetch": "^3.0.0", - "mocha": "^10.0.0" + "mocha": "^10.0.0", + "rollup": "^3.19.1", + "tslib": "^2.5.0" } } diff --git a/rollup.config.mjs b/rollup.config.mjs new file mode 100644 index 0000000..4592801 --- /dev/null +++ b/rollup.config.mjs @@ -0,0 +1,18 @@ +import typescript from '@rollup/plugin-typescript'; +import nodeResolve from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; + +const plugins = [commonjs(), nodeResolve(), typescript()]; + +export default [ + { + input: 'src/client.ts', + output: { + file: 'dist/client.js', + format: 'umd', + name: 'Cloud' + }, + plugins, + preferBuiltins: true, + }, +]; diff --git a/src/client.js b/src/client.js index 66f0aa7..3c13488 100644 --- a/src/client.js +++ b/src/client.js @@ -1,42 +1,33 @@ -require('isomorphic-fetch'); -const assert = require('assert'); - +import 'isomorphic-fetch'; +import assert from 'assert'; const defaultLocalizer = text => text; const isNodeJs = typeof window === 'undefined'; - -class Cloud { - constructor(url, clientId, username, localize=defaultLocalizer) { +export default class Cloud { + constructor(url, clientId, username, localize = defaultLocalizer) { this.clientId = clientId; this.username = username; this.projectId = null; this.roleId = null; this.url = url; - this.token = null; // only needed in NodeJs + this.token = null; // only needed in NodeJs this.localize = localize; } - clear() { this.username = null; this.token = null; } - hasProtocol() { return this.url.toLowerCase().indexOf('http') === 0; } - async resetPassword(username) { - const response = await this.fetch(`/users/${username}/password`, {method: 'POST'}); + const response = await this.fetch(`/users/${username}/password`, { method: 'POST' }); return await response.text(); - }; - - async login( - username, - password, - remember, // TODO: use this... - strategy = 'NetsBlox', - ) { + } + ; + async login(username, password, remember, // TODO: use this... + strategy = 'NetsBlox') { const credentials = {}; - credentials[strategy] = {username, password}; + credentials[strategy] = { username, password }; const body = { credentials, clientId: this.clientId, @@ -50,65 +41,53 @@ class Cloud { } return this.username; } - async getProjectList() { const response = await this.fetch(`/projects/user/${this.username}`); return await response.json(); } - async getSharedProjectList() { const response = await this.fetch(`/projects/shared/${this.username}`); return await response.json(); } - - async changePassword( - oldPW, - newPW, - ) { + async changePassword(oldPW, newPW) { const body = JSON.stringify(newPW); - const response = await this.fetch( - `/users/${this.username}/password`, - {method: 'PATCH', body} - ); + const response = await this.fetch(`/users/${this.username}/password`, { method: 'PATCH', body }); return await response.text(); } - parseResponse(src) { - var ans = [], - lines; - if (!src) {return ans; } + var ans = [], lines; + if (!src) { + return ans; + } lines = src.split(" "); lines.forEach(function (service) { - var entries = service.split("&"), - dict = {}; + var entries = service.split("&"), dict = {}; entries.forEach(function (entry) { - var pair = entry.split("="), - key = decodeURIComponent(pair[0]), - val = decodeURIComponent(pair[1]); + var pair = entry.split("="), key = decodeURIComponent(pair[0]), val = decodeURIComponent(pair[1]); dict[key] = val; }); ans.push(dict); }); return ans; - }; - + } + ; parseDict(src) { var dict = {}; - if (!src) {return dict; } + if (!src) { + return dict; + } src.split("&").forEach(function (entry) { - var pair = entry.split("="), - key = decodeURIComponent(pair[0]), - val = decodeURIComponent(pair[1]); + var pair = entry.split("="), key = decodeURIComponent(pair[0]), val = decodeURIComponent(pair[1]); dict[key] = val; }); return dict; - }; - + } + ; encodeDict(dict) { - var str = '', - pair, - key; - if (!dict) {return null; } + var str = '', pair, key; + if (!dict) { + return null; + } for (key in dict) { if (dict.hasOwnProperty(key)) { pair = encodeURIComponent(key) @@ -121,30 +100,29 @@ class Cloud { } } return str; - }; - + } + ; async getUserData() { const response = await this.fetch(`/users/${this.username}`); return await response.json(); - }; - + } + ; async addRole(name) { - const response = await this.post(`/projects/id/${this.projectId}/`, {name}); + const response = await this.post(`/projects/id/${this.projectId}/`, { name }); // TODO: should I request the new project state, too? // I shouldn't have to since we should be subscribed to changes... //return await response.json(); - }; - + } + ; async saveRole(roleData) { - const url = `/projects/id/${this.projectId}/${this.roleId}`; const options = { method: 'POST', body: JSON.stringify(roleData), }; await this.fetch(url, options); - }; - + } + ; async renameRole(roleId, name) { const body = { name, @@ -153,75 +131,73 @@ class Cloud { const response = await this.patch(`/projects/id/${this.projectId}/${roleId}`, body); // TODO: error handling //return await response.json(); - }; - + } + ; async renameProject(name) { const body = { name, clientId: this.clientId, }; const response = await this.patch(`/projects/id/${this.projectId}`, body); - }; - + } + ; async reportLatestRole(id, data) { const clientId = this.clientId; const options = { method: 'POST', - body: JSON.stringify({id, data}) + body: JSON.stringify({ id, data }) }; await this.fetch(`/projects/id/${this.projectId}/${this.roleId}/latest?clientId=${clientId}`, options); - }; - + } + ; async cloneRole(roleId) { const projectId = this.projectId; const fetchRoleResponse = await this.fetch(`/projects/id/${projectId}/${roleId}/latest`); - const {name, code, media} = await fetchRoleResponse.json(); - const response = await this.post(`/projects/id/${projectId}/`, {name, code, media}); - }; - + const { name, code, media } = await fetchRoleResponse.json(); + const response = await this.post(`/projects/id/${projectId}/`, { name, code, media }); + } + ; async sendOccupantInvite(username, roleId) { - const body = {username, roleId}; + const body = { username, roleId }; await this.post(`/network/id/${this.projectId}/occupants/invite`, body); - }; - + } + ; async evictOccupant(clientID) { const method = 'DELETE'; - await this.fetch(`/network/id/${this.projectdId}/occupants/${clientID}`, {method}); - }; - + await this.fetch(`/network/id/${this.projectId}/occupants/${clientID}`, { method }); + } + ; async getCollaboratorList() { const response = await this.get(`/projects/id/${this.projectId}/collaborators/`); return await response.json(); - }; - + } + ; async getCollaboratorRequestList() { const response = await this.get(`/collaboration-invites/user/${this.username}/`); return await response.json(); } - async sendCollaborateRequest(username) { await this.post(`/collaboration-invites/${this.projectId}/invite/${username}`); - }; - + } + ; async respondToCollaborateRequest(id, accepted) { const newState = accepted ? 'ACCEPTED' : 'REJECTED'; await this.post(`/collaboration-invites/id/${id}`, newState); - }; - + } + ; async removeCollaborator(username, projectId) { await this.delete(`/projects/id/${projectId}/collaborators/${username}`); - }; - + } + ; async getOnlineFriendList() { const response = await this.get(`/friends/${this.username}/online`); return await response.json(); } - async getFriendList() { const response = await this.get(`/friends/${this.username}/`); return await response.json(); - }; - + } + ; async getRole(projectId, roleId) { const qs = this.clientId ? `clientId=${this.clientId}` : ''; const response = await this.fetch(`/projects/id/${projectId}/${roleId}/latest?${qs}`); @@ -229,84 +205,79 @@ class Cloud { // TODO: Set the state here? this.setLocalState(projectId, roleId); return project; - }; - + } + ; async getProjectMetadata(projectId) { const response = await this.fetch(`/projects/id/${projectId}/metadata`); const project = await response.json(); return project; - }; - + } + ; async getProjectByName(owner, name) { const response = await this.fetch(`/projects/user/${owner}/${name}`); return await response.json(); - }; - + } + ; async getProjectMetadataByName(owner, name) { const response = await this.fetch(`/projects/user/${owner}/${name}/metadata`); return await response.json(); - }; - + } + ; async startNetworkTrace(projectId) { const response = await this.post(`/network/id/${projectId}/trace/`); return await response.text(); - }; - + } + ; async stopNetworkTrace(projectId, traceId) { const response = await this.post(`/network/id/${projectId}/trace/${traceId}/stop`); return await response.text(); - }; - + } + ; async getNetworkTrace(projectId, traceId) { const response = await this.fetch(`/network/id/${projectId}/trace/${traceId}`); return await response.json(); - }; - + } + ; async getFriendRequestList() { const response = await this.get(`/friends/${this.username}/invites/`); return response.json(); } - async sendFriendRequest(username) { await this.post(`/friends/${this.username}/invite/`, username.trim()); } - async unfriend(username) { username = encodeURIComponent(username.trim()); await this.post(`/friends/${this.username}/unfriend/${username}`); } - async respondToFriendRequest(sender, newState) { sender = encodeURIComponent(sender); await this.post(`/friends/${this.username}/invites/${sender}`, newState); } - async deleteRole(roleId) { const method = 'DELETE'; - await this.fetch(`/projects/id/${this.projectId}/${roleId}`, {method}); - }; - + await this.fetch(`/projects/id/${this.projectId}/${roleId}`, { method }); + } + ; async deleteProject(projectId) { const method = 'DELETE'; - await this.fetch(`/projects/id/${projectId}`, {method}); - }; - + await this.fetch(`/projects/id/${projectId}`, { method }); + } + ; async publishProject(projectId) { const method = 'POST'; - await this.fetch(`/projects/id/${projectId}/publish`, {method}); - }; - + await this.fetch(`/projects/id/${projectId}/publish`, { method }); + } + ; async unpublishProject(projectId) { const method = 'POST'; - await this.fetch(`/projects/id/${projectId}/unpublish`, {method}); - }; - + await this.fetch(`/projects/id/${projectId}/unpublish`, { method }); + } + ; reconnect(callback, errorCall) { if (!this.username) { this.message('You are not logged in'); return; } - // need to set 'api' from setClientState let promise = this.setClientState(); if (callback && errorCall) { @@ -314,20 +285,16 @@ class Cloud { .catch(errorCall); } return promise; - }; - + } + ; async logout() { const method = 'POST'; - await this.fetch('/users/logout', {method}); + await this.fetch('/users/logout', { method }); this.clear(); return true; - }; - - async signup( - username, - password, - email, - ) { + } + ; + async signup(username, password, email) { const body = { username, password, @@ -335,24 +302,22 @@ class Cloud { }; const response = await this.post('/users/create', body); return await response.text(); - }; - + } + ; async saveProjectCopy() { const response = await this.fetch(`/projects/${this.projectId}/latest`); const xml = await response.text(); const options = { method: 'POST', - body: xml, // TODO: add options for allow rename? + body: xml, // TODO: add options for allow rename? }; const saveResponse = await this.fetch(`/projects/`, options); - // TODO: set the state with the network overlay //this.setLocalState(response.projectId, this.roleId); - return saveResponse.status == 200; - }; - - async patch(url, body) { + } + ; + async patch(url, body = undefined) { const opts = { method: 'PATCH', }; @@ -360,9 +325,9 @@ class Cloud { opts.body = JSON.stringify(body); } return await this.fetch(url, opts); - }; - - async post(url, body) { + } + ; + async post(url, body = undefined) { const opts = { method: 'POST', }; @@ -370,29 +335,31 @@ class Cloud { opts.body = JSON.stringify(body); } return await this.fetch(url, opts); - }; - + } + ; async delete(url) { const opts = { method: 'DELETE', }; return await this.fetch(url, opts); - }; - + } + ; async get(url) { const opts = { method: 'GET', }; return await this.fetch(url, opts); - }; - - async fetch(url, opts={}) { + } + ; + async fetch(url, opts) { + opts = opts || {}; url = this.url + url; opts.credentials = opts.credentials || 'include'; - opts.headers = opts.headers || {}; - opts.headers['Content-Type'] = opts.headers['Content-Type'] || 'application/json'; + opts.headers = { + 'Content-Type': 'application/json' + }; if (this.token) { - opts.headers.cookie = `netsblox=${this.token}`; + opts.headers['cookie'] = `netsblox=${this.token}`; } const response = await fetch(url, opts); if (response.status > 399) { @@ -403,44 +370,40 @@ class Cloud { } return response; } - async onerror(response) { const text = await response.text(); throw new CloudError(text); } - setLocalState(projectId, roleId) { this.projectId = projectId; this.roleId = roleId; - }; - + } + ; resetLocalState() { this.setLocalState(null, null); - }; - - newProject(name=this.localize('untitled')) { + } + ; + newProject(name = this.localize('untitled')) { var myself = this; - if (!this.newProjectRequest) { - const saveResponse = this.post(`/projects/`, {name, clientId: this.clientId}); + const saveResponse = this.post(`/projects/`, { name, clientId: this.clientId }); this.newProjectRequest = saveResponse .then(response => response.json()) - .then(async metadata => { - const [roleId] = Object.keys(metadata.roles); - this.setClientState(metadata.id, roleId); - myself.newProjectRequest = null; - return metadata; - }) - .catch(function(req) { - myself.resetLocalState(); - myself.newProjectRequest = null; - throw new Error(req.responseText); - }); + .then(async (metadata) => { + const [roleId] = Object.keys(metadata.roles); + this.setClientState(metadata.id, roleId); + myself.newProjectRequest = null; + return metadata; + }) + .catch(function (req) { + myself.resetLocalState(); + myself.newProjectRequest = null; + throw new Error(req.responseText); + }); } - return this.newProjectRequest; - }; - + } + ; getClientState() { return { username: this.username, @@ -448,88 +411,79 @@ class Cloud { projectId: this.projectId, roleId: this.roleId }; - }; - - async setClientState(projectId=this.projectId, roleId=this.roleId) { - var myself = this, - newProjectRequest = this.newProjectRequest || Promise.resolve(); - + } + ; + async setClientState(projectId = this.projectId, roleId = this.roleId) { + var myself = this, newProjectRequest = this.newProjectRequest || Promise.resolve(); this.projectId = projectId; this.roleId = roleId; return newProjectRequest .then(async () => { - const body = { - state: { - browser: { - projectId: this.projectId, - roleId: this.roleId, - } + const body = { + state: { + browser: { + projectId: this.projectId, + roleId: this.roleId, } - }; - await this.post(`/network/${this.clientId}/state`, body); - // Only change the project ID if no other moves/newProjects/etc have occurred - }) - .catch(function(req) { - var connError = 'Could not connect to ' + myself.url; - throw new Error(req.responseText || connError); - }); - }; - + } + }; + await this.post(`/network/${this.clientId}/state`, body); + // Only change the project ID if no other moves/newProjects/etc have occurred + }) + .catch(function (req) { + var connError = 'Could not connect to ' + myself.url; + throw new Error(req.responseText || connError); + }); + } + ; setProjectName(name) { const newProjectRequest = this.newProjectRequest || Promise.resolve(); - - return newProjectRequest .then(async () => { - await this.patch(`/projects/id/${this.projectId}`, {name, clientId: this.clientId}); - }); - }; - + await this.patch(`/projects/id/${this.projectId}`, { name, clientId: this.clientId }); + }); + } + ; async importProject(projectData) { projectData.clientId = this.clientId; - const response = await this.post('/projects/', projectData); return await response.json(); - }; - + } + ; async linkAccount(username, password, type) { - await this.post(`/users/${this.username}/link/`, {Snap: {username, password}}); - }; - + await this.post(`/users/${this.username}/link/`, { Snap: { username, password } }); + } + ; async unlinkAccount(account) { - await this.post(`/users/${this.username}/unlink/`, {username, strategy: 'snap'}); - }; - - async getProjectData(projectId=this.projectId) { + await this.post(`/users/${this.username}/unlink/`, account); + } + ; + async getProjectData(projectId = this.projectId) { const response = await this.fetch(`/projects/id/${projectId}/latest?clientId=${this.clientId}`); return await response.json(); - }; - - async exportRole(projectId=this.projectId, roleId=this.roleId) { + } + ; + async exportRole(projectId = this.projectId, roleId = this.roleId) { const response = await this.fetch(`/projects/id/${projectId}/${roleId}/latest?clientId=${this.clientId}`); return await response.text(); - }; - - async viewUser(username=this.username) { + } + ; + async viewUser(username = this.username) { const response = await this.fetch(`/users/${username}`); return await response.json(); } - async whoAmI() { const response = await this.get('/users/whoami'); return await response.text(); } - async getCommunityLibraryList() { const response = await this.get('/libraries/community/'); return await response.json(); } - async getLibraryList() { const response = await this.get(`/libraries/user/${this.username}/`); return await response.json(); } - async saveLibrary(name, blocks, notes) { const library = { name, notes, blocks @@ -537,60 +491,49 @@ class Cloud { const response = await this.post(`/libraries/user/${this.username}/`, library); return await response.json(); } - async getLibrary(username, name) { name = encodeURIComponent(name); const response = await this.get(`/libraries/user/${username}/${name}`); return await response.text(); } - async deleteLibrary(name) { name = encodeURIComponent(name); return await this.delete(`/libraries/user/${this.username}/${name}`); } - async publishLibrary(name) { name = encodeURIComponent(name); const response = await this.post(`/libraries/user/${this.username}/${name}/publish`); return await response.json(); } - async unpublishLibrary(name) { name = encodeURIComponent(name); await this.post(`/libraries/user/${this.username}/${name}/unpublish`); } - // Cloud: user messages (to be overridden) - message(string) { alert(string); } - - // TODO: legacy api used by other sites using NetsBlox authentication - async register() { - return this.signup(...arguments); + // legacy api used by other sites using NetsBlox authentication + async register(username, password, email) { + return this.signup(username, password, email); } - async checkLogin() { try { await this.whoAmI(); return true; - } catch (err) { + } + catch (err) { return false; } } - async getProfile() { const profile = await this.viewUser(); return profile; } } - -class CloudError extends Error { - constructor(label, message) { +export class CloudError extends Error { + constructor(label, message = undefined) { super(message || label); this.label = label; } } - -module.exports = Cloud; diff --git a/src/client.ts b/src/client.ts new file mode 100644 index 0000000..8f5d46f --- /dev/null +++ b/src/client.ts @@ -0,0 +1,619 @@ +import 'isomorphic-fetch'; +import assert from 'assert'; + +const defaultLocalizer = text => text; +const isNodeJs = typeof window === 'undefined'; + +interface LinkedAccount { + username: string; + strategy: string; +} + +export default class Cloud { + url: string; + clientId: string; + username: string; + projectId: string | null; + roleId: string | null; + newProjectRequest: Promise | undefined; + localize: (text: string) => string; + token: string | null; + + constructor(url, clientId, username, localize=defaultLocalizer) { + this.clientId = clientId; + this.username = username; + this.projectId = null; + this.roleId = null; + this.url = url; + this.token = null; // only needed in NodeJs + this.localize = localize; + } + + clear() { + this.username = null; + this.token = null; + } + + hasProtocol() { + return this.url.toLowerCase().indexOf('http') === 0; + } + + async resetPassword(username) { + const response = await this.fetch(`/users/${username}/password`, {method: 'POST'}); + return await response.text(); + }; + + async login( + username, + password, + remember, // TODO: use this... + strategy = 'NetsBlox', + ) { + const credentials = {}; + credentials[strategy] = {username, password}; + const body = { + credentials, + clientId: this.clientId, + }; + const response = await this.post('/users/login', body); + this.username = await response.text(); + if (isNodeJs) { + const cookie = response.headers.get('set-cookie'); + assert(cookie, new CloudError('No cookie received')); + this.token = cookie.split('=')[1].split(';').shift(); + } + return this.username; + } + + async getProjectList() { + const response = await this.fetch(`/projects/user/${this.username}`); + return await response.json(); + } + + async getSharedProjectList() { + const response = await this.fetch(`/projects/shared/${this.username}`); + return await response.json(); + } + + async changePassword( + oldPW, + newPW, + ) { + const body = JSON.stringify(newPW); + const response = await this.fetch( + `/users/${this.username}/password`, + {method: 'PATCH', body} + ); + return await response.text(); + } + + parseResponse(src) { + var ans = [], + lines; + if (!src) {return ans; } + lines = src.split(" "); + lines.forEach(function (service) { + var entries = service.split("&"), + dict = {}; + entries.forEach(function (entry) { + var pair = entry.split("="), + key = decodeURIComponent(pair[0]), + val = decodeURIComponent(pair[1]); + dict[key] = val; + }); + ans.push(dict); + }); + return ans; + }; + + parseDict(src) { + var dict = {}; + if (!src) {return dict; } + src.split("&").forEach(function (entry) { + var pair = entry.split("="), + key = decodeURIComponent(pair[0]), + val = decodeURIComponent(pair[1]); + dict[key] = val; + }); + return dict; + }; + + encodeDict(dict) { + var str = '', + pair, + key; + if (!dict) {return null; } + for (key in dict) { + if (dict.hasOwnProperty(key)) { + pair = encodeURIComponent(key) + + '=' + + encodeURIComponent(dict[key]); + if (str.length > 0) { + str += '&'; + } + str += pair; + } + } + return str; + }; + + async getUserData() { + const response = await this.fetch(`/users/${this.username}`); + return await response.json(); + }; + + async addRole(name) { + const response = await this.post(`/projects/id/${this.projectId}/`, {name}); + // TODO: should I request the new project state, too? + // I shouldn't have to since we should be subscribed to changes... + //return await response.json(); + }; + + async saveRole(roleData) { + + const url = `/projects/id/${this.projectId}/${this.roleId}`; + const options = { + method: 'POST', + body: JSON.stringify(roleData), + }; + await this.fetch(url, options); + }; + + async renameRole(roleId, name) { + const body = { + name, + clientId: this.clientId, + }; + const response = await this.patch(`/projects/id/${this.projectId}/${roleId}`, body); + // TODO: error handling + //return await response.json(); + }; + + async renameProject(name) { + const body = { + name, + clientId: this.clientId, + }; + const response = await this.patch(`/projects/id/${this.projectId}`, body); + }; + + async reportLatestRole(id, data) { + const clientId = this.clientId; + const options = { + method: 'POST', + body: JSON.stringify({id, data}) + }; + await this.fetch(`/projects/id/${this.projectId}/${this.roleId}/latest?clientId=${clientId}`, options); + }; + + async cloneRole(roleId) { + const projectId = this.projectId; + const fetchRoleResponse = await this.fetch(`/projects/id/${projectId}/${roleId}/latest`); + const {name, code, media} = await fetchRoleResponse.json(); + const response = await this.post(`/projects/id/${projectId}/`, {name, code, media}); + }; + + async sendOccupantInvite(username, roleId) { + const body = {username, roleId}; + await this.post(`/network/id/${this.projectId}/occupants/invite`, body); + }; + + async evictOccupant(clientID) { + const method = 'DELETE'; + await this.fetch(`/network/id/${this.projectId}/occupants/${clientID}`, {method}); + }; + + async getCollaboratorList() { + const response = await this.get(`/projects/id/${this.projectId}/collaborators/`); + return await response.json(); + }; + + async getCollaboratorRequestList() { + const response = await this.get(`/collaboration-invites/user/${this.username}/`); + return await response.json(); + } + + async sendCollaborateRequest(username) { + await this.post(`/collaboration-invites/${this.projectId}/invite/${username}`); + }; + + async respondToCollaborateRequest(id, accepted) { + const newState = accepted ? 'ACCEPTED' : 'REJECTED'; + await this.post(`/collaboration-invites/id/${id}`, newState); + }; + + async removeCollaborator(username, projectId) { + await this.delete(`/projects/id/${projectId}/collaborators/${username}`); + }; + + async getOnlineFriendList() { + const response = await this.get(`/friends/${this.username}/online`); + return await response.json(); + } + + async getFriendList() { + const response = await this.get(`/friends/${this.username}/`); + return await response.json(); + }; + + async getRole(projectId, roleId) { + const qs = this.clientId ? `clientId=${this.clientId}` : ''; + const response = await this.fetch(`/projects/id/${projectId}/${roleId}/latest?${qs}`); + const project = await response.json(); + // TODO: Set the state here? + this.setLocalState(projectId, roleId); + return project; + }; + + async getProjectMetadata(projectId) { + const response = await this.fetch(`/projects/id/${projectId}/metadata`); + const project = await response.json(); + return project; + }; + + async getProjectByName(owner, name) { + const response = await this.fetch(`/projects/user/${owner}/${name}`); + return await response.json(); + }; + + async getProjectMetadataByName(owner, name) { + const response = await this.fetch(`/projects/user/${owner}/${name}/metadata`); + return await response.json(); + }; + + async startNetworkTrace(projectId) { + const response = await this.post(`/network/id/${projectId}/trace/`); + return await response.text(); + }; + + async stopNetworkTrace(projectId, traceId) { + const response = await this.post(`/network/id/${projectId}/trace/${traceId}/stop`); + return await response.text(); + }; + + async getNetworkTrace(projectId, traceId) { + const response = await this.fetch(`/network/id/${projectId}/trace/${traceId}`); + return await response.json(); + }; + + async getFriendRequestList() { + const response = await this.get(`/friends/${this.username}/invites/`); + return response.json(); + } + + async sendFriendRequest(username) { + await this.post(`/friends/${this.username}/invite/`, username.trim()); + } + + async unfriend(username) { + username = encodeURIComponent(username.trim()); + await this.post(`/friends/${this.username}/unfriend/${username}`); + } + + async respondToFriendRequest(sender, newState) { + sender = encodeURIComponent(sender); + await this.post(`/friends/${this.username}/invites/${sender}`, newState); + } + + async deleteRole(roleId) { + const method = 'DELETE'; + await this.fetch(`/projects/id/${this.projectId}/${roleId}`, {method}); + }; + + async deleteProject(projectId) { + const method = 'DELETE'; + await this.fetch(`/projects/id/${projectId}`, {method}); + }; + + async publishProject(projectId) { + const method = 'POST'; + await this.fetch(`/projects/id/${projectId}/publish`, {method}); + }; + + async unpublishProject(projectId) { + const method = 'POST'; + await this.fetch(`/projects/id/${projectId}/unpublish`, {method}); + }; + + reconnect(callback, errorCall) { + if (!this.username) { + this.message('You are not logged in'); + return; + } + + // need to set 'api' from setClientState + let promise = this.setClientState(); + if (callback && errorCall) { + promise = promise.then(callback) + .catch(errorCall); + } + return promise; + }; + + async logout() { + const method = 'POST'; + await this.fetch('/users/logout', {method}); + this.clear(); + return true; + }; + + async signup( + username: string, + password: string, + email: string, + ) { + const body = { + username, + password, + email, + }; + const response = await this.post('/users/create', body); + return await response.text(); + }; + + async saveProjectCopy() { + const response = await this.fetch(`/projects/${this.projectId}/latest`); + const xml = await response.text(); + const options = { + method: 'POST', + body: xml, // TODO: add options for allow rename? + }; + const saveResponse = await this.fetch(`/projects/`, options); + + // TODO: set the state with the network overlay + //this.setLocalState(response.projectId, this.roleId); + + return saveResponse.status == 200; + }; + + async patch(url: string, body = undefined) { + const opts: RequestInit = { + method: 'PATCH', + }; + if (body !== undefined) { + opts.body = JSON.stringify(body); + } + return await this.fetch(url, opts); + }; + + async post(url, body=undefined) { + const opts: RequestInit = { + method: 'POST', + }; + if (body !== undefined) { + opts.body = JSON.stringify(body); + } + return await this.fetch(url, opts); + }; + + async delete(url) { + const opts = { + method: 'DELETE', + }; + return await this.fetch(url, opts); + }; + + async get(url) { + const opts = { + method: 'GET', + }; + return await this.fetch(url, opts); + }; + + async fetch(url, opts?: RequestInit) { + opts = opts || {}; + url = this.url + url; + opts.credentials = opts.credentials || 'include'; + opts.headers = { + 'Content-Type': 'application/json' + }; + if (this.token) { + opts.headers['cookie'] = `netsblox=${this.token}`; + } + const response = await fetch(url, opts); + if (response.status > 399) { + const text = await response.text(); + const error = new CloudError(text); + await this.onerror(error); + throw error; + } + return response; + } + + async onerror(response) { + const text = await response.text(); + throw new CloudError(text); + } + + setLocalState(projectId, roleId) { + this.projectId = projectId; + this.roleId = roleId; + }; + + resetLocalState() { + this.setLocalState(null, null); + }; + + newProject(name=this.localize('untitled')) { + var myself = this; + + if (!this.newProjectRequest) { + const saveResponse = this.post(`/projects/`, {name, clientId: this.clientId}); + this.newProjectRequest = saveResponse + .then(response => response.json()) + .then(async metadata => { + const [roleId] = Object.keys(metadata.roles); + this.setClientState(metadata.id, roleId); + myself.newProjectRequest = null; + return metadata; + }) + .catch(function(req) { + myself.resetLocalState(); + myself.newProjectRequest = null; + throw new Error(req.responseText); + }); + } + + return this.newProjectRequest; + }; + + getClientState() { + return { + username: this.username, + clientId: this.clientId, + projectId: this.projectId, + roleId: this.roleId + }; + }; + + async setClientState(projectId=this.projectId, roleId=this.roleId) { + var myself = this, + newProjectRequest = this.newProjectRequest || Promise.resolve(); + + this.projectId = projectId; + this.roleId = roleId; + return newProjectRequest + .then(async () => { + const body = { + state: { + browser: { + projectId: this.projectId, + roleId: this.roleId, + } + } + }; + await this.post(`/network/${this.clientId}/state`, body); + // Only change the project ID if no other moves/newProjects/etc have occurred + }) + .catch(function(req) { + var connError = 'Could not connect to ' + myself.url; + throw new Error(req.responseText || connError); + }); + }; + + setProjectName(name) { + const newProjectRequest = this.newProjectRequest || Promise.resolve(); + + + return newProjectRequest + .then(async () => { + await this.patch(`/projects/id/${this.projectId}`, {name, clientId: this.clientId}); + }); + }; + + async importProject(projectData) { + projectData.clientId = this.clientId; + + const response = await this.post('/projects/', projectData); + return await response.json(); + }; + + async linkAccount(username, password, type) { + await this.post(`/users/${this.username}/link/`, {Snap: {username, password}}); + }; + + async unlinkAccount(account: LinkedAccount) { + await this.post(`/users/${this.username}/unlink/`, account); + }; + + async getProjectData(projectId=this.projectId) { + const response = await this.fetch(`/projects/id/${projectId}/latest?clientId=${this.clientId}`); + return await response.json(); + }; + + async exportRole(projectId=this.projectId, roleId=this.roleId) { + const response = await this.fetch(`/projects/id/${projectId}/${roleId}/latest?clientId=${this.clientId}`); + return await response.text(); + }; + + async viewUser(username=this.username) { + const response = await this.fetch(`/users/${username}`); + return await response.json(); + } + + async whoAmI() { + const response = await this.get('/users/whoami'); + return await response.text(); + } + + async getCommunityLibraryList() { + const response = await this.get('/libraries/community/'); + return await response.json(); + } + + async getLibraryList() { + const response = await this.get(`/libraries/user/${this.username}/`); + return await response.json(); + } + + async saveLibrary(name, blocks, notes) { + const library = { + name, notes, blocks + }; + const response = await this.post(`/libraries/user/${this.username}/`, library); + return await response.json(); + } + + async getLibrary(username, name) { + name = encodeURIComponent(name); + const response = await this.get(`/libraries/user/${username}/${name}`); + return await response.text(); + } + + async deleteLibrary(name) { + name = encodeURIComponent(name); + return await this.delete(`/libraries/user/${this.username}/${name}`); + } + + async publishLibrary(name) { + name = encodeURIComponent(name); + const response = await this.post(`/libraries/user/${this.username}/${name}/publish`); + return await response.json(); + } + + async unpublishLibrary(name) { + name = encodeURIComponent(name); + await this.post(`/libraries/user/${this.username}/${name}/unpublish`); + } + + // Cloud: user messages (to be overridden) + + message(string) { + alert(string); + } + + // legacy api used by other sites using NetsBlox authentication + async register( + username: string, + password: string, + email: string, + ) { + return this.signup( + username, + password, + email); + } + + async checkLogin() { + try { + await this.whoAmI(); + return true; + } catch (err) { + return false; + } + } + + async getProfile() { + const profile = await this.viewUser(); + return profile; + } +} + +export class CloudError extends Error { + label: string; + + constructor(label, message = undefined) { + super(message || label); + this.label = label; + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..3c0ab33 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "target": "es2020", + "moduleResolution": "node", + }, + "include": ["src/**/*.ts"] +}