diff --git a/README.md b/README.md index a73233ce0..4b712e470 100644 --- a/README.md +++ b/README.md @@ -144,30 +144,25 @@ The continued development and maintenance of GitHub1s is made possible by these ### Chrome Extensions -- [chouglesaud/](https://github.com/chouglesaud)[repositree](https://github.com/chouglesaud/repositree) - [Chrome Web Store](https://chrome.google.com/webstore/detail/repositree/lafjldoccjnjlcmdhmniholdpjkbgajo) - -- [zulhfreelancer](https://github.com/zulhfreelancer)[Open in VS Code](https://chrome.google.com/webstore/detail/open-in-vs-code-github1sc/neloiopjjeflfnecdlajhopdlojlkhll) - -- [fhefh2015/GitHub1s_chrome_extension](https://github.com/fhefh2015/GitHub1s_chrome_extension) - [Chrome Web Store](https://chrome.google.com/webstore/detail/github1s/lodjfmkfbfkpdhnhkcdcoonghhghbkhe) - -- [febaoshan/edge-extensions-github-code-viewer](https://github.com/febaoshan/edge-extensions-github-code-viewer) - [Chrome Web Store](https://chrome.google.com/webstore/detail/github-code-viewer/ecddapgifccgblebfibdgkagfbdagjfn) - -- [Darkempire78/GitHub1s-Extension](https://github.com/Darkempire78/GitHub1s-Extension) -- [zvizvi/GitHub Web IDE](https://github.com/zvizvi/github-web-ide) - [Chrome Web Store](https://chrome.google.com/webstore/detail/adjiklnjodbiaioggfpbpkhbfcnhgkfe) -- [katsuhisa91/github1s-shortcut](https://github.com/katsuhisa91/github1s-shortcut) - [Chrome Web Store](https://chrome.google.com/webstore/detail/shortcut-to-github1s/gfcdbodapcbfckbfpmgeldfkkgjknceo) +- [Repositree](https://chrome.google.com/webstore/detail/repositree/lafjldoccjnjlcmdhmniholdpjkbgajo) ([chouglesaud/repositree](https://github.com/chouglesaud/repositree)) +- [Open in VS Code](https://chrome.google.com/webstore/detail/open-in-vs-code-github1sc/neloiopjjeflfnecdlajhopdlojlkhll) by [zulhfreelancer](https://github.com/zulhfreelancer) +- [GitHub1s](https://chrome.google.com/webstore/detail/github1s/lodjfmkfbfkpdhnhkcdcoonghhghbkhe) ([fhefh2015/GitHub1s_chrome_extension](https://github.com/fhefh2015/GitHub1s_chrome_extension)) +- [github-code-viewer](https://chrome.google.com/webstore/detail/github-code-viewer/ecddapgifccgblebfibdgkagfbdagjfn) ([febaoshan/edge-extensions-github-code-viewer](https://github.com/febaoshan/edge-extensions-github-code-viewer)) +- Github1s Extension ([Darkempire78/GitHub1s-Extension](https://github.com/Darkempire78/GitHub1s-Extension)) +- [Github Web IDE](https://chrome.google.com/webstore/detail/adjiklnjodbiaioggfpbpkhbfcnhgkfe) ([zvizvi/Github-Web-IDE](https://github.com/zvizvi/Github-Web-IDE)) +- [shortcut to github1s](https://chrome.google.com/webstore/detail/shortcut-to-github1s/gfcdbodapcbfckbfpmgeldfkkgjknceo) ([katsuhisa91/github1s-shortcut](https://github.com/katsuhisa91/github1s-shortcut)) ### Firefox Extensions -- [chouglesaud/](https://github.com/chouglesaud)[repositree](https://github.com/chouglesaud/repositree) - [Firefox Browser Addons](https://addons.mozilla.org/en-US/firefox/addon/repositree/) - -- [Darkempire78/GitHub1s-Extension](https://github.com/Darkempire78/GitHub1s-Extension) - [Firefox Browser Addons](https://addons.mozilla.org/firefox/addon/github1s-extension) -- [mcherifi/github1s-firefox-addon](https://github.com/mcherifi/github1s-firefox-addon) - [Firefox Browser Addons](https://addons.mozilla.org/firefox/addon/github1s/) -- [zvizvi/GitHub Web IDE](https://github.com/zvizvi/github-web-ide) - [Firefox Browser Addons](https://addons.mozilla.org/firefox/addon/github-web-ide/) +- [Repositree](https://addons.mozilla.org/en-US/firefox/addon/repositree/) ([chouglesaud/repositree](https://github.com/chouglesaud/repositree)) +- [Github1s Extension](https://addons.mozilla.org/firefox/addon/github1s-extension) ([Darkempire78/GitHub1s-Extension](https://github.com/Darkempire78/GitHub1s-Extension)) +- [Github1s](https://addons.mozilla.org/firefox/addon/github1s/) ([mcherifi/github1s-firefox-addon](https://github.com/mcherifi/github1s-firefox-addon)) +- [Github Web IDE](https://addons.mozilla.org/firefox/addon/github-web-ide/) ([zvizvi/Github-Web-IDE](https://github.com/zvizvi/Github-Web-IDE)) ### Microsoft Edge Extensions -- [febaoshan/edge-extensions-github-code-viewer](https://github.com/febaoshan/edge-extensions-github-code-viewer) -- [zvizvi/GitHub Web IDE](https://github.com/zvizvi/github-web-ide) - [Edge Add-ons Store](https://microsoftedge.microsoft.com/addons/detail/akjbkjciknacicbnkfjbnlaeednpadcf) +- [github-code-viewer](https://microsoftedge.microsoft.com/addons/detail/githubcodeviewer/jaaaapanahkknbgdbglnlchbjfhhjlpi) ([febaoshan/edge-extensions-github-code-viewer](https://github.com/febaoshan/edge-extensions-github-code-viewer)) +- [Github Web IDE](https://microsoftedge.microsoft.com/addons/detail/akjbkjciknacicbnkfjbnlaeednpadcf) ([zvizvi/Github-Web-IDE](https://github.com/zvizvi/Github-Web-IDE)) ### Tampermonkey scripts diff --git a/api/github-auth-callback/index.js b/api/github-auth-callback/index.js new file mode 100644 index 000000000..a1db0ebcf --- /dev/null +++ b/api/github-auth-callback/index.js @@ -0,0 +1,62 @@ +/** + * @file github auth callback + * @author netcon + */ + +const got = require('got'); + +const CLIENT_ID = process.env.GITHUB_OAUTH_ID || ''; +const CLIENT_SECRET = process.env.GITHUB_OAUTH_SECRET || ''; +// allow origins should split by ',' +const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS || ''; + +// return the data to the opener window by postMessage API, +// and close current window then +const getResponseHtml = (dataStr) => ` + +`; + +const MISSING_CODE_ERROR = { + error: 'request_invalid', + error_description: 'Missing code', +}; +const UNKNOWN_ERROR = { + error: 'internal_error', + error_description: 'Unknown error', +}; + +module.exports = async (req, res) => { + const code = req.query.code; + const sendResponseHtml = (status, data) => { + res.status(status); + const responseData = { type: 'authorizing', payload: data }; + res.send(getResponseHtml(JSON.stringify(responseData))); + }; + + if (!code) { + return sendResponseHtml(401, MISSING_CODE_ERROR); + } + + try { + // https://docs.github.com/en/developers/apps/authorizing-oauth-apps#2-users-are-redirected-back-to-your-site-by-github + const response = await got.post( + 'https://github.com/login/oauth/access_token', + { + json: { client_id: CLIENT_ID, client_secret: CLIENT_SECRET, code }, + responseType: 'json', + } + ); + return sendResponseHtml(response.statusCode, response.body); + } catch (e) { + // the error is responded by GitHub + if (e.response) { + return sendResponseHtml(e.response.statusCode, e.response.body); + } + return sendResponseHtml(500, UNKNOWN_ERROR); + } +}; diff --git a/api/github-auth-callback/package.json b/api/github-auth-callback/package.json new file mode 100644 index 000000000..3b69c9330 --- /dev/null +++ b/api/github-auth-callback/package.json @@ -0,0 +1,10 @@ +{ + "name": "github-auth-callback", + "version": "0.0.0", + "main": "index.js", + "license": "MIT", + "private": true, + "dependencies": { + "got": "^11.8.2" + } +} diff --git a/api/github-auth-callback/yarn.lock b/api/github-auth-callback/yarn.lock new file mode 100644 index 000000000..b0648e453 --- /dev/null +++ b/api/github-auth-callback/yarn.lock @@ -0,0 +1,204 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@sindresorhus/is@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.1.tgz#d26729db850fa327b7cacc5522252194404226f5" + integrity sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g== + +"@szmarczak/http-timer@^4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152" + integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ== + dependencies: + defer-to-connect "^2.0.0" + +"@types/cacheable-request@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976" + integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "*" + "@types/node" "*" + "@types/responselike" "*" + +"@types/http-cache-semantics@*": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a" + integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== + +"@types/keyv@*": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7" + integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "15.0.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-15.0.2.tgz#51e9c0920d1b45936ea04341aa3e2e58d339fb67" + integrity sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA== + +"@types/responselike@*", "@types/responselike@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" + integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== + dependencies: + "@types/node" "*" + +cacheable-lookup@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" + integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== + +cacheable-request@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58" + integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^2.0.0" + +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + dependencies: + mimic-response "^1.0.0" + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + +defer-to-connect@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +got@^11.8.2: + version "11.8.2" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" + integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ== + dependencies: + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.1" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + +http-cache-semantics@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + +http2-wrapper@^1.0.0-beta.5.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" + integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +keyv@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" + integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA== + dependencies: + json-buffer "3.0.1" + +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + +mimic-response@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + +normalize-url@^4.1.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" + integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== + +once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +p-cancelable@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" + integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + +resolve-alpn@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.1.2.tgz#30b60cfbb0c0b8dc897940fe13fe255afcdd4d28" + integrity sha512-8OyfzhAtA32LVUsJSke3auIyINcwdh5l3cvYKdKO0nvsYSKuiLfTM5i78PJswFPT8y6cPW+L1v6/hE95chcpDA== + +responselike@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" + integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== + dependencies: + lowercase-keys "^2.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= diff --git a/api/static-fallback/index.js b/api/static-fallback/index.js new file mode 100644 index 000000000..32c04b7c3 --- /dev/null +++ b/api/static-fallback/index.js @@ -0,0 +1,17 @@ +/** + * @file static files not found fallback + * According to the config in vercel.json, we cached the + * static files with `max-age=X` header, but we may get 404 + * response durning deploying, the default behavior for vercel + * would add the cache header to this 404 response, which caused + * the user can not get right resources anymore. so we should + * fallback to here and clear the cache header for such 404 requests. + * See also: https://github.com/conwnet/github1s/issues/299 + * @author netcon + */ + +module.exports = async (req, res) => { + res.status(404); + res.setHeader('Cache-Control', 'no-store'); + res.send('Not Found'); +}; diff --git a/extensions/github1s/src/commands/index.ts b/extensions/github1s/src/commands/index.ts index 871e6c8f2..de1b04e37 100644 --- a/extensions/github1s/src/commands/index.ts +++ b/extensions/github1s/src/commands/index.ts @@ -9,6 +9,8 @@ import { commandValidateToken, commandUpdateToken, commandClearToken, + commandAuthorizingGithub, + commandAuthorizingGithubWithOverlay, } from './token'; import { commandGetCurrentAuthority, commandCheckoutRef } from './ref'; import { @@ -48,6 +50,10 @@ const commands: { id: string; callback: (...args: any[]) => any }[] = [ { id: 'github1s.update-token', callback: commandUpdateToken }, // clear GitHub OAuth Token { id: 'github1s.clear-token', callback: commandClearToken }, + // authorizing github with `Web application flow` + { id: 'github1s.authorizing-github', callback: commandAuthorizingGithub }, + // open the overlay on the page and authorizing github with `Web application flow` + { id: 'github1s.authorizing-github-with-overlay', callback: commandAuthorizingGithubWithOverlay }, // prettier-ignore // get current authority (`${owner}+${repo}+${ref}`) { id: 'github1s.get-current-authority', callback: commandGetCurrentAuthority }, // prettier-ignore diff --git a/extensions/github1s/src/commands/token.ts b/extensions/github1s/src/commands/token.ts index d788274cd..330fa9988 100644 --- a/extensions/github1s/src/commands/token.ts +++ b/extensions/github1s/src/commands/token.ts @@ -8,8 +8,13 @@ import { getExtensionContext, getOAuthToken } from '@/helpers/context'; import { validateToken } from '@/interfaces/github-api-rest'; import { GITHUB_OAUTH_TOKEN } from '@/helpers/constants'; -export const commandValidateToken = (silent: boolean = false) => { - const oAuthToken = getOAuthToken(); +export const commandValidateToken = ( + token: string, + silent: boolean = false +) => { + // if we can not get a token from arguments, + // just verify current token which has been saved + const oAuthToken = token || getOAuthToken(); return validateToken(oAuthToken).then((tokenStatus) => { if (!silent) { const remaining = tokenStatus.remaining; @@ -41,43 +46,46 @@ export const commandValidateToken = (silent: boolean = false) => { }); }; -export const commandUpdateToken = (silent: boolean = false) => { - return vscode.window - .showInputBox({ +export const commandUpdateToken = async ( + token: string, + silent: boolean = false +) => { + if (!token) { + // if the token isn't passed by arguments, + // open an input box and request user input it + token = await vscode.window.showInputBox({ placeHolder: 'Please input the GitHub OAuth Token', - }) - .then((token) => { - if (!token) { - return; - } - return getExtensionContext()! - .globalState.update(GITHUB_OAUTH_TOKEN, token || '') - .then(() => { - // we don't need wait validate, so we don't `return` - validateToken(token).then((tokenStatus) => { - if (!silent) { - if (!tokenStatus.valid) { - vscode.window.showErrorMessage( - 'GitHub OAuth Token have updated, but it is invalid.' - ); - } else if (tokenStatus.remaining <= 0) { - vscode.window.showWarningMessage( - 'GitHub OAuth Token have updated, but the rate limit is exceeded.' - ); - } else { - vscode.window.showInformationMessage( - 'GitHub OAuth Token have updated.' - ); - } - } - tokenStatus.valid && - tokenStatus.remaining > 0 && - vscode.commands.executeCommand( - 'workbench.files.action.refreshFilesExplorer' - ); - }); - }); }); + } + + if (!token) { + return; + } + + await getExtensionContext()!.globalState.update( + GITHUB_OAUTH_TOKEN, + token || '' + ); + vscode.commands.executeCommand('workbench.files.action.refreshFilesExplorer'); + + if (silent) { + return; + } + + // we don't need wait validate, so we don't `return` + validateToken(token).then((tokenStatus) => { + if (!tokenStatus.valid) { + vscode.window.showErrorMessage( + 'GitHub OAuth Token have updated, but it is invalid.' + ); + } else if (tokenStatus.remaining <= 0) { + vscode.window.showWarningMessage( + 'GitHub OAuth Token have updated, but the rate limit is exceeded.' + ); + } else { + vscode.window.showInformationMessage('GitHub OAuth Token have updated.'); + } + }); }; export const commandClearToken = (silent: boolean = false) => { @@ -102,3 +110,45 @@ export const commandClearToken = (silent: boolean = false) => { return false; }); }; + +type AuthMessageData = + | { access_token: string; token_type: string; scope: string } + | { error: string; error_description: string; error_uri?: string }; + +export const commandAuthorizingGithub = async ( + silent: boolean = false +): Promise => { + // vscode-web-github1s/src/vs/github1s/authorizing-github.ts + // retry with authorizing overlay if browser blocked opening authorizing window + const data: AuthMessageData = await vscode.commands.executeCommand( + 'github1s.vscode.get-github-access-token', + true + ); + + if ('access_token' in data) { + // update the access_token into extension context + await commandUpdateToken(data.access_token); + return data.access_token; + } + + if ('error' in data && !silent) { + const seeMoreLinkText = data.error_uri ? ` [See more](data.error_uri)` : ''; + vscode.window.showErrorMessage( + `${data.error_description}${seeMoreLinkText}` + ); + } + return ''; +}; + +// open the overlay on the page, and get the access_token from it +export const commandAuthorizingGithubWithOverlay = async () => { + const data: AuthMessageData = await vscode.commands.executeCommand( + 'github1s.vscode.get-github-access-token-with-overlay' + ); + + if ('access_token' in data) { + await commandUpdateToken(data.access_token); + return data.access_token; + } + return ''; +}; diff --git a/extensions/github1s/src/helpers/fetch.ts b/extensions/github1s/src/helpers/fetch.ts index 1d75d7d02..e4393a365 100644 --- a/extensions/github1s/src/helpers/fetch.ts +++ b/extensions/github1s/src/helpers/fetch.ts @@ -59,9 +59,12 @@ export const getFetchOptions = (forceUpdate?: boolean): RequestInit => { const cache = new Map(); +const isGitHubApi = (url: string) => url.startsWith('https://api.github.com/'); + export const fetch = reuseable(async (url: string, options?: RequestInit) => { const token = getOAuthToken(); - const authHeaders = token ? { Authorization: `token ${token}` } : {}; + const authHeaders = + token && isGitHubApi(url) ? { Authorization: `token ${token}` } : {}; const customHeaders = options && 'headers' in options ? options.headers : {}; /** * We are reusing the same values from the https://developer.mozilla.org/en-US/docs/Web/API/Request/cache. @@ -92,12 +95,23 @@ export const fetch = reuseable(async (url: string, options?: RequestInit) => { return cache.get(url); } if (response.status === 403) { + // if there is no token saved and the rate limit exceeded, + // open the authorizing overly for requesting a access token + if (!token && isGitHubApi(url)) { + vscode.commands.executeCommand( + 'github1s.authorizing-github-with-overlay' + ); + } return response.json().then((data) => { throw new RequestRateLimitError(data.message, token); }); } if (response.status === 401) { return response.json().then((data) => { + // current token is invalid + if (data.message?.includes('Bad credentials')) { + vscode.commands.executeCommand('github1s.views.settings.focus'); + } throw new RequestInvalidTokenError(data.message, token); }); } diff --git a/extensions/github1s/src/interfaces/github-api-rest.ts b/extensions/github1s/src/interfaces/github-api-rest.ts index 33ba719c5..9413b2664 100644 --- a/extensions/github1s/src/interfaces/github-api-rest.ts +++ b/extensions/github1s/src/interfaces/github-api-rest.ts @@ -74,7 +74,7 @@ export const readGitHubFile = ( export const validateToken = (token: string) => { const authHeaders = token ? { Authorization: `token ${token}` } : {}; return self - .fetch(`https://api.github.com`, { headers: { ...authHeaders } }) + .fetch(`https://api.github.com/`, { headers: { ...authHeaders } }) .then((response) => ({ token: !!token, // if the token is not empty valid: response.status !== 401 ? true : false, // if the request is valid diff --git a/extensions/github1s/src/views/settings-view.ts b/extensions/github1s/src/views/settings-view.ts index 14bc2a502..c4a357201 100644 --- a/extensions/github1s/src/views/settings-view.ts +++ b/extensions/github1s/src/views/settings-view.ts @@ -60,6 +60,9 @@ export class SettingsView implements vscode.WebviewViewProvider { case 'welcome-page': vscode.commands.executeCommand('workbench.action.showWelcomePage'); break; + case 'authorizing-github': + this.handleAuthorizingGithub(); + break; default: const oauthToken = (this._extensionContext.globalState.get( @@ -148,6 +151,21 @@ export class SettingsView implements vscode.WebviewViewProvider { .catch(() => this.updateWebviewState({ token, validating: false })); } + async handleAuthorizingGithub() { + const token: string = await vscode.commands.executeCommand( + 'github1s.authorizing-github' + ); + if (!token) { + return; + } + this.updateWebviewState({ + token, + valid: true, + pageType: 'PREVIEW', + validating: false, + }); + } + _getHtmlForWebview(webview): string { const nonce = getNonce(); @@ -301,6 +319,14 @@ button.secondary:hover { margin-bottom: 0; } +.authorizing-method-block { + margin-bottom: 8px; +} + +.authorizing-method-title { + margin-bottom: 8px; +} + .token-link { margin-bottom: 10px; } @@ -316,13 +342,20 @@ button.secondary:hover {
For unauthenticated requests, the rate limit of GitHub allows for up to 60 requests per hour.
For API requests using Authentication, you can make up to 5,000 requests per hour.
-
@@ -393,6 +426,10 @@ button.secondary:hover { vscode.postMessage({ type: 'welcome-page' }); }); + delegate(document.body, '#authorizing-button', 'click', () => { + vscode.postMessage({ type: 'authorizing-github' }) + }); + const updatePage = () => { const { token, pageType, valid, validating } = vscode.getState() || { token: '', preview: 'EDIT', valid: true, validating: true }; if (validating) { diff --git a/extensions/jupyter-web/yarn.lock b/extensions/jupyter-web/yarn.lock index 9e9cffea8..5dcc9f27c 100644 --- a/extensions/jupyter-web/yarn.lock +++ b/extensions/jupyter-web/yarn.lock @@ -969,9 +969,9 @@ locate-path@^5.0.0: p-locate "^4.1.0" lodash@^4.17.4, lodash@^4.3.0: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== lru-cache@^4.0.1: version "4.1.5" diff --git a/package.json b/package.json index 571200832..87c9ccebf 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "lib": "lib" }, "devDependencies": { - "@github1s/vscode-web": "0.1.2", + "@github1s/vscode-web": "0.1.8", "@typescript-eslint/eslint-plugin": "^4.15.0", "@typescript-eslint/parser": "^4.15.0", "chokidar": "^3.5.1", diff --git a/resources/favicon-dark.ico b/resources/favicon-dark.ico deleted file mode 100644 index 12f05d0c2..000000000 Binary files a/resources/favicon-dark.ico and /dev/null differ diff --git a/resources/favicon-dark.svg b/resources/favicon-dark.svg new file mode 100644 index 000000000..439c80809 --- /dev/null +++ b/resources/favicon-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/favicon-light.ico b/resources/favicon-light.ico deleted file mode 100644 index f64685874..000000000 Binary files a/resources/favicon-light.ico and /dev/null differ diff --git a/resources/favicon-light.svg b/resources/favicon-light.svg new file mode 100644 index 000000000..08bd50e08 --- /dev/null +++ b/resources/favicon-light.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/favicon.ico b/resources/favicon.ico new file mode 100644 index 000000000..9b88bce89 Binary files /dev/null and b/resources/favicon.ico differ diff --git a/resources/index-dev-vscode.html b/resources/index-dev-vscode.html index 2239de4a6..7ad7e99ba 100644 --- a/resources/index-dev-vscode.html +++ b/resources/index-dev-vscode.html @@ -12,8 +12,8 @@ - - + + GitHub1s diff --git a/resources/index-hash.html b/resources/index-hash.html index a0bfa01d0..8bbeb9ebb 100644 --- a/resources/index-hash.html +++ b/resources/index-hash.html @@ -12,8 +12,8 @@ - - + + diff --git a/resources/index.html b/resources/index.html index d608acffd..253867c73 100644 --- a/resources/index.html +++ b/resources/index.html @@ -12,8 +12,8 @@ - - + + diff --git a/scripts/package/copy-resources.sh b/scripts/package/copy-resources.sh index 15c5c2ecd..7d78bcf4f 100755 --- a/scripts/package/copy-resources.sh +++ b/scripts/package/copy-resources.sh @@ -13,7 +13,7 @@ function main() { else cp resources/index.html dist/index.html fi - cp resources/*.ico dist + cp resources/favicon* dist cp resources/manifest.json dist echo "copy resources done!" diff --git a/tests/__tests__/index.test.ts b/tests/__tests__/index.test.ts index 3a4289cf9..373dbb0ac 100644 --- a/tests/__tests__/index.test.ts +++ b/tests/__tests__/index.test.ts @@ -43,7 +43,7 @@ it('should load successfully', async () => { // Make sure the repo loads await page.click('div[role="tab"]'); // GitHub repo Link available - await page.$eval('a[title="Home"][aria-label="Home"]', (el) => el.innerHTML); + await page.$eval('div.home-bar[role="toolbar"]', (el) => el.innerHTML); // File explorer available await page.$eval( 'div[role="tree"][aria-label="Files Explorer"]', diff --git a/tests/yarn.lock b/tests/yarn.lock index ebd3a1fa6..f4f3c1c6e 100644 --- a/tests/yarn.lock +++ b/tests/yarn.lock @@ -1971,9 +1971,9 @@ homedir-polyfill@^1.0.0: parse-passwd "^1.0.0" hosted-git-info@^2.1.4: - version "2.8.8" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" - integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== html-encoding-sniffer@^2.0.1: version "2.0.1" diff --git a/vercel.json b/vercel.json index a04641195..6a1e04a10 100644 --- a/vercel.json +++ b/vercel.json @@ -1,27 +1,49 @@ { - "routes": [ + "rewrites": [ { - "src": "/api/sourcegraph", - "dest": "https://sourcegraph.com/.api/graphql" + "source": "/api/sourcegraph", + "destination": "https://sourcegraph.com/.api/graphql" }, { - "src": "/static/(.*)", - "dest": "/static/$1", - "headers": { - "Cache-Control": "max-age=604800" - } + "source": "/api/github-auth-callback", + "destination": "/api/github-auth-callback" }, { - "src": "/favicon(-dark|-light)?.ico", - "dest": "/favicon$1.ico" + "source": "/static/(.*)", + "destination": "/api/static-fallback" }, { - "src": "/manifest.json", - "dest": "/manifest.json" + "source": "/(.*)", + "destination": "/index.html" + } + ], + "headers": [ + { + "source": "/static/(.*)", + "headers": [ + { + "key": "Cache-Control", + "value": "public, max-age=2592000, immutable" + } + ] + }, + { + "source": "/favicon(-dark|-light)?\\.(ico|svg)", + "headers": [ + { + "key": "Cache-Control", + "value": "public, max-age=3600" + } + ] }, { - "src": ".*", - "dest": "/index.html" + "source": "/manifest.json", + "headers": [ + { + "key": "Cache-Control", + "value": "public, max-age=3600" + } + ] } ] -} \ No newline at end of file +} diff --git a/vscode-web-github1s/package.json b/vscode-web-github1s/package.json index 6359738c4..4a3b604b9 100644 --- a/vscode-web-github1s/package.json +++ b/vscode-web-github1s/package.json @@ -1,6 +1,6 @@ { "name": "@github1s/vscode-web", - "version": "0.1.2", + "version": "0.1.8", "description": "VS Code web for GitHub1s", "author": "github1s", "license": "MIT", @@ -38,6 +38,7 @@ "xterm-addon-webgl": "0.10.0-beta.1" }, "devDependencies": { + "@types/trusted-types": "^2.0.0", "npm-run-all": "^4.1.5" } } diff --git a/vscode-web-github1s/src/vs/base/worker/workerMain.ts b/vscode-web-github1s/src/vs/base/worker/workerMain.ts new file mode 100644 index 000000000..5765195d4 --- /dev/null +++ b/vscode-web-github1s/src/vs/base/worker/workerMain.ts @@ -0,0 +1,101 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +(function () { + + const MonacoEnvironment = (self).MonacoEnvironment; + const monacoBaseUrl = MonacoEnvironment && MonacoEnvironment.baseUrl ? MonacoEnvironment.baseUrl : '../../../'; + + const trustedTypesPolicy = ( + typeof self.trustedTypes?.createPolicy === 'function' + ? self.trustedTypes?.createPolicy('amdLoader', { + createScriptURL: value => value, + createScript: (_, ...args: string[]) => { + // workaround a chrome issue not allowing to create new functions + // see https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor + const fnArgs = args.slice(0, -1).join(','); + const fnBody = args.pop()!.toString(); + const body = `(function anonymous(${fnArgs}) {\n${fnBody}\n})`; + return body; + } + }) + : undefined + ); + + function loadAMDLoader() { + return new Promise((resolve, reject) => { + if (typeof (self).define === 'function' && (self).define.amd) { + return resolve(); + } + const loaderSrc: string | TrustedScriptURL = monacoBaseUrl + 'vs/loader.js'; + + const isCrossOrigin = (/^((http:)|(https:)|(file:))/.test(loaderSrc) && loaderSrc.substring(0, self.origin.length) !== self.origin); + if (!isCrossOrigin) { + // use `fetch` if possible because `importScripts` + // is synchronous and can lead to deadlocks on Safari + fetch(loaderSrc).then((response) => { + if (response.status !== 200) { + throw new Error(response.statusText); + } + return response.text(); + }).then((text) => { + text = `${text}\n//# sourceURL=${loaderSrc}`; + const func = ( + trustedTypesPolicy + // below codes are changed by github1s + // fix error in webworker for old browsers + ? self.eval(trustedTypesPolicy.createScript('', text).toString()) + // above codes are changed by github1s + : new Function(text) + ); + func.call(self); + resolve(); + }).then(undefined, reject); + return; + } + + if (trustedTypesPolicy) { + importScripts(trustedTypesPolicy.createScriptURL(loaderSrc) as unknown as string); + } else { + importScripts(loaderSrc as string); + } + resolve(); + }); + } + + const loadCode = function (moduleId: string) { + loadAMDLoader().then(() => { + require.config({ + baseUrl: monacoBaseUrl, + catchError: true, + trustedTypesPolicy, + }); + require([moduleId], function (ws) { + setTimeout(function () { + let messageHandler = ws.create((msg: any, transfer?: Transferable[]) => { + (self).postMessage(msg, transfer); + }, null); + + self.onmessage = (e: MessageEvent) => messageHandler.onmessage(e.data); + while (beforeReadyMessages.length > 0) { + self.onmessage(beforeReadyMessages.shift()!); + } + }, 0); + }); + }); + }; + + let isFirstMessage = true; + let beforeReadyMessages: MessageEvent[] = []; + self.onmessage = (message: MessageEvent) => { + if (!isFirstMessage) { + beforeReadyMessages.push(message); + return; + } + + isFirstMessage = false; + loadCode(message.data); + }; +})(); diff --git a/vscode-web-github1s/src/vs/code/browser/workbench/workbench.ts b/vscode-web-github1s/src/vs/code/browser/workbench/workbench.ts index 5eb930f22..7cd7ff01f 100644 --- a/vscode-web-github1s/src/vs/code/browser/workbench/workbench.ts +++ b/vscode-web-github1s/src/vs/code/browser/workbench/workbench.ts @@ -23,6 +23,10 @@ import { parseLogLevel } from 'vs/platform/log/common/log'; import { getBrowserUrl, replaceBrowserUrl } from 'vs/github1s/util'; // eslint-disable-next-line import { renderNotification } from 'vs/github1s/notification'; +// eslint-disable-next-line +import { getGitHubAccessToken } from 'vs/github1s/authorizing-github'; +// eslint-disable-next-line +import { getGitHubAccessTokenWithOverlay, hideAuthorizingOverlay } from 'vs/github1s/authorizing-overlay'; // custom vs code commands defined by github1s const getGitHub1sCustomCommands: () => { @@ -31,6 +35,9 @@ const getGitHub1sCustomCommands: () => { }[] = () => [ { id: 'github1s.vscode.get-browser-url', handler: getBrowserUrl }, { id: 'github1s.vscode.replace-browser-url', handler: replaceBrowserUrl }, + { id: 'github1s.vscode.get-github-access-token', handler: getGitHubAccessToken }, + { id: 'github1s.vscode.get-github-access-token-with-overlay', handler: getGitHubAccessTokenWithOverlay }, + { id: 'github1s.vscode.hide-authorizing-overlay', handler: hideAuthorizingOverlay }, ]; // above codes are changed by github1s diff --git a/vscode-web-github1s/src/vs/github1s/authorizing-github.ts b/vscode-web-github1s/src/vs/github1s/authorizing-github.ts new file mode 100644 index 000000000..526f7f591 --- /dev/null +++ b/vscode-web-github1s/src/vs/github1s/authorizing-github.ts @@ -0,0 +1,59 @@ +/** + * @file Authorizing to GitHub with OAuth App flow + * @doc https://docs.github.com/en/developers/apps/authorizing-oauth-apps + * @author netcon + */ + +import { commands } from 'vs/workbench/workbench.web.api'; + +const CLIENT_ID = 'eae6621348403ea49103'; +const GITHUB_AUTH_URL = `https://github.com/login/oauth/authorize?scope=repo,user:email&client_id=${CLIENT_ID}`; +const OPEN_WINDOW_FEATURES = 'directories=no,titlebar=no,toolbar=no,location=no,status=no,menubar=no,scrollbars=no,resizable=no,width=800,height=520,top=150,left=150'; +const AUTH_PAGE_ORIGIN = 'https://auth.github1s.com'; + +const timeout = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +export type AuthMessageData = + | { access_token: string; token_type?: string; scope?: string } + | { error: string; error_description: string; error_uri?: string }; + +export const getGitHubAccessToken = (retryWithOverlay: boolean = false) => { + const opener = window.open(GITHUB_AUTH_URL, '_blank', OPEN_WINDOW_FEATURES); + + return new Promise((resolve) => { + let overlayOpened = false; + // if this function isn't called by a user event, + // the browser may block opening a new window, + // open the overlay to try authorizing again in this case + if (!opener && retryWithOverlay) { + overlayOpened = true; + commands.executeCommand('github1s.vscode.get-github-access-token-with-overlay') + .then(data => resolve(data as AuthMessageData)); + } + + const handleAuthMessage = (event: MessageEvent<{ type: string, payload: AuthMessageData }>) => { + // Note that though the browser block opening window and popup a tip, + // the user can be still open it from the tip. In this case, the `opener` + // is null, and we should still process the authorizing message + if (event.origin !== AUTH_PAGE_ORIGIN || (opener && event.source !== opener) || event.data?.type !== 'authorizing') { + return; + } + // if we have retried with overlay, but we received a message from the + // blocked window before, and there is no token in it, just ignore the message + if (overlayOpened && !('access_token' in event.data.payload)) { + return; + } + // if we get the token here, and the overlay is opened, just close it + overlayOpened && commands.executeCommand('github1s.vscode.hide-authorizing-overlay'); + window.removeEventListener('message', handleAuthMessage); + resolve(event.data.payload); + }; + + window.addEventListener('message', handleAuthMessage); + // if there isn't any message from opener window in 300s, remove the message handler + timeout(300 * 1000).then(() => { + window.removeEventListener('message', handleAuthMessage); + resolve({ error: 'authorizing_timeout', error_description: 'Authorizing timeout' }); + }); + }); +}; diff --git a/vscode-web-github1s/src/vs/github1s/authorizing-overlay.css b/vscode-web-github1s/src/vs/github1s/authorizing-overlay.css new file mode 100644 index 000000000..7e3f4c0ef --- /dev/null +++ b/vscode-web-github1s/src/vs/github1s/authorizing-overlay.css @@ -0,0 +1,269 @@ +.github1s-authorizing-mask { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 99999; + background-color: rgba(0, 0, 0, .6); + display: flex; + justify-content: center; + align-items: flex-start; +} + +.github1s-authorizing-visible { + overflow: hidden; +} + +.github1s-overlay-visible .monaco-workbench { + filter: blur(2px); +} + +@keyframes authorizingDialogBouncedIn { + from { opacity: 0; transform: scale(0); } + to { opacity: 1; transform: scale(1); } +} + +@keyframes authorizingDialogBouncedOut { + from { opacity: 1; transform: scale(1); } + to { opacity: 0; transform: scale(0); } +} + +.github1s-authorizing-dialog { + color: #ccc; + font-family: Inter, sans-serif; + width: 80%; + max-width: 520px; + background-color: #101010; + border-radius: 8px; + border: 1px solid #666; + text-align: center; + padding: 40px 20px 52px; + position: absolute; + margin-top: 20vh; + animation-name: authorizingDialogBouncedIn; + animation-duration: 0.2s; + animation-iteration-count: 1; +} + +.github1s-authorizing-dialog.exiting { + animation-name: authorizingDialogBouncedOut; +} + +.github1s-authorizing-dialog a.link { + color: #1890ff; +} + +.github1s-authorizing-dialog a.link:hover { + color: #40a9ff; +} + +.github1s-authorizing-dialog a.link:active { + color: #096dd9; +} + +.github1s-authorizing-dialog [disabled] { + opacity: 0.7; + cursor: not-allowed !important; +} + +.github1s-authorizing-dialog .error-message { + text-align: left; + margin-top: 6px; + font-size: 13px; + color: #ff4d4f; +} + +.github1s-authorizing-dialog .error-message .link { + margin-left: 4px; +} + +.github1s-authorizing-dialog .close-button { + position: absolute; + top: 0; + right: 0; + width: 36px; + height: 36px; + display: inline-flex; + align-items: center; + justify-content: center; + color: #fff; + font-size: 20px; + cursor: pointer; + transition: color 0.1s; +} + +.github1s-authorizing-dialog .close-button::after { + content: '×'; +} + +.github1s-authorizing-dialog .close-button:hover { + color: #ff4d4f; +} + +.github1s-authorizing-dialog .close-button:active { + color: #d9363e; +} + +.github1s-authorizing-dialog .header-title { + color: #fff; + font-size: 23px; + margin-bottom: 20px; +} + +.github1s-authorizing-dialog .feature-list { + list-style: none; + font-size: 14px; + display: flex; + justify-content: center; + align-items: center; + padding: 0; + margin: 0 0 20px; +} + +.github1s-authorizing-dialog .features-description .feature-item { + margin-right: 12px; + white-space: nowrap; + line-height: 20px; +} + +.github1s-authorizing-dialog li.feature-item a { + color: #ccc; + text-decoration: none; +} + + +.github1s-authorizing-dialog .features-description .feature-item::before { + content: '✓'; + color: #32d74b; + margin-right: 4px; +} + +.github1s-authorizing-dialog .features-description .feature-item:last-child { + margin-right: 0; +} + +.github1s-authorizing-dialog .github-documentation { + font-size: 13px; + margin-bottom: 32px; +} + +.github1s-authorizing-dialog .authorizing-methods { + display: inline-block; + width: 85%; + max-width: 320px; +} + +.github1s-authorizing-dialog .authorizing-button { + height: 40px; + width: 100%; + outline: none; + margin: 0 auto; + display: flex; + align-items: center; + justify-content: center; + background-color: #fff; + box-shadow: rgba(255, 255, 255, 0.2) 0px 2px 4px; + border: 1px solid #ccc; + border-radius: 4px; + font-size: 16px; + transition: all 0.1s; + cursor: pointer; +} + +.github1s-authorizing-dialog .authorizing-button:not([disabled]):hover { + transform: scale(1.03); + box-shadow: rgba(255, 255, 255, 0.25) 0px 2px 8px; +} + +.github1s-authorizing-dialog .authorizing-button:not([disabled]):active { + transform: scale(0.98); +} + +.github1s-authorizing-dialog .authorizing-button .github-logo { + height: 24px; + margin-right: 8px; +} + +.github1s-authorizing-dialog .authorizing-methods .split-line { + height: 0; + border: 1px solid #ccc; + margin: 28px 0; + position: relative; +} + +.github1s-authorizing-dialog .authorizing-methods .split-line::after { + content: 'OR'; + display: block; + position: absolute; + top: 50%; + left: 50%; + transform: translateY(-50%) translateX(-50%); + border: 2px solid #ccc; + padding: 4px 16px; + border-radius: 8px; + background-color: #000; + font-size: 12px; + font-weight: bold; +} + +.github1s-authorizing-dialog .authorizing-methods .input-oauth-token-form { + display: flex; + height: 32px; +} + +.github1s-authorizing-dialog .authorizing-methods .create-token-link { + font-size: 12px; + text-align: left; + margin-top: 8px; +} + +.github1s-authorizing-dialog .authorizing-methods .input-oauth-token-form .input-box { + flex: 1; + outline: none; + color: #ccc; + border: 1px solid #3c3c3c; + background-color: #3c3c3c; + border-radius: 4px; + padding: 0 8px; + margin-right: 8px; + font-size: 14px; +} + +.github1s-authorizing-dialog .authorizing-methods .input-oauth-token-form .input-box:focus { + border: 1px solid #096dd9; +} + +.github1s-authorizing-dialog .authorizing-methods .input-oauth-token-form .submit-button { + border-style: none; + outline: none; + width: 60px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + color: #fff; + background-color: #09639c; + cursor: pointer; +} + +.github1s-authorizing-dialog .authorizing-methods .input-oauth-token-form .submit-button:not([disabled]):hover { + background-color: #1177bb; +} + +.github1s-authorizing-dialog .authorizing-methods .input-oauth-token-form .submit-button:not([disabled]):active { + color: #ccc; + background-color: #0867ab; +} + +@media (max-width: 550px) { + .github1s-authorizing-dialog .feature-list { + flex-direction: column; + } + + .github1s-authorizing-dialog .features-description .feature-item { + width: 180px; + text-align: left; + margin-right: 0; + } +} diff --git a/vscode-web-github1s/src/vs/github1s/authorizing-overlay.ts b/vscode-web-github1s/src/vs/github1s/authorizing-overlay.ts new file mode 100644 index 000000000..69a2e8833 --- /dev/null +++ b/vscode-web-github1s/src/vs/github1s/authorizing-overlay.ts @@ -0,0 +1,246 @@ +/** + * @file authorizing overlay + * @author netcon + */ + +import { commands } from 'vs/workbench/workbench.web.api'; +import { getGitHubAccessToken, AuthMessageData } from 'vs/github1s/authorizing-github'; +import 'vs/css!./authorizing-overlay'; + +const AUTHORIZING_DIALOG_HTML = ` +
+
+
+ Authenticating to GitHub +
+ +
+ Read more about this on + + GitHub Documentation + +
+
+ +
+
+ + +
+ +
+
+`; + +/** + * `AuthorizingOverlay.getInstance().show()` will open a dialog + * and mask the entire page, It returns a Promise of `AuthMessageData`. + */ +export class AuthorizingOverlay { + private static _instance: AuthorizingOverlay; + private _overlayVisible: boolean = false; + private _rootElement?: HTMLDivElement; + private _finishCallback: (result: AuthMessageData) => void = () => {}; + + // only one overlay instance should be exists + public static getInstance() { + if (AuthorizingOverlay._instance) { + return AuthorizingOverlay._instance; + } + return AuthorizingOverlay._instance = new AuthorizingOverlay(); + } + + // show the authorizing overlay + public show() { + if (this._overlayVisible === true) { + return; + } + this._overlayVisible = true; + // add `github1s-overlay-visible` to document.body for `filter: blur(2px)` + document.body.classList.add('github1s-overlay-visible'); + document.body.appendChild(this.getRootElement()); + this.clearAllErrors(); + return new Promise(resolve => (this._finishCallback = resolve)); + } + + // hide the authorizing overlay + public hide() { + if (this._overlayVisible === false) { + return; + } + this._overlayVisible = false; + const dialogElement = this.getDialogElement(); + // run the exiting animation and waiting 0.18s for finishing it + dialogElement.classList.add('exiting'); + setTimeout(() => { + dialogElement.classList.remove('exiting'); + document.body.classList.remove('github1s-overlay-visible'); + document.body.removeChild(this.getRootElement()); + }, 180); + } + + // get the root html element of the overlay, there would not + // create dom repeatedly, just cache it in the first time + private getRootElement(): HTMLDivElement { + if (!this._rootElement) { + this._rootElement = this.createElements(); + this.registerListeners(); + } + return this._rootElement; + } + + // get dialog html element + private getDialogElement(): HTMLDivElement { + return this.getRootElement().querySelector('.github1s-authorizing-dialog')!; + } + + // create necessary doms + private createElements(): HTMLDivElement { + const element = document.createElement('div'); + + element.classList.add('github1s-authorizing-mask'); + element.innerHTML = AUTHORIZING_DIALOG_HTML; + return element; + } + + // create a html element to show error messages + private createErrorElement(description: string, link?: string) { + const element = document.createElement('div'); + + element.classList.add('error-message'); + element.innerText = description; + if (link) { + const linkElement = document.createElement('a'); + linkElement.setAttribute('href', link); + linkElement.setAttribute('target', '_blank'); + linkElement.classList.add('link'); + linkElement.innerText = 'See more'; + element.appendChild(linkElement); + } + return element; + }; + + // clear all exists error messages + private clearAllErrors() { + this.getDialogElement().querySelectorAll('.error-message')?.forEach(element => { + element.parentElement?.removeChild(element); + }) + } + + // set the error message for the Authorizing Button + private setAuthorizingButtonError(description: string, link?: string) { + this.clearAllErrors(); + const errorElement = this.createErrorElement(description, link); + this.getDialogElement().querySelector('.authorizing-button')?.insertAdjacentElement('afterend', errorElement); + } + + // handle the event when user click the Authorizing Button + private async handleClickAuthorizingButton() { + // open the authorizing window + const data = await getGitHubAccessToken(false); + + if ('access_token' in data) { + // we got the token here! + this._finishCallback(data); + return true; + } + if ('error' in data) { + this.setAuthorizingButtonError(data.error_description, data.error_uri); + } + return false; + } + + // set the error message for the input box where user can input token manually + private setSubmitTokenError(description: string, link?: string) { + this.clearAllErrors(); + const errorElement = this.createErrorElement(description, link); + this.getDialogElement().querySelector('.create-token-link')?.insertAdjacentElement('afterend', errorElement); + } + + // handle the event when user click the submit button for token input box + private async handleClickSubmitTokenButton() { + const token = (this.getDialogElement().querySelector('.input-oauth-token-form .input-box') as HTMLInputElement)?.value; + + if (!token) { + this.setSubmitTokenError('Please input the token'); + return false; + } + + type ValidateResult = { valid: boolean; remaining: number; }; + const tokenStatus = (await commands.executeCommand('github1s.validate-token', token, true)) as ValidateResult; + + if (!tokenStatus.valid) { + this.setSubmitTokenError('The token is invalid'); + return false; + } + + if (tokenStatus.remaining <= 0) { + this.setSubmitTokenError('The token is valid, but it has exceeded the rate limit'); + return false; + } + + // we got the token here! + this._finishCallback({ access_token: token }); + return true; + } + + // register the event listeners + private registerListeners() { + const dialogElement = this.getDialogElement(); + + // close the dialog + dialogElement.querySelector('.close-button')?.addEventListener('click', () => { + this._finishCallback({ error: 'user_canceled', error_description: 'Authorizing canceled' }); + this.hide(); + }); + + // click the authorizing button + const authorizingButtonElement = dialogElement.querySelector('.authorizing-button'); + authorizingButtonElement?.addEventListener('click', async () => { + this.clearAllErrors(); + authorizingButtonElement.setAttribute('disabled', 'disabled'); + // if authorizing successful, then hide the dialog + (await this.handleClickAuthorizingButton()) && this.hide(); + authorizingButtonElement.removeAttribute('disabled'); + }); + + // submit the manually entered token + const submitTokenButtonElement = dialogElement.querySelector('.input-oauth-token-form .submit-button'); + submitTokenButtonElement?.addEventListener('click', async (event: Event) => { + event.preventDefault(); + this.clearAllErrors(); + submitTokenButtonElement.setAttribute('disabled', 'disabled'); + // if authorizing successful, then hide the dialog + (await this.handleClickSubmitTokenButton()) && this.hide(); + submitTokenButtonElement.removeAttribute('disabled'); + }); + } +} + +export const getGitHubAccessTokenWithOverlay = () => AuthorizingOverlay.getInstance().show(); +export const hideAuthorizingOverlay = () => AuthorizingOverlay.getInstance().hide(); diff --git a/vscode-web-github1s/src/vs/github1s/util.ts b/vscode-web-github1s/src/vs/github1s/util.ts index d39f0bfb9..216fa27d3 100644 --- a/vscode-web-github1s/src/vs/github1s/util.ts +++ b/vscode-web-github1s/src/vs/github1s/util.ts @@ -12,3 +12,19 @@ export const replaceBrowserUrl = (url: string) => { window.history.replaceState(null, '', url); } }; + +export const delegate = ( + element: HTMLElement, + selector: string, + eventName: K, + handler: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any +): void => { + return element?.addEventListener(eventName, function (event) { + const children = element.querySelectorAll(selector); + for (let i = 0, len = children.length; i < len; i++) { + if (children[i] === event.target) { + handler.call(this, event); + } + } + }); +}; diff --git a/vscode-web-github1s/src/vs/loader.js b/vscode-web-github1s/src/vs/loader.js new file mode 100644 index 000000000..10ee09e8d --- /dev/null +++ b/vscode-web-github1s/src/vs/loader.js @@ -0,0 +1,1904 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +/*--------------------------------------------------------------------------------------------- + *--------------------------------------------------------------------------------------------- + *--------------------------------------------------------------------------------------------- + *--------------------------------------------------------------------------------------------- + *--------------------------------------------------------------------------------------------- + * Please make sure to make edits in the .ts file at https://github.com/microsoft/vscode-loader/ + *--------------------------------------------------------------------------------------------- + *--------------------------------------------------------------------------------------------- + *--------------------------------------------------------------------------------------------- + *--------------------------------------------------------------------------------------------- + *--------------------------------------------------------------------------------------------*/ +var _amdLoaderGlobal = this; +var _commonjsGlobal = typeof global === 'object' ? global : {}; +var AMDLoader; +(function (AMDLoader) { + AMDLoader.global = _amdLoaderGlobal; + var Environment = /** @class */ (function () { + function Environment() { + this._detected = false; + this._isWindows = false; + this._isNode = false; + this._isElectronRenderer = false; + this._isWebWorker = false; + } + Object.defineProperty(Environment.prototype, "isWindows", { + get: function () { + this._detect(); + return this._isWindows; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Environment.prototype, "isNode", { + get: function () { + this._detect(); + return this._isNode; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Environment.prototype, "isElectronRenderer", { + get: function () { + this._detect(); + return this._isElectronRenderer; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Environment.prototype, "isWebWorker", { + get: function () { + this._detect(); + return this._isWebWorker; + }, + enumerable: false, + configurable: true + }); + Environment.prototype._detect = function () { + if (this._detected) { + return; + } + this._detected = true; + this._isWindows = Environment._isWindows(); + this._isNode = (typeof module !== 'undefined' && !!module.exports); + this._isElectronRenderer = (typeof process !== 'undefined' && typeof process.versions !== 'undefined' && typeof process.versions.electron !== 'undefined' && process.type === 'renderer'); + this._isWebWorker = (typeof AMDLoader.global.importScripts === 'function'); + }; + Environment._isWindows = function () { + if (typeof navigator !== 'undefined') { + if (navigator.userAgent && navigator.userAgent.indexOf('Windows') >= 0) { + return true; + } + } + if (typeof process !== 'undefined') { + return (process.platform === 'win32'); + } + return false; + }; + return Environment; + }()); + AMDLoader.Environment = Environment; +})(AMDLoader || (AMDLoader = {})); +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +var AMDLoader; +(function (AMDLoader) { + var LoaderEvent = /** @class */ (function () { + function LoaderEvent(type, detail, timestamp) { + this.type = type; + this.detail = detail; + this.timestamp = timestamp; + } + return LoaderEvent; + }()); + AMDLoader.LoaderEvent = LoaderEvent; + var LoaderEventRecorder = /** @class */ (function () { + function LoaderEventRecorder(loaderAvailableTimestamp) { + this._events = [new LoaderEvent(1 /* LoaderAvailable */, '', loaderAvailableTimestamp)]; + } + LoaderEventRecorder.prototype.record = function (type, detail) { + this._events.push(new LoaderEvent(type, detail, AMDLoader.Utilities.getHighPerformanceTimestamp())); + }; + LoaderEventRecorder.prototype.getEvents = function () { + return this._events; + }; + return LoaderEventRecorder; + }()); + AMDLoader.LoaderEventRecorder = LoaderEventRecorder; + var NullLoaderEventRecorder = /** @class */ (function () { + function NullLoaderEventRecorder() { + } + NullLoaderEventRecorder.prototype.record = function (type, detail) { + // Nothing to do + }; + NullLoaderEventRecorder.prototype.getEvents = function () { + return []; + }; + NullLoaderEventRecorder.INSTANCE = new NullLoaderEventRecorder(); + return NullLoaderEventRecorder; + }()); + AMDLoader.NullLoaderEventRecorder = NullLoaderEventRecorder; +})(AMDLoader || (AMDLoader = {})); +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +var AMDLoader; +(function (AMDLoader) { + var Utilities = /** @class */ (function () { + function Utilities() { + } + /** + * This method does not take care of / vs \ + */ + Utilities.fileUriToFilePath = function (isWindows, uri) { + uri = decodeURI(uri).replace(/%23/g, '#'); + if (isWindows) { + if (/^file:\/\/\//.test(uri)) { + // This is a URI without a hostname => return only the path segment + return uri.substr(8); + } + if (/^file:\/\//.test(uri)) { + return uri.substr(5); + } + } + else { + if (/^file:\/\//.test(uri)) { + return uri.substr(7); + } + } + // Not sure... + return uri; + }; + Utilities.startsWith = function (haystack, needle) { + return haystack.length >= needle.length && haystack.substr(0, needle.length) === needle; + }; + Utilities.endsWith = function (haystack, needle) { + return haystack.length >= needle.length && haystack.substr(haystack.length - needle.length) === needle; + }; + // only check for "?" before "#" to ensure that there is a real Query-String + Utilities.containsQueryString = function (url) { + return /^[^\#]*\?/gi.test(url); + }; + /** + * Does `url` start with http:// or https:// or file:// or / ? + */ + Utilities.isAbsolutePath = function (url) { + return /^((http:\/\/)|(https:\/\/)|(file:\/\/)|(\/))/.test(url); + }; + Utilities.forEachProperty = function (obj, callback) { + if (obj) { + var key = void 0; + for (key in obj) { + if (obj.hasOwnProperty(key)) { + callback(key, obj[key]); + } + } + } + }; + Utilities.isEmpty = function (obj) { + var isEmpty = true; + Utilities.forEachProperty(obj, function () { + isEmpty = false; + }); + return isEmpty; + }; + Utilities.recursiveClone = function (obj) { + if (!obj || typeof obj !== 'object' || obj instanceof RegExp) { + return obj; + } + if (!Array.isArray(obj) && Object.getPrototypeOf(obj) !== Object.prototype) { + // only clone "simple" objects + return obj; + } + var result = Array.isArray(obj) ? [] : {}; + Utilities.forEachProperty(obj, function (key, value) { + if (value && typeof value === 'object') { + result[key] = Utilities.recursiveClone(value); + } + else { + result[key] = value; + } + }); + return result; + }; + Utilities.generateAnonymousModule = function () { + return '===anonymous' + (Utilities.NEXT_ANONYMOUS_ID++) + '==='; + }; + Utilities.isAnonymousModule = function (id) { + return Utilities.startsWith(id, '===anonymous'); + }; + Utilities.getHighPerformanceTimestamp = function () { + if (!this.PERFORMANCE_NOW_PROBED) { + this.PERFORMANCE_NOW_PROBED = true; + this.HAS_PERFORMANCE_NOW = (AMDLoader.global.performance && typeof AMDLoader.global.performance.now === 'function'); + } + return (this.HAS_PERFORMANCE_NOW ? AMDLoader.global.performance.now() : Date.now()); + }; + Utilities.NEXT_ANONYMOUS_ID = 1; + Utilities.PERFORMANCE_NOW_PROBED = false; + Utilities.HAS_PERFORMANCE_NOW = false; + return Utilities; + }()); + AMDLoader.Utilities = Utilities; +})(AMDLoader || (AMDLoader = {})); +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +var AMDLoader; +(function (AMDLoader) { + function ensureError(err) { + if (err instanceof Error) { + return err; + } + var result = new Error(err.message || String(err) || 'Unknown Error'); + if (err.stack) { + result.stack = err.stack; + } + return result; + } + AMDLoader.ensureError = ensureError; + ; + var ConfigurationOptionsUtil = /** @class */ (function () { + function ConfigurationOptionsUtil() { + } + /** + * Ensure configuration options make sense + */ + ConfigurationOptionsUtil.validateConfigurationOptions = function (options) { + function defaultOnError(err) { + if (err.phase === 'loading') { + console.error('Loading "' + err.moduleId + '" failed'); + console.error(err); + console.error('Here are the modules that depend on it:'); + console.error(err.neededBy); + return; + } + if (err.phase === 'factory') { + console.error('The factory method of "' + err.moduleId + '" has thrown an exception'); + console.error(err); + return; + } + } + options = options || {}; + if (typeof options.baseUrl !== 'string') { + options.baseUrl = ''; + } + if (typeof options.isBuild !== 'boolean') { + options.isBuild = false; + } + if (typeof options.paths !== 'object') { + options.paths = {}; + } + if (typeof options.config !== 'object') { + options.config = {}; + } + if (typeof options.catchError === 'undefined') { + options.catchError = false; + } + if (typeof options.recordStats === 'undefined') { + options.recordStats = false; + } + if (typeof options.urlArgs !== 'string') { + options.urlArgs = ''; + } + if (typeof options.onError !== 'function') { + options.onError = defaultOnError; + } + if (!Array.isArray(options.ignoreDuplicateModules)) { + options.ignoreDuplicateModules = []; + } + if (options.baseUrl.length > 0) { + if (!AMDLoader.Utilities.endsWith(options.baseUrl, '/')) { + options.baseUrl += '/'; + } + } + if (typeof options.cspNonce !== 'string') { + options.cspNonce = ''; + } + if (typeof options.preferScriptTags === 'undefined') { + options.preferScriptTags = false; + } + if (!Array.isArray(options.nodeModules)) { + options.nodeModules = []; + } + if (options.nodeCachedData && typeof options.nodeCachedData === 'object') { + if (typeof options.nodeCachedData.seed !== 'string') { + options.nodeCachedData.seed = 'seed'; + } + if (typeof options.nodeCachedData.writeDelay !== 'number' || options.nodeCachedData.writeDelay < 0) { + options.nodeCachedData.writeDelay = 1000 * 7; + } + if (!options.nodeCachedData.path || typeof options.nodeCachedData.path !== 'string') { + var err = ensureError(new Error('INVALID cached data configuration, \'path\' MUST be set')); + err.phase = 'configuration'; + options.onError(err); + options.nodeCachedData = undefined; + } + } + return options; + }; + ConfigurationOptionsUtil.mergeConfigurationOptions = function (overwrite, base) { + if (overwrite === void 0) { overwrite = null; } + if (base === void 0) { base = null; } + var result = AMDLoader.Utilities.recursiveClone(base || {}); + // Merge known properties and overwrite the unknown ones + AMDLoader.Utilities.forEachProperty(overwrite, function (key, value) { + if (key === 'ignoreDuplicateModules' && typeof result.ignoreDuplicateModules !== 'undefined') { + result.ignoreDuplicateModules = result.ignoreDuplicateModules.concat(value); + } + else if (key === 'paths' && typeof result.paths !== 'undefined') { + AMDLoader.Utilities.forEachProperty(value, function (key2, value2) { return result.paths[key2] = value2; }); + } + else if (key === 'config' && typeof result.config !== 'undefined') { + AMDLoader.Utilities.forEachProperty(value, function (key2, value2) { return result.config[key2] = value2; }); + } + else { + result[key] = AMDLoader.Utilities.recursiveClone(value); + } + }); + return ConfigurationOptionsUtil.validateConfigurationOptions(result); + }; + return ConfigurationOptionsUtil; + }()); + AMDLoader.ConfigurationOptionsUtil = ConfigurationOptionsUtil; + var Configuration = /** @class */ (function () { + function Configuration(env, options) { + this._env = env; + this.options = ConfigurationOptionsUtil.mergeConfigurationOptions(options); + this._createIgnoreDuplicateModulesMap(); + this._createNodeModulesMap(); + this._createSortedPathsRules(); + if (this.options.baseUrl === '') { + if (this.options.nodeRequire && this.options.nodeRequire.main && this.options.nodeRequire.main.filename && this._env.isNode) { + var nodeMain = this.options.nodeRequire.main.filename; + var dirnameIndex = Math.max(nodeMain.lastIndexOf('/'), nodeMain.lastIndexOf('\\')); + this.options.baseUrl = nodeMain.substring(0, dirnameIndex + 1); + } + if (this.options.nodeMain && this._env.isNode) { + var nodeMain = this.options.nodeMain; + var dirnameIndex = Math.max(nodeMain.lastIndexOf('/'), nodeMain.lastIndexOf('\\')); + this.options.baseUrl = nodeMain.substring(0, dirnameIndex + 1); + } + } + } + Configuration.prototype._createIgnoreDuplicateModulesMap = function () { + // Build a map out of the ignoreDuplicateModules array + this.ignoreDuplicateModulesMap = {}; + for (var i = 0; i < this.options.ignoreDuplicateModules.length; i++) { + this.ignoreDuplicateModulesMap[this.options.ignoreDuplicateModules[i]] = true; + } + }; + Configuration.prototype._createNodeModulesMap = function () { + // Build a map out of nodeModules array + this.nodeModulesMap = Object.create(null); + for (var _i = 0, _a = this.options.nodeModules; _i < _a.length; _i++) { + var nodeModule = _a[_i]; + this.nodeModulesMap[nodeModule] = true; + } + }; + Configuration.prototype._createSortedPathsRules = function () { + var _this = this; + // Create an array our of the paths rules, sorted descending by length to + // result in a more specific -> less specific order + this.sortedPathsRules = []; + AMDLoader.Utilities.forEachProperty(this.options.paths, function (from, to) { + if (!Array.isArray(to)) { + _this.sortedPathsRules.push({ + from: from, + to: [to] + }); + } + else { + _this.sortedPathsRules.push({ + from: from, + to: to + }); + } + }); + this.sortedPathsRules.sort(function (a, b) { + return b.from.length - a.from.length; + }); + }; + /** + * Clone current configuration and overwrite options selectively. + * @param options The selective options to overwrite with. + * @result A new configuration + */ + Configuration.prototype.cloneAndMerge = function (options) { + return new Configuration(this._env, ConfigurationOptionsUtil.mergeConfigurationOptions(options, this.options)); + }; + /** + * Get current options bag. Useful for passing it forward to plugins. + */ + Configuration.prototype.getOptionsLiteral = function () { + return this.options; + }; + Configuration.prototype._applyPaths = function (moduleId) { + var pathRule; + for (var i = 0, len = this.sortedPathsRules.length; i < len; i++) { + pathRule = this.sortedPathsRules[i]; + if (AMDLoader.Utilities.startsWith(moduleId, pathRule.from)) { + var result = []; + for (var j = 0, lenJ = pathRule.to.length; j < lenJ; j++) { + result.push(pathRule.to[j] + moduleId.substr(pathRule.from.length)); + } + return result; + } + } + return [moduleId]; + }; + Configuration.prototype._addUrlArgsToUrl = function (url) { + if (AMDLoader.Utilities.containsQueryString(url)) { + return url + '&' + this.options.urlArgs; + } + else { + return url + '?' + this.options.urlArgs; + } + }; + Configuration.prototype._addUrlArgsIfNecessaryToUrl = function (url) { + if (this.options.urlArgs) { + return this._addUrlArgsToUrl(url); + } + return url; + }; + Configuration.prototype._addUrlArgsIfNecessaryToUrls = function (urls) { + if (this.options.urlArgs) { + for (var i = 0, len = urls.length; i < len; i++) { + urls[i] = this._addUrlArgsToUrl(urls[i]); + } + } + return urls; + }; + /** + * Transform a module id to a location. Appends .js to module ids + */ + Configuration.prototype.moduleIdToPaths = function (moduleId) { + var isNodeModule = ((this.nodeModulesMap[moduleId] === true) + || (this.options.amdModulesPattern instanceof RegExp && !this.options.amdModulesPattern.test(moduleId))); + if (isNodeModule) { + // This is a node module... + if (this.isBuild()) { + // ...and we are at build time, drop it + return ['empty:']; + } + else { + // ...and at runtime we create a `shortcut`-path + return ['node|' + moduleId]; + } + } + var result = moduleId; + var results; + if (!AMDLoader.Utilities.endsWith(result, '.js') && !AMDLoader.Utilities.isAbsolutePath(result)) { + results = this._applyPaths(result); + for (var i = 0, len = results.length; i < len; i++) { + if (this.isBuild() && results[i] === 'empty:') { + continue; + } + if (!AMDLoader.Utilities.isAbsolutePath(results[i])) { + results[i] = this.options.baseUrl + results[i]; + } + if (!AMDLoader.Utilities.endsWith(results[i], '.js') && !AMDLoader.Utilities.containsQueryString(results[i])) { + results[i] = results[i] + '.js'; + } + } + } + else { + if (!AMDLoader.Utilities.endsWith(result, '.js') && !AMDLoader.Utilities.containsQueryString(result)) { + result = result + '.js'; + } + results = [result]; + } + return this._addUrlArgsIfNecessaryToUrls(results); + }; + /** + * Transform a module id or url to a location. + */ + Configuration.prototype.requireToUrl = function (url) { + var result = url; + if (!AMDLoader.Utilities.isAbsolutePath(result)) { + result = this._applyPaths(result)[0]; + if (!AMDLoader.Utilities.isAbsolutePath(result)) { + result = this.options.baseUrl + result; + } + } + return this._addUrlArgsIfNecessaryToUrl(result); + }; + /** + * Flag to indicate if current execution is as part of a build. + */ + Configuration.prototype.isBuild = function () { + return this.options.isBuild; + }; + /** + * Test if module `moduleId` is expected to be defined multiple times + */ + Configuration.prototype.isDuplicateMessageIgnoredFor = function (moduleId) { + return this.ignoreDuplicateModulesMap.hasOwnProperty(moduleId); + }; + /** + * Get the configuration settings for the provided module id + */ + Configuration.prototype.getConfigForModule = function (moduleId) { + if (this.options.config) { + return this.options.config[moduleId]; + } + }; + /** + * Should errors be caught when executing module factories? + */ + Configuration.prototype.shouldCatchError = function () { + return this.options.catchError; + }; + /** + * Should statistics be recorded? + */ + Configuration.prototype.shouldRecordStats = function () { + return this.options.recordStats; + }; + /** + * Forward an error to the error handler. + */ + Configuration.prototype.onError = function (err) { + this.options.onError(err); + }; + return Configuration; + }()); + AMDLoader.Configuration = Configuration; +})(AMDLoader || (AMDLoader = {})); +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +var AMDLoader; +(function (AMDLoader) { + /** + * Load `scriptSrc` only once (avoid multiple