diff --git a/frontend/package.json b/frontend/package.json index 3f8ea58f0e..1badfef171 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,6 +20,7 @@ "lit": "^2.4.1", "lodash": "^4.17.21", "pretty-ms": "^7.0.1", + "query-string": "^8.1.0", "regex-colorize": "^0.0.3", "tailwindcss": "^3.1.8", "url-pattern": "^1.0.3", @@ -42,6 +43,7 @@ "@esm-bundle/chai": "^4.3.4-fix.0", "@lit/localize-tools": "^0.6.5", "@open-wc/testing": "^3.1.7", + "@rollup/plugin-commonjs": "^18.0.0", "@types/color": "^3.0.2", "@types/lodash": "^4.14.178", "@types/sinon": "^10.0.6", @@ -49,6 +51,7 @@ "@typescript-eslint/parser": "^5.4.0", "@web/dev-server-esbuild": "^0.2.16", "@web/dev-server-import-maps": "^0.0.6", + "@web/dev-server-rollup": "^0.3.21", "@web/test-runner": "^0.13.22", "@web/test-runner-playwright": "^0.8.8", "autoprefixer": "^10.4.2", diff --git a/frontend/src/utils/APIRouter.test.ts b/frontend/src/utils/APIRouter.test.ts new file mode 100644 index 0000000000..13cb96ecb0 --- /dev/null +++ b/frontend/src/utils/APIRouter.test.ts @@ -0,0 +1,59 @@ +import { spy } from "sinon"; +import { expect } from "@esm-bundle/chai"; + +import APIRouter from "./APIRouter"; +import { ROUTES } from "../routes"; + +describe("APIRouter", () => { + describe("match", () => { + it("matches org", () => { + const apiRouter = new APIRouter(ROUTES); + const viewState = apiRouter.match("/orgs/_fake_org_id_/_fake_tab_"); + + expect(viewState.route).to.equal("org"); + expect(viewState.params).to.deep.equal({ + orgId: "_fake_org_id_", + orgTab: "_fake_tab_", + }); + }); + + it("matches join", () => { + const apiRouter = new APIRouter(ROUTES); + const viewState = apiRouter.match( + "/join/_fake_token_?email=_fake_email_" + ); + + expect(viewState.route).to.equal("join"); + expect(viewState.params).to.deep.equal({ + token: "_fake_token_", + email: "_fake_email_", + }); + }); + + it("matches join with email comment", () => { + const apiRouter = new APIRouter(ROUTES); + const viewState = apiRouter.match( + "/join/_fake_token_?email=fake+comment@email.com" + ); + + expect(viewState.route).to.equal("join"); + expect(viewState.params).to.deep.equal({ + token: "_fake_token_", + email: "fake+comment@email.com", + }); + }); + + it("matches join with encoded email", () => { + const apiRouter = new APIRouter(ROUTES); + const viewState = apiRouter.match( + "/join/_fake_token_?email=fake%2Bcomment%40email.com" + ); + + expect(viewState.route).to.equal("join"); + expect(viewState.params).to.deep.equal({ + token: "_fake_token_", + email: "fake+comment@email.com", + }); + }); + }); +}); diff --git a/frontend/src/utils/APIRouter.ts b/frontend/src/utils/APIRouter.ts index 675e782741..4b1d0e8308 100644 --- a/frontend/src/utils/APIRouter.ts +++ b/frontend/src/utils/APIRouter.ts @@ -1,4 +1,5 @@ import UrlPattern from "url-pattern"; +import queryString from "query-string"; type Routes = { [key: string]: UrlPattern }; type Paths = { [key: string]: string }; @@ -29,20 +30,25 @@ export default class APIRouter { } } - match(url: string): ViewState { + match(relativePath: string): ViewState { for (const [name, pattern] of Object.entries(this.routes)) { - const [path, qs] = url.split("?"); - const urlParams = pattern.match(path); - - if (urlParams) { + const [path, qs = ""] = relativePath.split("?"); + const match = pattern.match(path); + + if (match) { + const queryParams = queryString.parse(qs, { + // Only decode if needed, or else `+` in invite emails + // may be incorrectly decoded + decode: qs.includes("%"), + }); const params = { - ...urlParams, - ...Object.fromEntries(new URLSearchParams(qs).entries()), + ...match, + ...queryParams, }; - return { route: name, pathname: url, params }; + return { route: name, pathname: relativePath, params }; } } - return { route: null, pathname: url, params: {} }; + return { route: null, pathname: relativePath, params: {} }; } } diff --git a/frontend/src/utils/auth.test.ts b/frontend/src/utils/auth.test.ts index 4c34c85d94..d71181380f 100644 --- a/frontend/src/utils/auth.test.ts +++ b/frontend/src/utils/auth.test.ts @@ -18,7 +18,7 @@ describe("auth", () => { ); const element = new Element(); - element.connectedCallback(); + element.update(); expect(dispatchEventSpy.getCall(0).firstArg.type).to.equal("need-login"); }); diff --git a/frontend/web-test-runner.config.mjs b/frontend/web-test-runner.config.mjs index a0e33021f3..35f2c1e6b2 100644 --- a/frontend/web-test-runner.config.mjs +++ b/frontend/web-test-runner.config.mjs @@ -1,9 +1,25 @@ import { esbuildPlugin } from "@web/dev-server-esbuild"; import { importMapsPlugin } from "@web/dev-server-import-maps"; +import commonjsPlugin from "@rollup/plugin-commonjs"; +import { fromRollup } from "@web/dev-server-rollup"; +import { fileURLToPath } from "url"; + +const commonjs = fromRollup(commonjsPlugin); export default { plugins: [ - esbuildPlugin({ ts: true }), + esbuildPlugin({ + ts: true, + // tsconfig: fileURLToPath(new URL("./tsconfig.json", import.meta.url)), + target: "auto", + }), + commonjs({ + include: [ + // web-test-runner expects es modules, + // include umd/commonjs modules here: + "node_modules/url-pattern/**/*", + ], + }), importMapsPlugin({ inject: { importMap: { diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 0bcdcbad02..e9ea4db29f 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -272,6 +272,19 @@ "@types/sinon-chai" "^3.2.3" chai-a11y-axe "^1.3.2" +"@rollup/plugin-commonjs@^18.0.0": + version "18.1.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-18.1.0.tgz#5a760d757af168a50727c0ae080251fbfcc5eb02" + integrity sha512-h3e6T9rUxVMAQswpDIobfUHn/doMzM9sgkMrsMWCFLmB84PSoC8mV8tOloAJjSRwdqhXBqstlX2BwBpHJvbhxg== + dependencies: + "@rollup/pluginutils" "^3.1.0" + commondir "^1.0.1" + estree-walker "^2.0.1" + glob "^7.1.6" + is-reference "^1.2.1" + magic-string "^0.25.7" + resolve "^1.17.0" + "@rollup/plugin-node-resolve@^11.0.1": version "11.2.1" resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz#82aa59397a29cd4e13248b106e6a4a1880362a60" @@ -284,6 +297,18 @@ is-module "^1.0.0" resolve "^1.19.0" +"@rollup/plugin-node-resolve@^13.0.4": + version "13.3.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz#da1c5c5ce8316cef96a2f823d111c1e4e498801c" + integrity sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw== + dependencies: + "@rollup/pluginutils" "^3.1.0" + "@types/resolve" "1.17.1" + deepmerge "^4.2.2" + is-builtin-module "^3.1.0" + is-module "^1.0.0" + resolve "^1.19.0" + "@rollup/pluginutils@^3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" @@ -789,7 +814,7 @@ picomatch "^2.2.2" ws "^7.4.2" -"@web/dev-server-core@^0.3.18": +"@web/dev-server-core@^0.3.18", "@web/dev-server-core@^0.3.19": version "0.3.19" resolved "https://registry.yarnpkg.com/@web/dev-server-core/-/dev-server-core-0.3.19.tgz#b61f9a0b92351371347a758b30ba19e683c72e94" integrity sha512-Q/Xt4RMVebLWvALofz1C0KvP8qHbzU1EmdIA2Y1WMPJwiFJFhPxdr75p9YxK32P2t0hGs6aqqS5zE0HW9wYzYA== @@ -848,6 +873,18 @@ rollup "^2.58.0" whatwg-url "^11.0.0" +"@web/dev-server-rollup@^0.3.21": + version "0.3.21" + resolved "https://registry.yarnpkg.com/@web/dev-server-rollup/-/dev-server-rollup-0.3.21.tgz#edeecc599970fcc03f6a53fd7c5fdaf01178e88a" + integrity sha512-138t+vMFkegRip6Rtlz68Bo5rl984C9c2rLg3dWl9JEEJSQcWgA3iEwXYh4xTc52WjXnM3/LpboAjTYQOMyfrA== + dependencies: + "@rollup/plugin-node-resolve" "^13.0.4" + "@web/dev-server-core" "^0.3.19" + nanocolors "^0.2.1" + parse5 "^6.0.1" + rollup "^2.67.0" + whatwg-url "^11.0.0" + "@web/dev-server@^0.1.24": version "0.1.28" resolved "https://registry.yarnpkg.com/@web/dev-server/-/dev-server-0.1.28.tgz#fcd8a074e71554594361930803dbd0a1380d6d29" @@ -1512,6 +1549,11 @@ builtin-modules@^3.1.0: resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -1830,6 +1872,11 @@ commander@^9.4.1: resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + compressible@~2.0.16: version "2.0.18" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" @@ -2025,6 +2072,11 @@ decamelize@^5.0.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-5.0.1.tgz#db11a92e58c741ef339fb0a2868d8a06a9a7b1e9" integrity sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA== +decode-uri-component@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.4.1.tgz#2ac4859663c704be22bf7db760a1494a49ab2cc5" + integrity sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ== + deep-equal@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" @@ -2537,6 +2589,11 @@ estree-walker@^1.0.1: resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== +estree-walker@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -2718,6 +2775,11 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +filter-obj@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-5.1.0.tgz#5bd89676000a713d7db2e197f660274428e524ed" + integrity sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng== + finalhandler@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" @@ -2890,6 +2952,18 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.6: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + globals@^13.6.0, globals@^13.9.0: version "13.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.0.tgz#4d733760304230a0082ed96e21e5c565f898089e" @@ -3287,6 +3361,13 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-builtin-module@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + is-core-module@^2.2.0: version "2.7.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.7.0.tgz#3c0ef7d31b4acfc574f80c58409d568a836848e3" @@ -3398,6 +3479,13 @@ is-potential-custom-element-name@^1.0.0: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== +is-reference@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" + integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== + dependencies: + "@types/estree" "*" + is-regex@^1.0.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -3830,6 +3918,13 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +magic-string@^0.25.7: + version "0.25.9" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" + integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== + dependencies: + sourcemap-codec "^1.4.8" + make-dir@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -3979,6 +4074,13 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + minimist-options@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" @@ -4755,6 +4857,15 @@ qs@^6.5.2: dependencies: side-channel "^1.0.4" +query-string@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-8.1.0.tgz#e7f95367737219544cd360a11a4f4ca03836e115" + integrity sha512-BFQeWxJOZxZGix7y+SByG3F36dA0AbTy9o6pSmKFcFz7DAj0re9Frkty3saBn3nHo3D0oZJ/+rx3r8H8r8Jbpw== + dependencies: + decode-uri-component "^0.4.1" + filter-obj "^5.1.0" + split-on-first "^3.0.0" + querystring@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" @@ -4946,7 +5057,7 @@ resolve-path@^1.4.0: http-errors "~1.6.2" path-is-absolute "1.0.1" -resolve@^1.1.7, resolve@^1.22.1: +resolve@^1.1.7, resolve@^1.17.0, resolve@^1.22.1: version "1.22.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== @@ -5005,6 +5116,13 @@ rollup@^2.58.0: optionalDependencies: fsevents "~2.3.2" +rollup@^2.67.0: + version "2.79.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7" + integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw== + optionalDependencies: + fsevents "~2.3.2" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -5291,6 +5409,11 @@ source-map@^0.7.3, source-map@~0.7.2: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== +sourcemap-codec@^1.4.8: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + spdx-correct@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" @@ -5340,6 +5463,11 @@ spdy@^4.0.2: select-hose "^2.0.0" spdy-transport "^3.0.0" +split-on-first@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-3.0.0.tgz#f04959c9ea8101b9b0bbf35a61b9ebea784a23e7" + integrity sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA== + stack-utils@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5"