From 982791ea05a15208fa4abffc75e3c9fc5121684e Mon Sep 17 00:00:00 2001 From: ff6347 Date: Mon, 20 Mar 2023 14:56:03 +0100 Subject: [PATCH 001/121] feat(auth): Working auth using supabase With: signup, login, password reset, change password, change email, change username --- package-lock.json | 584 ++++++++++++++++++++++++++++++++++++++-- package.json | 3 + pages/_app.tsx | 4 +- pages/auth.tsx | 523 +++++++++++++++++++++++++++++++++++ src/Providers/index.tsx | 70 +++-- src/common/database.ts | 442 ++++++++++++++++++++++++++++++ 6 files changed, 1584 insertions(+), 42 deletions(-) create mode 100644 pages/auth.tsx create mode 100644 src/common/database.ts diff --git a/package-lock.json b/package-lock.json index c157a8dd1..e4726eeed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,9 @@ "@material-ui/icons": "4.11.3", "@material-ui/styles": "4.11.5", "@popperjs/core": "2.11.5", + "@supabase/auth-helpers-nextjs": "0.5.6", + "@supabase/auth-helpers-react": "0.3.1", + "@supabase/auth-ui-react": "0.3.5", "core-js": "3.21.1", "d3": "6.7.0", "date-fns": "2.28.0", @@ -7022,6 +7025,11 @@ "@sinonjs/commons": "^1.7.0" } }, + "node_modules/@stitches/core": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@stitches/core/-/core-1.2.8.tgz", + "integrity": "sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==" + }, "node_modules/@storybook/addon-actions": { "version": "6.5.13", "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.5.13.tgz", @@ -13026,6 +13034,147 @@ "node": ">=8" } }, + "node_modules/@supabase/auth-helpers-nextjs": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-nextjs/-/auth-helpers-nextjs-0.5.6.tgz", + "integrity": "sha512-NiUGGnO9vRycMDnl/DzFbHCJhNV9DdH1vH61T1Fzgp2aRW8LfLRVlBQeGCg2AzyvAe0MiLbbRsGNcJmAmJPs9A==", + "dependencies": { + "@supabase/auth-helpers-shared": "0.3.0" + }, + "peerDependencies": { + "@supabase/supabase-js": "^2.0.4" + } + }, + "node_modules/@supabase/auth-helpers-react": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-react/-/auth-helpers-react-0.3.1.tgz", + "integrity": "sha512-g3SFv08Dz9FapNif/ZY1b7qKGlMJDyTLSayHBz3kb3FuYxg7aLWgQtydDhm5AGbc0XtvpIBuhGTIOVevwpdosA==", + "peerDependencies": { + "@supabase/supabase-js": "^2.0.4" + } + }, + "node_modules/@supabase/auth-helpers-shared": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-shared/-/auth-helpers-shared-0.3.0.tgz", + "integrity": "sha512-6lKhVMBZnGdFelpZyvQDBQSOY+pglS6a5U4ieAqfKQ/rx3rSjRASR3UGh7V+el1re3xYe3ddVBWXw16rdVAzrQ==", + "peerDependencies": { + "@supabase/supabase-js": "^2.0.4" + } + }, + "node_modules/@supabase/auth-ui-react": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@supabase/auth-ui-react/-/auth-ui-react-0.3.5.tgz", + "integrity": "sha512-3X9rlLwQliRZ386qFjsCdHvMYQIzAByXT4uWcHssFIX4s0ymwkIH49UJDb6vtpswDV4z9ZOxF0igPZyEyZT32w==", + "dependencies": { + "@stitches/core": "^1.2.8", + "@supabase/auth-ui-shared": "0.1.3", + "prop-types": "^15.7.2", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "peerDependencies": { + "@supabase/supabase-js": "^2.8.0" + } + }, + "node_modules/@supabase/auth-ui-react/node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@supabase/auth-ui-react/node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/@supabase/auth-ui-react/node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/@supabase/auth-ui-shared": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@supabase/auth-ui-shared/-/auth-ui-shared-0.1.3.tgz", + "integrity": "sha512-GoyW+3EiDcy+sQdmdkezx9fpsRSogHJwTdzxgf79PjyLbpyAKCnTVhOB02tiMHZ0uIZ+afAQ8CmNEwwxwHVnJw==", + "peerDependencies": { + "@supabase/supabase-js": "^2.8.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.0.tgz", + "integrity": "sha512-vRziB+AqRXRaGHjEFHwBo0kuNDTuAxI7VUeqU24Fe86ISoD8YEQm0dGdpleJEcqgDGWaO6pxT1tfj1BRY5PwMg==", + "peer": true, + "dependencies": { + "cross-fetch": "^3.1.5" + } + }, + "node_modules/@supabase/gotrue-js": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.14.0.tgz", + "integrity": "sha512-FI6q4n4iZ2zrEt1BnBYYe8HQ1k9t5CpBcDQxVXa8PeMwygXpzR0AcdfAsZ5Yba42C8YsBA132ti01f+RINS3UQ==", + "peer": true, + "dependencies": { + "cross-fetch": "^3.1.5" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.4.1.tgz", + "integrity": "sha512-aruqwV/aTggkM7OVv2JinCeXmRMKHJCZpkuS1nuoa0NgLw7g3NyILSyWOKYTBJ/PxE/zXtWsBhdxFzaaNz5uxg==", + "peer": true, + "dependencies": { + "cross-fetch": "^3.1.5" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.7.0.tgz", + "integrity": "sha512-wg35ofiCpIemycmPZvvZk3jM9c9z8VvnPUBbSP9ZZN2vSOEJ9C7DZuLiiZMXsyNUzjVgIn62A1tN99T5+9O8Aw==", + "peer": true, + "dependencies": { + "@types/phoenix": "^1.5.4", + "websocket": "^1.0.34" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.3.1.tgz", + "integrity": "sha512-BaPIvyvjuZW1V0CnfGKUZyzpBUXnsh0XD8eqTOYd+MdiGPmIPI0vtwnT4fAoK8mipp1vpcN62EVQaqeUnWXPtQ==", + "peer": true, + "dependencies": { + "cross-fetch": "^3.1.5" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.11.0.tgz", + "integrity": "sha512-FkaPZjVx1oY4boS02kAoNhmdyy5hrezxGlY8lZdQwKvk7ee5NPiXzjjsGruc6JOS1QNeuWIUAw1L7uKVYI30dA==", + "peer": true, + "dependencies": { + "@supabase/functions-js": "^2.1.0", + "@supabase/gotrue-js": "^2.12.0", + "@supabase/postgrest-js": "^1.1.1", + "@supabase/realtime-js": "^2.7.0", + "@supabase/storage-js": "^2.3.1", + "cross-fetch": "^3.1.5" + } + }, "node_modules/@swc/helpers": { "version": "0.4.11", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.11.tgz", @@ -14106,6 +14255,12 @@ "integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==", "dev": true }, + "node_modules/@types/phoenix": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.5.5.tgz", + "integrity": "sha512-1eWWT19k0L4ZiTvdXjAvJ9KvW0B8SdiVftQmFPJGTEx78Q4PCSIQDpz+EfkFVR1N4U9gREjlW4JXL8YCIlY0bw==", + "peer": true + }, "node_modules/@types/prettier": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.3.2.tgz", @@ -16682,6 +16837,19 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true }, + "node_modules/bufferutil": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", + "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", + "hasInstallScript": true, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", @@ -18532,6 +18700,15 @@ "yarn": ">=1" } }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "peer": true, + "dependencies": { + "node-fetch": "2.6.7" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -18764,6 +18941,16 @@ "integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A==", "dev": true }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "peer": true, + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, "node_modules/d3": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/d3/-/d3-6.7.0.tgz", @@ -20219,6 +20406,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es5-ext": { + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "hasInstallScript": true, + "peer": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/es5-shim": { "version": "4.6.7", "resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.6.7.tgz", @@ -20228,12 +20430,33 @@ "node": ">=0.4.0" } }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "peer": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, "node_modules/es6-shim": { "version": "0.35.6", "resolved": "https://registry.npmjs.org/es6-shim/-/es6-shim-0.35.6.tgz", "integrity": "sha512-EmTr31wppcaIAgblChZiuN/l9Y7DPyw8Xtbg7fIVngn6zMW+IEBJDJngeKC3x6wr0V/vcA2wqeFnaw1bFJbDdA==", "dev": true }, + "node_modules/es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "peer": true, + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -21605,6 +21828,21 @@ "jsep": "^0.3.0" } }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "peer": true, + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", + "peer": true + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -24487,8 +24725,7 @@ "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "node_modules/is-unicode-supported": { "version": "0.1.0", @@ -33298,6 +33535,12 @@ } } }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "peer": true + }, "node_modules/next/node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -33367,7 +33610,6 @@ "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -33386,25 +33628,33 @@ "node_modules/node-fetch/node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, "node_modules/node-fetch/node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" }, "node_modules/node-fetch/node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, + "node_modules/node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "peer": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -41994,6 +42244,12 @@ "integrity": "sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==", "dev": true }, + "node_modules/type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "peer": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -42051,7 +42307,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, "dependencies": { "is-typedarray": "^1.0.0" } @@ -42634,6 +42889,19 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", @@ -43438,6 +43706,23 @@ "node": ">=10.13.0" } }, + "node_modules/websocket": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", + "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", + "peer": true, + "dependencies": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/whatwg-encoding": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", @@ -43761,6 +44046,15 @@ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "dev": true }, + "node_modules/yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", + "peer": true, + "engines": { + "node": ">=0.10.32" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -48865,6 +49159,11 @@ "@sinonjs/commons": "^1.7.0" } }, + "@stitches/core": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@stitches/core/-/core-1.2.8.tgz", + "integrity": "sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==" + }, "@storybook/addon-actions": { "version": "6.5.13", "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.5.13.tgz", @@ -53461,6 +53760,131 @@ } } }, + "@supabase/auth-helpers-nextjs": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-nextjs/-/auth-helpers-nextjs-0.5.6.tgz", + "integrity": "sha512-NiUGGnO9vRycMDnl/DzFbHCJhNV9DdH1vH61T1Fzgp2aRW8LfLRVlBQeGCg2AzyvAe0MiLbbRsGNcJmAmJPs9A==", + "requires": { + "@supabase/auth-helpers-shared": "0.3.0" + } + }, + "@supabase/auth-helpers-react": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-react/-/auth-helpers-react-0.3.1.tgz", + "integrity": "sha512-g3SFv08Dz9FapNif/ZY1b7qKGlMJDyTLSayHBz3kb3FuYxg7aLWgQtydDhm5AGbc0XtvpIBuhGTIOVevwpdosA==", + "requires": {} + }, + "@supabase/auth-helpers-shared": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-shared/-/auth-helpers-shared-0.3.0.tgz", + "integrity": "sha512-6lKhVMBZnGdFelpZyvQDBQSOY+pglS6a5U4ieAqfKQ/rx3rSjRASR3UGh7V+el1re3xYe3ddVBWXw16rdVAzrQ==", + "requires": {} + }, + "@supabase/auth-ui-react": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@supabase/auth-ui-react/-/auth-ui-react-0.3.5.tgz", + "integrity": "sha512-3X9rlLwQliRZ386qFjsCdHvMYQIzAByXT4uWcHssFIX4s0ymwkIH49UJDb6vtpswDV4z9ZOxF0igPZyEyZT32w==", + "requires": { + "@stitches/core": "^1.2.8", + "@supabase/auth-ui-shared": "0.1.3", + "prop-types": "^15.7.2", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "dependencies": { + "react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + } + }, + "scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "requires": { + "loose-envify": "^1.1.0" + } + } + } + }, + "@supabase/auth-ui-shared": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@supabase/auth-ui-shared/-/auth-ui-shared-0.1.3.tgz", + "integrity": "sha512-GoyW+3EiDcy+sQdmdkezx9fpsRSogHJwTdzxgf79PjyLbpyAKCnTVhOB02tiMHZ0uIZ+afAQ8CmNEwwxwHVnJw==", + "requires": {} + }, + "@supabase/functions-js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.1.0.tgz", + "integrity": "sha512-vRziB+AqRXRaGHjEFHwBo0kuNDTuAxI7VUeqU24Fe86ISoD8YEQm0dGdpleJEcqgDGWaO6pxT1tfj1BRY5PwMg==", + "peer": true, + "requires": { + "cross-fetch": "^3.1.5" + } + }, + "@supabase/gotrue-js": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.14.0.tgz", + "integrity": "sha512-FI6q4n4iZ2zrEt1BnBYYe8HQ1k9t5CpBcDQxVXa8PeMwygXpzR0AcdfAsZ5Yba42C8YsBA132ti01f+RINS3UQ==", + "peer": true, + "requires": { + "cross-fetch": "^3.1.5" + } + }, + "@supabase/postgrest-js": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.4.1.tgz", + "integrity": "sha512-aruqwV/aTggkM7OVv2JinCeXmRMKHJCZpkuS1nuoa0NgLw7g3NyILSyWOKYTBJ/PxE/zXtWsBhdxFzaaNz5uxg==", + "peer": true, + "requires": { + "cross-fetch": "^3.1.5" + } + }, + "@supabase/realtime-js": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.7.0.tgz", + "integrity": "sha512-wg35ofiCpIemycmPZvvZk3jM9c9z8VvnPUBbSP9ZZN2vSOEJ9C7DZuLiiZMXsyNUzjVgIn62A1tN99T5+9O8Aw==", + "peer": true, + "requires": { + "@types/phoenix": "^1.5.4", + "websocket": "^1.0.34" + } + }, + "@supabase/storage-js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.3.1.tgz", + "integrity": "sha512-BaPIvyvjuZW1V0CnfGKUZyzpBUXnsh0XD8eqTOYd+MdiGPmIPI0vtwnT4fAoK8mipp1vpcN62EVQaqeUnWXPtQ==", + "peer": true, + "requires": { + "cross-fetch": "^3.1.5" + } + }, + "@supabase/supabase-js": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.11.0.tgz", + "integrity": "sha512-FkaPZjVx1oY4boS02kAoNhmdyy5hrezxGlY8lZdQwKvk7ee5NPiXzjjsGruc6JOS1QNeuWIUAw1L7uKVYI30dA==", + "peer": true, + "requires": { + "@supabase/functions-js": "^2.1.0", + "@supabase/gotrue-js": "^2.12.0", + "@supabase/postgrest-js": "^1.1.1", + "@supabase/realtime-js": "^2.7.0", + "@supabase/storage-js": "^2.3.1", + "cross-fetch": "^3.1.5" + } + }, "@swc/helpers": { "version": "0.4.11", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.11.tgz", @@ -54440,6 +54864,12 @@ "integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==", "dev": true }, + "@types/phoenix": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.5.5.tgz", + "integrity": "sha512-1eWWT19k0L4ZiTvdXjAvJ9KvW0B8SdiVftQmFPJGTEx78Q4PCSIQDpz+EfkFVR1N4U9gREjlW4JXL8YCIlY0bw==", + "peer": true + }, "@types/prettier": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.3.2.tgz", @@ -56508,6 +56938,15 @@ "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", "dev": true }, + "bufferutil": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", + "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", + "peer": true, + "requires": { + "node-gyp-build": "^4.3.0" + } + }, "builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", @@ -57940,6 +58379,15 @@ "cross-spawn": "^7.0.1" } }, + "cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "peer": true, + "requires": { + "node-fetch": "2.6.7" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -58120,6 +58568,16 @@ "integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A==", "dev": true }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "peer": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, "d3": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/d3/-/d3-6.7.0.tgz", @@ -59330,18 +59788,50 @@ "is-symbol": "^1.0.2" } }, + "es5-ext": { + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "peer": true, + "requires": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + } + }, "es5-shim": { "version": "4.6.7", "resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.6.7.tgz", "integrity": "sha512-jg21/dmlrNQI7JyyA2w7n+yifSxBng0ZralnSfVZjoCawgNTCnS+yBCyVM9DL5itm7SUnDGgv7hcq2XCZX4iRQ==", "dev": true }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "peer": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, "es6-shim": { "version": "0.35.6", "resolved": "https://registry.npmjs.org/es6-shim/-/es6-shim-0.35.6.tgz", "integrity": "sha512-EmTr31wppcaIAgblChZiuN/l9Y7DPyw8Xtbg7fIVngn6zMW+IEBJDJngeKC3x6wr0V/vcA2wqeFnaw1bFJbDdA==", "dev": true }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "peer": true, + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -60359,6 +60849,23 @@ "jsep": "^0.3.0" } }, + "ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "peer": true, + "requires": { + "type": "^2.7.2" + }, + "dependencies": { + "type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", + "peer": true + } + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -62531,8 +63038,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "is-unicode-supported": { "version": "0.1.0", @@ -69398,6 +69904,12 @@ } } }, + "next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "peer": true + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -69436,7 +69948,6 @@ "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, "requires": { "whatwg-url": "^5.0.0" }, @@ -69444,20 +69955,17 @@ "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" }, "whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, "requires": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -69465,6 +69973,12 @@ } } }, + "node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "peer": true + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -75863,6 +76377,12 @@ "integrity": "sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==", "dev": true }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "peer": true + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -75905,7 +76425,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, "requires": { "is-typedarray": "^1.0.0" } @@ -76314,6 +76833,15 @@ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", "requires": {} }, + "utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "peer": true, + "requires": { + "node-gyp-build": "^4.3.0" + } + }, "util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", @@ -76968,6 +77496,20 @@ "integrity": "sha512-5tyDlKLqPfMqjT3Q9TAqf2YqjwmnUleZwzJi1A5qXnlBCdj2AtOJ6wAWdglTIDOPgOiOrXeBeFcsQ8+aGQ6QbA==", "dev": true }, + "websocket": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", + "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", + "peer": true, + "requires": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + } + }, "whatwg-encoding": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", @@ -77221,6 +77763,12 @@ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "dev": true }, + "yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", + "peer": true + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 03db44baa..eaa92bb99 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,9 @@ "@material-ui/icons": "4.11.3", "@material-ui/styles": "4.11.5", "@popperjs/core": "2.11.5", + "@supabase/auth-helpers-nextjs": "0.5.6", + "@supabase/auth-helpers-react": "0.3.1", + "@supabase/auth-ui-react": "0.3.5", "core-js": "3.21.1", "d3": "6.7.0", "date-fns": "2.28.0", diff --git a/pages/_app.tsx b/pages/_app.tsx index 5ea720642..9b8ea226c 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,8 +1,9 @@ import React from 'react'; +import { useState } from 'react'; import type { AppProps } from 'next/app'; import { Page } from '../src/nextPage'; import { Providers } from '../src/Providers'; - +import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'; type PagePropsType = { treeId?: string | null; }; @@ -16,6 +17,7 @@ export default function MyApp({ Component, pageProps, }: AppPropsType): JSX.Element { + const [supabaseClient] = useState(() => createBrowserSupabaseClient()); const getLayout = Component.getLayout ?? (page => page); const Layout = Component.layout ?? (({ children }) => <>{children}); return ( diff --git a/pages/auth.tsx b/pages/auth.tsx new file mode 100644 index 000000000..45fd539d6 --- /dev/null +++ b/pages/auth.tsx @@ -0,0 +1,523 @@ +import React, { useEffect, useState } from 'react'; +import { MapLayout } from '../src/components/MapLayout'; +import { Page } from '../src/nextPage'; +// import { Auth } from '@supabase/auth-ui-react'; +import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react'; +import { Database } from '../src/common/database'; +type AuthView = 'signin' | 'signup' | 'recovery' | 'confirm'; + +interface FormData { + email: string; + password: string; +} +const linkStyle = { + textDecoration: 'underline', +}; + +const Auth = () => { + const supabase = useSupabaseClient(); + + const [view, setView] = useState('signin'); + const [formData, setFormData] = useState({ + email: '', + password: '', + }); + + const clearFields = () => { + setFormData({ + email: '', + password: '', + }); + }; + + const handleSignInSubmit = (event: React.FormEvent) => { + event.preventDefault(); + + signIn(formData.email, formData.password).catch(error => { + console.log(error); + }); + clearFields(); + }; + const handleSignUpSubmit = (event: React.FormEvent) => { + event.preventDefault(); + console.log(formData); + signUp(formData.email, formData.password).catch(error => { + console.log(error); + }); + clearFields(); + }; + + const handleRecoverySubmit = (event: React.FormEvent) => { + event.preventDefault(); + recovery(formData.email).catch(error => { + console.log(error); + }); + clearFields(); + }; + + const handleInputChange = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setFormData({ + ...formData, + [name]: value, + }); + }; + + let form: JSX.Element | null = null; + let linkText: JSX.Element | null = null; + + const signUp = async (email: string, password: string) => { + const { data, error } = await supabase.auth.signUp({ + email, + password, + }); + if (error) { + if (error.message.includes('User already registered')) { + console.log('User already registered'); + setView('signin'); + return; + } + throw error; + } + console.log(data); + if (data.user) { + console.log('User created', data.user); + console.log('Check your email for the link!'); + console.log('Autoconfirm is not active'); + setView('confirm'); + } + if (data.session) { + console.log('Session', data.session); + console.log('Autoconfirm is active'); + } + }; + + const signIn = async (email: string, password: string) => { + const { data, error } = await supabase.auth.signInWithPassword({ + email, + password, + }); + if (error) { + throw error; + } + if (!data.user) { + throw new Error('No user'); + } + + if (!data.session) { + throw new Error('No session'); + } + + if (data.user) { + console.log('User', data.user); + } + if (data.session) { + console.log('Session', data.session); + } + }; + + const recovery = async (email: string) => { + let { data, error } = await supabase.auth.resetPasswordForEmail(email, { + redirectTo: 'http://localhost:3000/auth', + }); + if (error) { + throw error; + } + if (data) { + console.log('Check your email for the link!'); + } + }; + + switch (view) { + case 'signin': + form = ( +
+ + +
+ + +
+ +
+ ); + linkText = ( + <> + {'Du hast noch keinen Account?'}{' '} + setView('signup')} style={linkStyle}> + Registrier dich{' '} + + + ); + break; + case 'signup': + form = ( +
+ + +
+ + +
+ +
+ ); + linkText = ( + <> + {'Du hast schon einen Account?'}{' '} + setView('signin')} style={linkStyle}> + Log dich ein{' '} + + + ); + break; + case 'recovery': + form = ( +
+ + +
+ +
+ ); + linkText = ( + <> + {'Zurück zur Anmeldung?'}{' '} + setView('signin')} style={linkStyle}> + Hier klicken{' '} + + + ); + break; + default: + form = null; + linkText = null; + } + + return ( + <> + {form} +
+

{linkText}

+ {view !== 'recovery' && ( +

+ Oh nein. Du hast dein{' '} + setView('recovery')} style={linkStyle}> + Passwort vergessen? + +

+ )} +
+ + ); +}; + +const ResetPasswordButton = ({ + handleClick, +}: { + handleClick: (e: React.MouseEvent) => void; +}) => { + return ; +}; +const SignOut = () => { + const supabase = useSupabaseClient(); + return ( + <> + {' '} + + ); +}; + +const UpdateUserDataForm = () => { + const supabase = useSupabaseClient(); + const session = useSession(); + const [formData, setFormData] = useState({ + name: '', + email: '', + }); + + useEffect(() => { + if (session) { + const getUserProfile = async () => { + const { data, error } = await supabase + .from('profiles') + .select('id,username') + .eq('id', session?.user?.id) + .single(); + if (error) { + throw error; + } + if (data) { + setFormData({ + name: data.username ?? formData.email.split('@')[0], + email: session.user?.email || '', + }); + } + }; + setFormData({ + name: '', + email: session.user?.email || '', + }); + getUserProfile().catch(console.error); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleInputChange = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setFormData({ + ...formData, + [name]: value, + }); + }; + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + + const updateUser = async () => { + if (formData.email !== session?.user?.email) { + const { data, error } = await supabase.auth.updateUser({ + email: formData.email, + }); + if (error) { + throw error; + } + if (data) { + console.log('User updated'); + } + } + const { data, error } = await supabase + .from('profiles') + .select('id,username') + .eq('id', session?.user?.id) + .single(); + if (error) { + throw error; + } + if (data) { + if (data.username !== formData.name) { + const { data, error } = await supabase + .from('profiles') + .update({ username: formData.name }) + .eq('id', session?.user?.id); + if (error) { + throw error; + } + if (data) { + console.log('User updated'); + } + } + } + }; + updateUser().catch(console.error); + }; + return ( + <> + {session ? ( + <> + {' '} +
+ + +
+ + +
+ +
+ + ) : null} + + ); +}; + +const PasswordResetForm = ({ + additionalSubmitHandler, +}: { + additionalSubmitHandler: () => void; +}) => { + const supabase = useSupabaseClient(); + const session = useSession(); + const [formData, setFormData] = useState({ + password: '', + }); + const handleInputChange = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setFormData({ + ...formData, + [name]: value, + }); + }; + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + additionalSubmitHandler(); + const updatePassword = async () => { + const { data, error } = await supabase.auth.updateUser({ + password: formData.password, + }); + if (error) { + throw error; + } + if (data) { + console.log('Password updated'); + } + }; + updatePassword().catch(console.error); + }; + return ( + <> +
+

Passwort zurücksetzen für {session?.user?.email}

+
+
+ + +
+ +
+ + ); +}; + +const AuthPage: Page = () => { + const supabase = useSupabaseClient(); + const session = useSession(); + const [showPasswordResetScreen, setShowPasswordResetScreen] = useState(false); + + useEffect(() => { + const { + data: { subscription: authListener }, + } = supabase.auth.onAuthStateChange((event, session) => { + switch (event) { + case 'SIGNED_IN': + console.log('SIGNED_IN', session); + break; + case 'SIGNED_OUT': + console.log('SIGNED_OUT'); + break; + case 'USER_UPDATED': + console.log('USER_UPDATED', session?.user); + break; + case 'PASSWORD_RECOVERY': + console.log('PASSWORD_RECOVERY', session); + + // show screen to update user's password + setShowPasswordResetScreen(true); + break; + case 'TOKEN_REFRESHED': + console.log('TOKEN_REFRESHED', session); + break; + case 'USER_DELETED': + console.log('USER_DELETED'); + break; + default: + console.log('UNKNOWN_EVENT', event); + throw new Error('Unknown event type'); + } + }); + + return () => { + authListener?.unsubscribe(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + console.log('Session', session); + }, [session]); + if (showPasswordResetScreen) { + return ( + <> + {' '} + { + setShowPasswordResetScreen(false); + }} + /> + + ); + } + return ( + <> +

Auth

+ {!session ? ( + + ) : ( + <> + + { + e.preventDefault(); + setShowPasswordResetScreen(true); + }} + /> + + +
+            {JSON.stringify(session, null, 2)}
+          
+ + )} + + ); +}; +AuthPage.layout = MapLayout; + +export default AuthPage; diff --git a/src/Providers/index.tsx b/src/Providers/index.tsx index c42b425bc..9d04a6e08 100644 --- a/src/Providers/index.tsx +++ b/src/Providers/index.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { FC, useEffect, useState } from 'react'; import { QueryClient, QueryClientProvider } from 'react-query'; import { ReactQueryDevtools } from 'react-query/devtools'; import { ThemeProvider } from 'styled-components'; @@ -9,28 +9,52 @@ import GlobalStyles from '../assets/Global'; import { theme } from '../assets/theme'; import ErrorBoundary from '../ErrorBoundary'; import store from '../state/Store'; +import { Session, SessionContextProvider } from '@supabase/auth-helpers-react'; +import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'; const queryClient = new QueryClient(); -export const Providers: FC = ({ children }) => ( - - - - - - - - {children} - - - - - -); +export const Providers: FC = ({ children }) => { + const [supabaseClient] = useState(() => createBrowserSupabaseClient()); + const [session, setSession] = useState(null); + + useEffect(() => { + const getSession = async () => { + const { data, error } = await supabaseClient.auth.getSession(); + if (error) { + console.log(error); + return; + } + if (data) { + setSession(data.session); + } + }; + }); + return ( + + + + + + + + + {children} + + + + + + + ); +}; diff --git a/src/common/database.ts b/src/common/database.ts new file mode 100644 index 000000000..e1c047c45 --- /dev/null +++ b/src/common/database.ts @@ -0,0 +1,442 @@ +export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json } + | Json[] + +export interface Database { + graphql_public: { + Tables: { + [_ in never]: never + } + Views: { + [_ in never]: never + } + Functions: { + graphql: { + Args: { + operationName?: string + query?: string + variables?: Json + extensions?: Json + } + Returns: Json + } + } + Enums: { + [_ in never]: never + } + CompositeTypes: { + [_ in never]: never + } + } + public: { + Tables: { + profiles: { + Row: { + id: string + username: string | null + } + Insert: { + id: string + username?: string | null + } + Update: { + id?: string + username?: string | null + } + } + radolan_data: { + Row: { + geom_id: number | null + id: number + measured_at: string | null + value: number | null + } + Insert: { + geom_id?: number | null + id?: number + measured_at?: string | null + value?: number | null + } + Update: { + geom_id?: number | null + id?: number + measured_at?: string | null + value?: number | null + } + } + radolan_geometry: { + Row: { + centroid: unknown | null + geometry: unknown | null + id: number + } + Insert: { + centroid?: unknown | null + geometry?: unknown | null + id?: number + } + Update: { + centroid?: unknown | null + geometry?: unknown | null + id?: number + } + } + radolan_harvester: { + Row: { + collection_date: string | null + end_date: string | null + id: number + start_date: string | null + } + Insert: { + collection_date?: string | null + end_date?: string | null + id?: number + start_date?: string | null + } + Update: { + collection_date?: string | null + end_date?: string | null + id?: number + start_date?: string | null + } + } + radolan_temp: { + Row: { + geometry: unknown | null + id: number + measured_at: string | null + value: number | null + } + Insert: { + geometry?: unknown | null + id?: number + measured_at?: string | null + value?: number | null + } + Update: { + geometry?: unknown | null + id?: number + measured_at?: string | null + value?: number | null + } + } + trees: { + Row: { + adopted: string | null + artbot: string | null + artdtsch: string | null + baumhoehe: string | null + bezirk: string | null + caretaker: string | null + eigentuemer: string | null + gattung: string | null + gattungdeutsch: string | null + geom: unknown | null + gmlid: string | null + hausnr: string | null + id: string + kennzeich: string | null + kronedurch: string | null + lat: string | null + lng: string | null + pflanzjahr: number | null + radolan_days: number[] | null + radolan_sum: number | null + stammumfg: string | null + standalter: string | null + standortnr: string | null + strname: string | null + type: string | null + watered: string | null + zusatz: string | null + } + Insert: { + adopted?: string | null + artbot?: string | null + artdtsch?: string | null + baumhoehe?: string | null + bezirk?: string | null + caretaker?: string | null + eigentuemer?: string | null + gattung?: string | null + gattungdeutsch?: string | null + geom?: unknown | null + gmlid?: string | null + hausnr?: string | null + id: string + kennzeich?: string | null + kronedurch?: string | null + lat?: string | null + lng?: string | null + pflanzjahr?: number | null + radolan_days?: number[] | null + radolan_sum?: number | null + stammumfg?: string | null + standalter?: string | null + standortnr?: string | null + strname?: string | null + type?: string | null + watered?: string | null + zusatz?: string | null + } + Update: { + adopted?: string | null + artbot?: string | null + artdtsch?: string | null + baumhoehe?: string | null + bezirk?: string | null + caretaker?: string | null + eigentuemer?: string | null + gattung?: string | null + gattungdeutsch?: string | null + geom?: unknown | null + gmlid?: string | null + hausnr?: string | null + id?: string + kennzeich?: string | null + kronedurch?: string | null + lat?: string | null + lng?: string | null + pflanzjahr?: number | null + radolan_days?: number[] | null + radolan_sum?: number | null + stammumfg?: string | null + standalter?: string | null + standortnr?: string | null + strname?: string | null + type?: string | null + watered?: string | null + zusatz?: string | null + } + } + trees_adopted: { + Row: { + id: number + tree_id: string + uuid: string | null + } + Insert: { + id?: number + tree_id: string + uuid?: string | null + } + Update: { + id?: number + tree_id?: string + uuid?: string | null + } + } + trees_watered: { + Row: { + amount: number + id: number + time: string | null + timestamp: string + tree_id: string + username: string | null + uuid: string | null + } + Insert: { + amount: number + id?: number + time?: string | null + timestamp: string + tree_id: string + username?: string | null + uuid?: string | null + } + Update: { + amount?: number + id?: number + time?: string | null + timestamp?: string + tree_id?: string + username?: string | null + uuid?: string | null + } + } + } + Views: { + [_ in never]: never + } + Functions: { + count_by_age: { + Args: { + start_year: number + end_year: number + } + Returns: number + } + get_watered_and_adopted: { + Args: Record + Returns: { + tree_id: string + adopted: number + watered: number + }[] + } + } + Enums: { + [_ in never]: never + } + CompositeTypes: { + [_ in never]: never + } + } + storage: { + Tables: { + buckets: { + Row: { + allowed_mime_types: string[] | null + avif_autodetection: boolean | null + created_at: string | null + file_size_limit: number | null + id: string + name: string + owner: string | null + public: boolean | null + updated_at: string | null + } + Insert: { + allowed_mime_types?: string[] | null + avif_autodetection?: boolean | null + created_at?: string | null + file_size_limit?: number | null + id: string + name: string + owner?: string | null + public?: boolean | null + updated_at?: string | null + } + Update: { + allowed_mime_types?: string[] | null + avif_autodetection?: boolean | null + created_at?: string | null + file_size_limit?: number | null + id?: string + name?: string + owner?: string | null + public?: boolean | null + updated_at?: string | null + } + } + migrations: { + Row: { + executed_at: string | null + hash: string + id: number + name: string + } + Insert: { + executed_at?: string | null + hash: string + id: number + name: string + } + Update: { + executed_at?: string | null + hash?: string + id?: number + name?: string + } + } + objects: { + Row: { + bucket_id: string | null + created_at: string | null + id: string + last_accessed_at: string | null + metadata: Json | null + name: string | null + owner: string | null + path_tokens: string[] | null + updated_at: string | null + } + Insert: { + bucket_id?: string | null + created_at?: string | null + id?: string + last_accessed_at?: string | null + metadata?: Json | null + name?: string | null + owner?: string | null + path_tokens?: string[] | null + updated_at?: string | null + } + Update: { + bucket_id?: string | null + created_at?: string | null + id?: string + last_accessed_at?: string | null + metadata?: Json | null + name?: string | null + owner?: string | null + path_tokens?: string[] | null + updated_at?: string | null + } + } + } + Views: { + [_ in never]: never + } + Functions: { + extension: { + Args: { + name: string + } + Returns: string + } + filename: { + Args: { + name: string + } + Returns: string + } + foldername: { + Args: { + name: string + } + Returns: string[] + } + get_size_by_bucket: { + Args: Record + Returns: { + size: number + bucket_id: string + }[] + } + search: { + Args: { + prefix: string + bucketname: string + limits?: number + levels?: number + offsets?: number + search?: string + sortcolumn?: string + sortorder?: string + } + Returns: { + name: string + id: string + updated_at: string + created_at: string + last_accessed_at: string + metadata: Json + }[] + } + } + Enums: { + [_ in never]: never + } + CompositeTypes: { + [_ in never]: never + } + } +} + From 2e4261be68878173af80276f2ed1067feaedaab5 Mon Sep 17 00:00:00 2001 From: ff6347 Date: Tue, 21 Mar 2023 17:20:13 +0100 Subject: [PATCH 002/121] refactor(api routes): Match routes of new api --- src/mocks/handlers.ts | 12 ++++++------ src/utils/requests/adoptTree.ts | 4 ++-- src/utils/requests/getCommunityData.ts | 2 +- src/utils/requests/getTreeData.ts | 4 ++-- src/utils/requests/getTreesAdoptedByUser.ts | 2 +- src/utils/requests/getTreesByIds.ts | 2 +- src/utils/requests/getUserWaterings.ts | 2 +- src/utils/requests/getWateredTrees.ts | 2 +- src/utils/requests/isTreeAdopted.ts | 2 +- src/utils/requests/unadoptTree.ts | 2 +- src/utils/requests/unwaterTree.ts | 2 +- src/utils/requests/waterTree.ts | 2 +- 12 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index dbfadfe73..3a08a5d8a 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -61,11 +61,11 @@ export function getProperty( export const handlers = [ // Handles a POST /login request - rest.post(`${location}/login`, async (_req, res, ctx) => { - return res(ctx.status(201)); - }), + // rest.post(`${location}/login`, async (_req, res, ctx) => { + // return res(ctx.status(201)); + // }), - rest.delete(`${location}/delete/:type`, (req, res, ctx) => { + rest.delete(`${location}/v3/delete/:type`, (req, res, ctx) => { // console.log('intercepting DELETE requests'); const json: Payload = {}; let body: Record = {}; @@ -107,7 +107,7 @@ export const handlers = [ } return res(ctx.status(201), ctx.json(json)); }), - rest.post(`${location}/post/:type`, (req, res, ctx) => { + rest.post(`${location}/v3/post/:type`, (req, res, ctx) => { let json: Payload = {}; let body: Record = {}; if (typeof req.body === 'string') { @@ -201,7 +201,7 @@ export const handlers = [ return res(ctx.status(200), ctx.json({ foo: 'bar' })); }), - rest.get(`${location}/get/:type`, async (req, res, ctx) => { + rest.get(`${location}/v3/get/:type`, async (req, res, ctx) => { let json: Payload = {}; const { type } = req.params as Record; diff --git a/src/utils/requests/adoptTree.ts b/src/utils/requests/adoptTree.ts index 82f463a19..e2f9ce0c3 100644 --- a/src/utils/requests/adoptTree.ts +++ b/src/utils/requests/adoptTree.ts @@ -12,7 +12,7 @@ export const adoptTree = async ({ token: string; userId: string; }): Promise => { - const url = createAPIUrl(`/post/adopt`); + const url = createAPIUrl(`/v3/post/adopt`); await requests(url, { token, @@ -23,7 +23,7 @@ export const adoptTree = async ({ }); const res = await requests<{ data: Tree[] }>( - createAPIUrl(`/get/byid?id=${id}`) + createAPIUrl(`/v3/get/byid?id=${id}`) ); const tree = res.data[0]; diff --git a/src/utils/requests/getCommunityData.ts b/src/utils/requests/getCommunityData.ts index 786de5f7a..05d1df39e 100644 --- a/src/utils/requests/getCommunityData.ts +++ b/src/utils/requests/getCommunityData.ts @@ -15,7 +15,7 @@ type WateredAndAdoptedResponseType = RawRequestResponse< >; export const getCommunityData = async (): Promise => { - const fetchWateredAndAdoptedUrl = createAPIUrl(`/get/wateredandadopted`); + const fetchWateredAndAdoptedUrl = createAPIUrl(`/v3/get/wateredandadopted`); const wateredAndAdopted = await requests( fetchWateredAndAdoptedUrl diff --git a/src/utils/requests/getTreeData.ts b/src/utils/requests/getTreeData.ts index cfcef4e33..7052718d7 100644 --- a/src/utils/requests/getTreeData.ts +++ b/src/utils/requests/getTreeData.ts @@ -40,8 +40,8 @@ export const getTreeData = async ( ): Promise<{ selectedTreeData: SelectedTreeType | undefined; }> => { - const urlSelectedTree = createAPIUrl(`/get/byid?id=${id}`); - const urlWaterings = createAPIUrl(`/get/lastwatered?id=${id}`); + const urlSelectedTree = createAPIUrl(`/v3/get/byid?id=${id}`); + const urlWaterings = createAPIUrl(`/v3/get/lastwatered?id=${id}`); const [resSelectedTree, resWaterings] = await Promise.all([ requests(urlSelectedTree), diff --git a/src/utils/requests/getTreesAdoptedByUser.ts b/src/utils/requests/getTreesAdoptedByUser.ts index 2ae395209..72fe89b8b 100644 --- a/src/utils/requests/getTreesAdoptedByUser.ts +++ b/src/utils/requests/getTreesAdoptedByUser.ts @@ -10,7 +10,7 @@ export const getTreesAdoptedByUser = async ({ userId: string; token: string; }): Promise => { - const urlAdoptedTreesIds = createAPIUrl(`/get/adopted?uuid=${userId}`); + const urlAdoptedTreesIds = createAPIUrl(`/v3/get/adopted?uuid=${userId}`); const res = await requests<{ data: string[] }>(urlAdoptedTreesIds, { token }); if (!res?.data || res.data.length === 0) return []; const trees = await getTreesByIds(res.data); diff --git a/src/utils/requests/getTreesByIds.ts b/src/utils/requests/getTreesByIds.ts index 512d6488e..ad0bf6989 100644 --- a/src/utils/requests/getTreesByIds.ts +++ b/src/utils/requests/getTreesByIds.ts @@ -9,7 +9,7 @@ export const getTreesByIds = async (ids: string[]): Promise => { '' ); - const url = createAPIUrl(`/get/treesbyids?tree_ids=${queryStr}`); + const url = createAPIUrl(`/v3/get/treesbyids?tree_ids=${queryStr}`); const res = await requests<{ data: Tree[] }>(url); return res.data; }; diff --git a/src/utils/requests/getUserWaterings.ts b/src/utils/requests/getUserWaterings.ts index a0428a647..abd64b3d3 100644 --- a/src/utils/requests/getUserWaterings.ts +++ b/src/utils/requests/getUserWaterings.ts @@ -10,7 +10,7 @@ export const getUserWaterings = async ({ userId: string; token: string; }): Promise => { - const urlWateredByUser = createAPIUrl(`/get/wateredbyuser?uuid=${userId}`); + const urlWateredByUser = createAPIUrl(`/v3/get/wateredbyuser?uuid=${userId}`); const res = await requests<{ data: RawWateringType[] }>(urlWateredByUser, { token, }); diff --git a/src/utils/requests/getWateredTrees.ts b/src/utils/requests/getWateredTrees.ts index 1dd6a5857..115e1ac86 100644 --- a/src/utils/requests/getWateredTrees.ts +++ b/src/utils/requests/getWateredTrees.ts @@ -2,7 +2,7 @@ import { createAPIUrl } from '../createAPIUrl'; import { requests } from '../requestUtil'; export const getWateredTrees = async (): Promise => { - const url = createAPIUrl('/get/watered'); + const url = createAPIUrl('/v3/get/watered'); const result = await requests<{ data?: { watered: string[] } }>(url); if (result.data === undefined) { diff --git a/src/utils/requests/isTreeAdopted.ts b/src/utils/requests/isTreeAdopted.ts index e0abcd135..7519cd0d9 100644 --- a/src/utils/requests/isTreeAdopted.ts +++ b/src/utils/requests/isTreeAdopted.ts @@ -14,7 +14,7 @@ export async function isTreeAdopted( ): Promise { const { isAuthenticated, uuid, id, token, signal } = opts; if (!isAuthenticated) return false; - const url = createAPIUrl(`/get/istreeadopted?uuid=${uuid}&id=${id}`); + const url = createAPIUrl(`/v3/get/istreeadopted?uuid=${uuid}&id=${id}`); const json = await requests< { data: IsTreeAdoptedProps }, diff --git a/src/utils/requests/unadoptTree.ts b/src/utils/requests/unadoptTree.ts index b6b65caa1..acbaed3ac 100644 --- a/src/utils/requests/unadoptTree.ts +++ b/src/utils/requests/unadoptTree.ts @@ -10,7 +10,7 @@ export const unadoptTree = async ({ token: string; userId: string; }): Promise => { - const urlUnadopt = createAPIUrl(`/delete/unadopt`); + const urlUnadopt = createAPIUrl(`/v3/delete/unadopt`); await requests(urlUnadopt, { token, diff --git a/src/utils/requests/unwaterTree.ts b/src/utils/requests/unwaterTree.ts index 340a8514b..69a80a11b 100644 --- a/src/utils/requests/unwaterTree.ts +++ b/src/utils/requests/unwaterTree.ts @@ -14,7 +14,7 @@ export const unwaterTree: UnwaterTreeSignature = async ({ userId, wateringId, }) => { - const urlUnwater = createAPIUrl(`/delete/unwater`); + const urlUnwater = createAPIUrl(`/v3/delete/unwater`); await requests(urlUnwater, { token, diff --git a/src/utils/requests/waterTree.ts b/src/utils/requests/waterTree.ts index 4d20c8ac1..62ac2ab0e 100644 --- a/src/utils/requests/waterTree.ts +++ b/src/utils/requests/waterTree.ts @@ -16,7 +16,7 @@ export const waterTree = async ({ token: string; timestamp: Date; }): Promise => { - const urlPostWatering = createAPIUrl(`/post/water`); + const urlPostWatering = createAPIUrl(`/v3/post/water`); await requests(urlPostWatering, { token, From 2d7b9c4442555c51f55fcbb7fea23e39159ebf24 Mon Sep 17 00:00:00 2001 From: ff6347 Date: Tue, 21 Mar 2023 17:21:30 +0100 Subject: [PATCH 003/121] refactor: Example error messaging --- pages/auth.tsx | 54 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/pages/auth.tsx b/pages/auth.tsx index 45fd539d6..2c6d77d9d 100644 --- a/pages/auth.tsx +++ b/pages/auth.tsx @@ -14,9 +14,26 @@ const linkStyle = { textDecoration: 'underline', }; +const ErrorNotifiction = ({ message }: { message: string }) => { + return ( +
+

{message}

+ +
+ ); +}; + const Auth = () => { const supabase = useSupabaseClient(); + const [errorMessage, setErrorMessage] = useState(null); const [view, setView] = useState('signin'); const [formData, setFormData] = useState({ email: '', @@ -24,6 +41,7 @@ const Auth = () => { }); const clearFields = () => { + setErrorMessage(null); setFormData({ email: '', password: '', @@ -34,15 +52,17 @@ const Auth = () => { event.preventDefault(); signIn(formData.email, formData.password).catch(error => { - console.log(error); + console.error(error); + setErrorMessage(error.message); }); clearFields(); }; const handleSignUpSubmit = (event: React.FormEvent) => { event.preventDefault(); - console.log(formData); + signUp(formData.email, formData.password).catch(error => { - console.log(error); + console.error(error); + setErrorMessage(error.message); }); clearFields(); }; @@ -50,13 +70,15 @@ const Auth = () => { const handleRecoverySubmit = (event: React.FormEvent) => { event.preventDefault(); recovery(formData.email).catch(error => { - console.log(error); + console.error(error); + setErrorMessage(error.message); }); clearFields(); }; const handleInputChange = (event: React.ChangeEvent) => { const { name, value } = event.target; + setErrorMessage(null); setFormData({ ...formData, [name]: value, @@ -73,17 +95,16 @@ const Auth = () => { }); if (error) { if (error.message.includes('User already registered')) { - console.log('User already registered'); + setErrorMessage('User already registered'); + console.error('User already registered'); setView('signin'); return; } throw error; } - console.log(data); if (data.user) { - console.log('User created', data.user); - console.log('Check your email for the link!'); - console.log('Autoconfirm is not active'); + setErrorMessage('Check your email for the link!'); + // console.log('Autoconfirm is not active'); setView('confirm'); } if (data.session) { @@ -98,22 +119,19 @@ const Auth = () => { password, }); if (error) { + setErrorMessage(error.message); + console.error(error); throw error; } if (!data.user) { + setErrorMessage('500 - Internal Server Error'); throw new Error('No user'); } if (!data.session) { + setErrorMessage('500 - Internal Server Error'); throw new Error('No session'); } - - if (data.user) { - console.log('User', data.user); - } - if (data.session) { - console.log('Session', data.session); - } }; const recovery = async (email: string) => { @@ -121,10 +139,11 @@ const Auth = () => { redirectTo: 'http://localhost:3000/auth', }); if (error) { + setErrorMessage(error.message); throw error; } if (data) { - console.log('Check your email for the link!'); + setErrorMessage('Check your email for the link!'); } }; @@ -222,6 +241,7 @@ const Auth = () => { return ( <> {form} +
{errorMessage && }

{linkText}

{view !== 'recovery' && ( From 65a0da2203d2f8babd82e133ba7c8d90a8469c65 Mon Sep 17 00:00:00 2001 From: ff6347 Date: Tue, 21 Mar 2023 17:22:41 +0100 Subject: [PATCH 004/121] chore: Mark functions as deprecated --- src/utils/hooks/useAccountActions.ts | 42 ++++++++++++++++++++-------- src/utils/hooks/useAuth0Token.ts | 5 +++- src/utils/requests/getUserInfo.ts | 4 +++ 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/utils/hooks/useAccountActions.ts b/src/utils/hooks/useAccountActions.ts index dbf75c99a..385ca767d 100644 --- a/src/utils/hooks/useAccountActions.ts +++ b/src/utils/hooks/useAccountActions.ts @@ -1,27 +1,47 @@ -import { useAuth0 } from '@auth0/auth0-react'; +import { useSupabaseClient } from '@supabase/auth-helpers-react'; +import Router from 'next/router'; import { deleteAccount } from '../requests/deleteAccount'; -import { useAuth0Token } from './useAuth0Token'; +import { useSupabaseToken } from './useSupabaseToken'; +import { useSupabaseUser } from './useSupabaseUser'; +/** + * @deprecated + */ export const useAccountActions = (): { logout: () => void; login: () => void; deleteAccount: () => Promise; } => { - const { user, logout, loginWithRedirect } = useAuth0(); - const token = useAuth0Token(); + const user = useSupabaseUser(); + const supabase = useSupabaseClient(); + // const { user, logout, loginWithRedirect } = useAuth0(); + const token = useSupabaseToken(); return { - logout: () => { - if (!user?.sub) return; - logout({ returnTo: window.location.origin }); + logout: async () => { + if (!user?.id) return; + const { error } = await supabase.auth.signOut(); + if (error) { + console.error(error); + return; + } + Router.push('/'); + + // logout({ returnTo: window.location.origin }); }, login: () => { - loginWithRedirect({ ui_locales: 'de' }); + Router.push('/auth'); + // loginWithRedirect({ ui_locales: 'de' }); }, deleteAccount: async () => { - if (!user?.sub || !token) return; - await deleteAccount({ token, userId: user.sub }); - logout({ returnTo: window.location.origin }); + if (!user?.id || !token) return; + await deleteAccount({ token }); + const { error } = await supabase.auth.signOut(); + if (error) { + console.error(error); + return; + } + Router.push('/'); }, }; }; diff --git a/src/utils/hooks/useAuth0Token.ts b/src/utils/hooks/useAuth0Token.ts index 391fff32e..6a00fa8e4 100644 --- a/src/utils/hooks/useAuth0Token.ts +++ b/src/utils/hooks/useAuth0Token.ts @@ -1,6 +1,9 @@ import { useEffect, useState } from 'react'; import { useAuth0 } from '@auth0/auth0-react'; - +// TODO: Replace the token here? +/** + * @deprecated + */ export const useAuth0Token = (): string | undefined => { const [token, setToken] = useState(undefined); const { user, getAccessTokenSilently } = useAuth0(); diff --git a/src/utils/requests/getUserInfo.ts b/src/utils/requests/getUserInfo.ts index 1410b6657..3ad3df013 100644 --- a/src/utils/requests/getUserInfo.ts +++ b/src/utils/requests/getUserInfo.ts @@ -1,6 +1,10 @@ import { requests } from '../requestUtil'; import { User } from 'auth0'; +/** + * + * @deprecated Replaced by useSupabaseUser() hook + */ export const getUserInfo = async ({ userId, token, From 5ddf0905a75c52fa50bb98c52472ddd41c372388 Mon Sep 17 00:00:00 2001 From: ff6347 Date: Tue, 21 Mar 2023 17:23:34 +0100 Subject: [PATCH 005/121] refactor(getUserData): User data exists already --- src/utils/requests/getUserData.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/utils/requests/getUserData.ts b/src/utils/requests/getUserData.ts index c91c2799c..fc7676914 100644 --- a/src/utils/requests/getUserData.ts +++ b/src/utils/requests/getUserData.ts @@ -1,7 +1,6 @@ -import { UserDataType } from '../../common/interfaces'; +import { OptionalUserDataType } from '../../common/interfaces'; import { getTreesAdoptedByUser } from './getTreesAdoptedByUser'; import { getUserWaterings } from './getUserWaterings'; -import { getUserInfo } from './getUserInfo'; export const getUserData = async ({ userId, @@ -9,20 +8,14 @@ export const getUserData = async ({ }: { userId: string; token: string; -}): Promise => { +}): Promise => { const res = await Promise.all([ - getUserInfo({ userId, token }), getUserWaterings({ userId, token }), getTreesAdoptedByUser({ userId, token }), ]); - const [user, waterings, adoptedTrees] = res; - if (!user) return undefined; + const [waterings, adoptedTrees] = res; return { - id: userId, - email: user.email || '', - username: user.username || '', - isVerified: user.email_verified || false, waterings, adoptedTrees, }; From 145b8a2e74b0d45d81e8f7e677a93db6a5940984 Mon Sep 17 00:00:00 2001 From: ff6347 Date: Tue, 21 Mar 2023 17:23:42 +0100 Subject: [PATCH 006/121] refactor(getUserData): User data exists already --- src/common/interfaces.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/common/interfaces.ts b/src/common/interfaces.ts index ee9756761..8f6bb5410 100644 --- a/src/common/interfaces.ts +++ b/src/common/interfaces.ts @@ -33,6 +33,14 @@ export interface UserDataType { waterings: WateringType[]; adoptedTrees: Tree[]; } + +export interface OptionalUserDataType + extends Omit { + id?: string; + email?: string; + username?: string; + isVerified?: boolean; +} export interface SelectedTreeType extends Tree { radolan_days: RadolanDays; radolan_sum: number; From c4863799bbfd998e9c1c07d71b2e68265990e4a3 Mon Sep 17 00:00:00 2001 From: ff6347 Date: Tue, 21 Mar 2023 17:24:36 +0100 Subject: [PATCH 007/121] refactor: Remove from deprecated functions --- src/utils/hooks/useAccountActions.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/utils/hooks/useAccountActions.ts b/src/utils/hooks/useAccountActions.ts index 385ca767d..27ca5670c 100644 --- a/src/utils/hooks/useAccountActions.ts +++ b/src/utils/hooks/useAccountActions.ts @@ -4,9 +4,6 @@ import { deleteAccount } from '../requests/deleteAccount'; import { useSupabaseToken } from './useSupabaseToken'; import { useSupabaseUser } from './useSupabaseUser'; -/** - * @deprecated - */ export const useAccountActions = (): { logout: () => void; login: () => void; From a10479e9804d9eb98b7d4ad5465cabcd7693b7af Mon Sep 17 00:00:00 2001 From: ff6347 Date: Tue, 21 Mar 2023 17:25:36 +0100 Subject: [PATCH 008/121] feat(hooks): Add supabase hooks Use supabase in all hooks --- src/utils/hooks/useAdoptingActions.ts | 16 ++++---- src/utils/hooks/useSupabaseProfile.ts | 33 ++++++++++++++++ src/utils/hooks/useSupabaseToken.ts | 24 ++++++++++++ src/utils/hooks/useSupabaseUser.ts | 34 ++++++++++++++++ src/utils/hooks/useTreeData.ts | 10 ++--- src/utils/hooks/useUserData.ts | 56 ++++++++++++++++++++++----- src/utils/hooks/useWateringActions.ts | 4 +- 7 files changed, 152 insertions(+), 25 deletions(-) create mode 100644 src/utils/hooks/useSupabaseProfile.ts create mode 100644 src/utils/hooks/useSupabaseToken.ts create mode 100644 src/utils/hooks/useSupabaseUser.ts diff --git a/src/utils/hooks/useAdoptingActions.ts b/src/utils/hooks/useAdoptingActions.ts index 4dd8a2ca5..16300de22 100644 --- a/src/utils/hooks/useAdoptingActions.ts +++ b/src/utils/hooks/useAdoptingActions.ts @@ -1,11 +1,11 @@ import { useState } from 'react'; -import { useAuth0 } from '@auth0/auth0-react'; import { adoptTree } from '../requests/adoptTree'; import { unadoptTree } from '../requests/unadoptTree'; -import { useAuth0Token } from './useAuth0Token'; import { useCommunityData } from './useCommunityData'; import { useTreeData } from './useTreeData'; import { useUserData } from './useUserData'; +import { useSupabaseToken } from './useSupabaseToken'; +import { useSupabaseUser } from './useSupabaseUser'; export const useAdoptingActions = ( treeId: string | null | undefined @@ -15,8 +15,8 @@ export const useAdoptingActions = ( isBeingAdopted: boolean; isBeingUnadopted: boolean; } => { - const { user } = useAuth0(); - const token = useAuth0Token(); + const user = useSupabaseUser(); + const token = useSupabaseToken(); const { invalidate: invalidateUserData } = useUserData(); const { invalidate: invalidateCommunityData } = useCommunityData(); const { invalidate: invalidateTreeData } = useTreeData(treeId); @@ -27,10 +27,10 @@ export const useAdoptingActions = ( isBeingAdopted, isBeingUnadopted, adoptTree: async (): Promise => { - if (!treeId || !user?.sub || !token) return; + if (!treeId || !user?.id || !token) return; setIsBeingAdopted(true); - await adoptTree({ id: treeId, token, userId: user.sub }); + await adoptTree({ id: treeId, token, userId: user.id }); setIsBeingAdopted(false); invalidateUserData(); @@ -38,10 +38,10 @@ export const useAdoptingActions = ( invalidateCommunityData(); }, unadoptTree: async (): Promise => { - if (!treeId || !user?.sub || !token) return; + if (!treeId || !user?.id || !token) return; setIsBeingUnadopted(true); - await unadoptTree({ id: treeId, token, userId: user.sub }); + await unadoptTree({ id: treeId, token, userId: user.id }); setIsBeingUnadopted(false); invalidateUserData(); diff --git a/src/utils/hooks/useSupabaseProfile.ts b/src/utils/hooks/useSupabaseProfile.ts new file mode 100644 index 000000000..9471ae983 --- /dev/null +++ b/src/utils/hooks/useSupabaseProfile.ts @@ -0,0 +1,33 @@ +import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react'; +import { useEffect, useState } from 'react'; +import { Database } from '../../common/database'; + +export const useSupabaseProfile = () => { + const supabase = useSupabaseClient(); + const [profile, setProfile] = useState< + Database['public']['Tables']['profiles']['Row'] | null + >(null); + + const session = useSession(); + + useEffect(() => { + if (!session) return; + const getProfile = async () => { + try { + const { data: profile, error } = await supabase + .from('profiles') + .select('*') + .eq('id', session.user?.id) + .single(); + if (error) throw error; + setProfile(profile ?? null); + return profile; + } catch (err) { + console.error(err); + return null; + } + }; + getProfile().catch(console.error); + }, [session, supabase]); + return profile; +}; diff --git a/src/utils/hooks/useSupabaseToken.ts b/src/utils/hooks/useSupabaseToken.ts new file mode 100644 index 000000000..4fa36f90c --- /dev/null +++ b/src/utils/hooks/useSupabaseToken.ts @@ -0,0 +1,24 @@ +import { useSession } from '@supabase/auth-helpers-react'; +import { useEffect, useState } from 'react'; + +export const useSupabaseToken = () => { + const [token, setToken] = useState(undefined); + const session = useSession(); + + useEffect(() => { + if (!session) return; + const getToken = async () => { + try { + const token = session.access_token; + setToken(token ?? undefined); + return token; + } catch (err) { + console.error(err); + return undefined; + } + }; + getToken().catch(console.error); + }, [session]); + + return token; +}; diff --git a/src/utils/hooks/useSupabaseUser.ts b/src/utils/hooks/useSupabaseUser.ts new file mode 100644 index 000000000..59e44b241 --- /dev/null +++ b/src/utils/hooks/useSupabaseUser.ts @@ -0,0 +1,34 @@ +import { + User, + useSession, + useSupabaseClient, +} from '@supabase/auth-helpers-react'; +import { useEffect, useState } from 'react'; + +export const useSupabaseUser = () => { + const [user, setUser] = useState(undefined); + const session = useSession(); + const supabase = useSupabaseClient(); + + useEffect(() => { + if (!session) return; + const getUser = async () => { + try { + const { + data: { user }, + } = await supabase.auth.getUser(); + + // const user = session.user; + setUser(user ?? undefined); + console.log(user, 'user'); + return user; + } catch (err) { + console.error(err); + return undefined; + } + }; + getUser().catch(console.error); + }, [session, supabase]); + + return user; +}; diff --git a/src/utils/hooks/useTreeData.ts b/src/utils/hooks/useTreeData.ts index 9ff41cb0e..60d90056e 100644 --- a/src/utils/hooks/useTreeData.ts +++ b/src/utils/hooks/useTreeData.ts @@ -1,9 +1,9 @@ import { QueryFunction, useQuery, useQueryClient } from 'react-query'; import { SelectedTreeType } from '../../common/interfaces'; -import { useAuth0 } from '@auth0/auth0-react'; import { getTreeData } from '../requests/getTreeData'; import { isTreeAdopted as isTreeAdoptedReq } from '../requests/isTreeAdopted'; -import { useAuth0Token } from './useAuth0Token'; +import { useSupabaseToken } from './useSupabaseToken'; +import { useSupabaseUser } from './useSupabaseUser'; const loadTree: QueryFunction = async ({ queryKey, @@ -49,8 +49,8 @@ export const useTreeData = ( invalidate: () => void; } => { const queryClient = useQueryClient(); - const { user } = useAuth0(); - const token = useAuth0Token(); + const user = useSupabaseUser(); + const token = useSupabaseToken(); const treeDataParams = [`tree-${treeId}`, treeId]; const { data: treeData, error } = useQuery< @@ -61,7 +61,7 @@ export const useTreeData = ( refetchOnWindowFocus: false, }); - const isAdoptedParams = [`tree-${treeId}-adopted`, treeId, token, user?.sub]; + const isAdoptedParams = [`tree-${treeId}-adopted`, treeId, token, user?.id]; const { data: isAdopted, error: adoptedError } = useQuery< boolean | undefined, Error diff --git a/src/utils/hooks/useUserData.ts b/src/utils/hooks/useUserData.ts index f382fa225..5a5f247f7 100644 --- a/src/utils/hooks/useUserData.ts +++ b/src/utils/hooks/useUserData.ts @@ -1,12 +1,12 @@ import { QueryFunction, useQuery, useQueryClient } from 'react-query'; -import { UserDataType } from '../../common/interfaces'; -import { useAuth0 } from '@auth0/auth0-react'; +import { OptionalUserDataType, UserDataType } from '../../common/interfaces'; import { getUserData } from '../requests/getUserData'; -import { useAuth0Token } from './useAuth0Token'; - +import { useSupabaseUser } from './useSupabaseUser'; +import { useSupabaseToken } from './useSupabaseToken'; +import { useSupabaseProfile } from './useSupabaseProfile'; type UserDataError = Error | null; -const fetchUserData: QueryFunction = async ({ +const fetchUserData: QueryFunction = async ({ queryKey, }) => { const [, token, userId] = queryKey; @@ -26,16 +26,52 @@ export const useUserData = (): { error: Error | null; invalidate: () => void; } => { - const { user } = useAuth0(); - const token = useAuth0Token(); + const user = useSupabaseUser(); + const profile = useSupabaseProfile(); + + const token = useSupabaseToken(); const queryClient = useQueryClient(); - const queryParams = ['userData', token, user?.sub]; - const { data: userData, error } = useQuery< - UserDataType | undefined, + const queryParams = ['userData', token, user?.id]; + const { data: partialUserData, error } = useQuery< + OptionalUserDataType | undefined, UserDataError >(queryParams, fetchUserData, { staleTime: Infinity }); + let userData: UserDataType | undefined; + if (user && partialUserData && profile) { +{ + +Create a trigger function that updates the table trees_watered whenever the table column username on the table +profiles changes. + +CREATE TABLE "public"."profiles" ( + "id" uuid NOT NULL, + "username" text +); +-- Sequence and defined type +CREATE SEQUENCE IF NOT EXISTS trees_watered_id_seq; + +-- Table Definition +CREATE TABLE "public"."trees_watered" ( + "time" text, + "uuid" text, + "amount" numeric NOT NULL CHECK (amount > (0)::numeric), + "timestamp" timestamptz NOT NULL, + "username" text, + "id" int4 NOT NULL DEFAULT nextval('trees_watered_id_seq'::regclass), + "tree_id" text NOT NULL, + CONSTRAINT "fk_trees_watered_trees" FOREIGN KEY ("tree_id") REFERENCES "public"."trees"("id"), + PRIMARY KEY ("id") +); +} userData = { + id: user.id, + email: user.email!, + isVerified: user.email_confirmed_at !== undefined ? true : false, + username: profile.username!, + ...partialUserData, + }; + } return { userData, error, diff --git a/src/utils/hooks/useWateringActions.ts b/src/utils/hooks/useWateringActions.ts index 41b3f8d7b..12f97fc61 100644 --- a/src/utils/hooks/useWateringActions.ts +++ b/src/utils/hooks/useWateringActions.ts @@ -1,10 +1,10 @@ import { useState } from 'react'; import { waterTree } from '../requests/waterTree'; import { unwaterTree } from '../requests/unwaterTree'; -import { useAuth0Token } from './useAuth0Token'; import { useCommunityData } from './useCommunityData'; import { useTreeData } from './useTreeData'; import { useUserData } from './useUserData'; +import { useSupabaseToken } from './useSupabaseToken'; export const useWateringActions = ( treeId: string | null | undefined @@ -14,7 +14,7 @@ export const useWateringActions = ( isBeingWatered: boolean; isBeingUnwatered: boolean; } => { - const token = useAuth0Token(); + const token = useSupabaseToken(); const { userData, invalidate: invalidateUserData } = useUserData(); const { invalidate: invalidateCommunityData } = useCommunityData(); const { invalidate: invalidateTreeData } = useTreeData(treeId); From d68c0f0b25b6011e57cef2257ec4f460bfb5c728 Mon Sep 17 00:00:00 2001 From: ff6347 Date: Tue, 21 Mar 2023 17:26:51 +0100 Subject: [PATCH 009/121] feat: Redirect to auth component --- src/components/Login/index.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/Login/index.tsx b/src/components/Login/index.tsx index 510b5ba74..e3d0ceafa 100644 --- a/src/components/Login/index.tsx +++ b/src/components/Login/index.tsx @@ -1,4 +1,6 @@ +// TODO: Make this component work right import React, { FC } from 'react'; +import Link from 'next/link'; import { useAccountActions } from '../../utils/hooks/useAccountActions'; import { useUserData } from '../../utils/hooks/useUserData'; @@ -10,14 +12,14 @@ const Login: FC<{ noLogout?: boolean; }> = ({ width, noLogout }) => { const { userData } = useUserData(); - const { login, logout } = useAccountActions(); + const { logout } = useAccountActions(); return ( <> {!userData && ( - - Konto anlegen / Einloggen - + + Konto anlegen / Einloggen + )} {userData && !noLogout && ( From fb15959eeaa3f4943de8d943f2335f26cce948cd Mon Sep 17 00:00:00 2001 From: ff6347 Date: Tue, 21 Mar 2023 17:27:18 +0100 Subject: [PATCH 010/121] feat: Logged in user can delete their account --- src/utils/requests/deleteAccount.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/utils/requests/deleteAccount.ts b/src/utils/requests/deleteAccount.ts index 38b9ba35b..cc6b3526d 100644 --- a/src/utils/requests/deleteAccount.ts +++ b/src/utils/requests/deleteAccount.ts @@ -2,20 +2,22 @@ import { requests } from '../requestUtil'; export const deleteAccount = async ({ token, - userId, }: { token: string; - userId: string; }): Promise => { try { const res = await requests<{ ok: boolean; text: () => Promise }>( - `${process.env.NEXT_PUBLIC_USER_DATA_API_URL}/api/user?userid=${userId}`, + `${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest/v1/rpc/remove_account`, { token, override: { mode: 'cors', - method: 'DELETE', - headers: { Authorization: `Bearer ${token}` }, + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY, + }, }, } ); From a1e6eb86b031dc40c7c3ad09d3c3f0ccfbd82428 Mon Sep 17 00:00:00 2001 From: ff6347 Date: Tue, 21 Mar 2023 17:27:44 +0100 Subject: [PATCH 011/121] docs: Remoce Auth0 credentials from env.example --- .env.sample | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.env.sample b/.env.sample index 58f952e96..325ee24f9 100644 --- a/.env.sample +++ b/.env.sample @@ -14,21 +14,9 @@ NEXT_PUBLIC_MAPBOX_TREES_TILESET_LAYER={layer-name-within-tileset} # min longitude min latitude max longitude max latitude NEXT_PUBLIC_MAP_BOUNDING_BOX=13.0824446341071,52.3281202651866,13.7682544186827,52.681600197973 -# Auth0 -NEXT_PUBLIC_AUTH0_DOMAIN=myauthzeropath.eu.auth0.com -NEXT_PUBLIC_AUTH0_CLIENT_ID=7479d17d-2212-4a23-a42c-363b898dc618 -NEXT_PUBLIC_AUTH0_AUDIENCE=https://my-tree-api-url.io - -# User management -# Uses the auth0 management API to create the possibility -# to delete users -# see https://github.com/technologiestiftung/tsb-trees-api-user-management -NEXT_PUBLIC_USER_DATA_API_URL=https://my-user-management-api.io - # giessdenkiez API # See https://github.com/technologiestiftung/giessdenkiez-de-postgres-api NEXT_PUBLIC_API_ENDPOINT=https://localhost:8000/my-local-postgres-api # Disable NextJs telemetry NEXT_TELEMETRY_DISABLED=1 - From 4ff63eeaa1042850e44dbb61b57ced09093f1360 Mon Sep 17 00:00:00 2001 From: ff6347 Date: Tue, 21 Mar 2023 17:30:10 +0100 Subject: [PATCH 012/121] docs: Update .env.example with SUPABASE vars --- .env.sample | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.env.sample b/.env.sample index 325ee24f9..20f79aeda 100644 --- a/.env.sample +++ b/.env.sample @@ -16,7 +16,9 @@ NEXT_PUBLIC_MAP_BOUNDING_BOX=13.0824446341071,52.3281202651866,13.7682544186827, # giessdenkiez API # See https://github.com/technologiestiftung/giessdenkiez-de-postgres-api -NEXT_PUBLIC_API_ENDPOINT=https://localhost:8000/my-local-postgres-api +NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321 +NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0 +NEXT_PUBLIC_API_ENDPOINT=https://localhost:8080 # Disable NextJs telemetry NEXT_TELEMETRY_DISABLED=1 From 1f6c7225fe5e9887c8e6c1513bf1a5644f62fd35 Mon Sep 17 00:00:00 2001 From: ff6347 Date: Tue, 21 Mar 2023 17:36:01 +0100 Subject: [PATCH 013/121] fix: Error paste sql function --- src/utils/hooks/useUserData.ts | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/src/utils/hooks/useUserData.ts b/src/utils/hooks/useUserData.ts index 5a5f247f7..c341fa48e 100644 --- a/src/utils/hooks/useUserData.ts +++ b/src/utils/hooks/useUserData.ts @@ -40,31 +40,7 @@ export const useUserData = (): { let userData: UserDataType | undefined; if (user && partialUserData && profile) { -{ - -Create a trigger function that updates the table trees_watered whenever the table column username on the table -profiles changes. - -CREATE TABLE "public"."profiles" ( - "id" uuid NOT NULL, - "username" text -); --- Sequence and defined type -CREATE SEQUENCE IF NOT EXISTS trees_watered_id_seq; - --- Table Definition -CREATE TABLE "public"."trees_watered" ( - "time" text, - "uuid" text, - "amount" numeric NOT NULL CHECK (amount > (0)::numeric), - "timestamp" timestamptz NOT NULL, - "username" text, - "id" int4 NOT NULL DEFAULT nextval('trees_watered_id_seq'::regclass), - "tree_id" text NOT NULL, - CONSTRAINT "fk_trees_watered_trees" FOREIGN KEY ("tree_id") REFERENCES "public"."trees"("id"), - PRIMARY KEY ("id") -); -} userData = { + userData = { id: user.id, email: user.email!, isVerified: user.email_confirmed_at !== undefined ? true : false, From 8124ca02749ce94baa06a155314155f1b40add38 Mon Sep 17 00:00:00 2001 From: ff6347 Date: Tue, 21 Mar 2023 17:51:08 +0100 Subject: [PATCH 014/121] feat: Update username on trees_watered Somehow the trigger function only works on sql console --- pages/auth.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pages/auth.tsx b/pages/auth.tsx index 2c6d77d9d..d1df3415a 100644 --- a/pages/auth.tsx +++ b/pages/auth.tsx @@ -361,6 +361,16 @@ const UpdateUserDataForm = () => { if (data) { console.log('User updated'); } + const { data: data2, error: error2 } = await supabase + .from('trees_watered') + .update({ username: formData.name }) + .eq('uuid', session?.user?.id); + if (error2) { + throw error2; + } + if (data2) { + console.log('User name on trees_watered updated'); + } } } }; From 76230ee21b350e66dc5d90602fce7373dd1fccac Mon Sep 17 00:00:00 2001 From: ff6347 Date: Wed, 22 Mar 2023 11:27:12 +0100 Subject: [PATCH 015/121] chore: Remove unneccesary code The update of the username happens within the DB using triggers on the profile table. It did not work because the RLS did not allow the user to make changes to the trees_watered table on update --- pages/auth.tsx | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/pages/auth.tsx b/pages/auth.tsx index d1df3415a..33348e002 100644 --- a/pages/auth.tsx +++ b/pages/auth.tsx @@ -354,23 +354,15 @@ const UpdateUserDataForm = () => { const { data, error } = await supabase .from('profiles') .update({ username: formData.name }) - .eq('id', session?.user?.id); + .eq('id', session?.user?.id) + .select('*'); if (error) { throw error; } if (data) { + console.log(data); console.log('User updated'); } - const { data: data2, error: error2 } = await supabase - .from('trees_watered') - .update({ username: formData.name }) - .eq('uuid', session?.user?.id); - if (error2) { - throw error2; - } - if (data2) { - console.log('User name on trees_watered updated'); - } } } }; From 57dd738574cd344e51eedf721df7fa24d82329a5 Mon Sep 17 00:00:00 2001 From: ff6347 Date: Wed, 22 Mar 2023 16:55:14 +0100 Subject: [PATCH 016/121] refactor(auth): Split large component Move into smaller ones and add some styling --- pages/auth.tsx | 507 ++---------------- .../ButtonRound/ButtonSubmitRound.tsx | 77 +++ src/components/Sidebar/SidebarAuth/Form.tsx | 124 +++++ .../Sidebar/SidebarAuth/Notification.tsx | 30 ++ .../Sidebar/SidebarAuth/PasswordResetForm.tsx | 61 +++ .../Sidebar/SidebarAuth/SignOut.tsx | 29 + .../Sidebar/SidebarAuth/TextInput.tsx | 17 + .../SidebarAuth/UpdateUserDataForm.tsx | 150 ++++++ src/components/Sidebar/SidebarAuth/index.tsx | 228 ++++++++ 9 files changed, 755 insertions(+), 468 deletions(-) create mode 100644 src/components/ButtonRound/ButtonSubmitRound.tsx create mode 100644 src/components/Sidebar/SidebarAuth/Form.tsx create mode 100644 src/components/Sidebar/SidebarAuth/Notification.tsx create mode 100644 src/components/Sidebar/SidebarAuth/PasswordResetForm.tsx create mode 100644 src/components/Sidebar/SidebarAuth/SignOut.tsx create mode 100644 src/components/Sidebar/SidebarAuth/TextInput.tsx create mode 100644 src/components/Sidebar/SidebarAuth/UpdateUserDataForm.tsx create mode 100644 src/components/Sidebar/SidebarAuth/index.tsx diff --git a/pages/auth.tsx b/pages/auth.tsx index 33348e002..d483d5b0a 100644 --- a/pages/auth.tsx +++ b/pages/auth.tsx @@ -1,467 +1,23 @@ import React, { useEffect, useState } from 'react'; import { MapLayout } from '../src/components/MapLayout'; import { Page } from '../src/nextPage'; -// import { Auth } from '@supabase/auth-ui-react'; import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react'; -import { Database } from '../src/common/database'; -type AuthView = 'signin' | 'signup' | 'recovery' | 'confirm'; - -interface FormData { - email: string; - password: string; -} -const linkStyle = { - textDecoration: 'underline', -}; - -const ErrorNotifiction = ({ message }: { message: string }) => { - return ( -
-

{message}

- -
- ); -}; - -const Auth = () => { - const supabase = useSupabaseClient(); - - const [errorMessage, setErrorMessage] = useState(null); - const [view, setView] = useState('signin'); - const [formData, setFormData] = useState({ - email: '', - password: '', - }); - - const clearFields = () => { - setErrorMessage(null); - setFormData({ - email: '', - password: '', - }); - }; - - const handleSignInSubmit = (event: React.FormEvent) => { - event.preventDefault(); - - signIn(formData.email, formData.password).catch(error => { - console.error(error); - setErrorMessage(error.message); - }); - clearFields(); - }; - const handleSignUpSubmit = (event: React.FormEvent) => { - event.preventDefault(); - - signUp(formData.email, formData.password).catch(error => { - console.error(error); - setErrorMessage(error.message); - }); - clearFields(); - }; - - const handleRecoverySubmit = (event: React.FormEvent) => { - event.preventDefault(); - recovery(formData.email).catch(error => { - console.error(error); - setErrorMessage(error.message); - }); - clearFields(); - }; - - const handleInputChange = (event: React.ChangeEvent) => { - const { name, value } = event.target; - setErrorMessage(null); - setFormData({ - ...formData, - [name]: value, - }); - }; - - let form: JSX.Element | null = null; - let linkText: JSX.Element | null = null; - - const signUp = async (email: string, password: string) => { - const { data, error } = await supabase.auth.signUp({ - email, - password, - }); - if (error) { - if (error.message.includes('User already registered')) { - setErrorMessage('User already registered'); - console.error('User already registered'); - setView('signin'); - return; - } - throw error; - } - if (data.user) { - setErrorMessage('Check your email for the link!'); - // console.log('Autoconfirm is not active'); - setView('confirm'); - } - if (data.session) { - console.log('Session', data.session); - console.log('Autoconfirm is active'); - } - }; - - const signIn = async (email: string, password: string) => { - const { data, error } = await supabase.auth.signInWithPassword({ - email, - password, - }); - if (error) { - setErrorMessage(error.message); - console.error(error); - throw error; - } - if (!data.user) { - setErrorMessage('500 - Internal Server Error'); - throw new Error('No user'); - } - - if (!data.session) { - setErrorMessage('500 - Internal Server Error'); - throw new Error('No session'); - } - }; - - const recovery = async (email: string) => { - let { data, error } = await supabase.auth.resetPasswordForEmail(email, { - redirectTo: 'http://localhost:3000/auth', - }); - if (error) { - setErrorMessage(error.message); - throw error; - } - if (data) { - setErrorMessage('Check your email for the link!'); - } - }; - - switch (view) { - case 'signin': - form = ( -
- - -
- - -
- -
- ); - linkText = ( - <> - {'Du hast noch keinen Account?'}{' '} - setView('signup')} style={linkStyle}> - Registrier dich{' '} - - - ); - break; - case 'signup': - form = ( -
- - -
- - -
- -
- ); - linkText = ( - <> - {'Du hast schon einen Account?'}{' '} - setView('signin')} style={linkStyle}> - Log dich ein{' '} - - - ); - break; - case 'recovery': - form = ( -
- - -
- -
- ); - linkText = ( - <> - {'Zurück zur Anmeldung?'}{' '} - setView('signin')} style={linkStyle}> - Hier klicken{' '} - - - ); - break; - default: - form = null; - linkText = null; - } - - return ( - <> - {form} -
{errorMessage && }
-
-

{linkText}

- {view !== 'recovery' && ( -

- Oh nein. Du hast dein{' '} - setView('recovery')} style={linkStyle}> - Passwort vergessen? - -

- )} -
- - ); -}; - -const ResetPasswordButton = ({ - handleClick, -}: { - handleClick: (e: React.MouseEvent) => void; -}) => { - return ; -}; -const SignOut = () => { - const supabase = useSupabaseClient(); - return ( - <> - {' '} - - ); -}; - -const UpdateUserDataForm = () => { - const supabase = useSupabaseClient(); - const session = useSession(); - const [formData, setFormData] = useState({ - name: '', - email: '', - }); - - useEffect(() => { - if (session) { - const getUserProfile = async () => { - const { data, error } = await supabase - .from('profiles') - .select('id,username') - .eq('id', session?.user?.id) - .single(); - if (error) { - throw error; - } - if (data) { - setFormData({ - name: data.username ?? formData.email.split('@')[0], - email: session.user?.email || '', - }); - } - }; - setFormData({ - name: '', - email: session.user?.email || '', - }); - getUserProfile().catch(console.error); - } - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const handleInputChange = (event: React.ChangeEvent) => { - const { name, value } = event.target; - setFormData({ - ...formData, - [name]: value, - }); - }; - const handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); - - const updateUser = async () => { - if (formData.email !== session?.user?.email) { - const { data, error } = await supabase.auth.updateUser({ - email: formData.email, - }); - if (error) { - throw error; - } - if (data) { - console.log('User updated'); - } - } - const { data, error } = await supabase - .from('profiles') - .select('id,username') - .eq('id', session?.user?.id) - .single(); - if (error) { - throw error; - } - if (data) { - if (data.username !== formData.name) { - const { data, error } = await supabase - .from('profiles') - .update({ username: formData.name }) - .eq('id', session?.user?.id) - .select('*'); - if (error) { - throw error; - } - if (data) { - console.log(data); - console.log('User updated'); - } - } - } - }; - updateUser().catch(console.error); - }; - return ( - <> - {session ? ( - <> - {' '} -
- - -
- - -
- -
- - ) : null} - - ); -}; - -const PasswordResetForm = ({ - additionalSubmitHandler, -}: { - additionalSubmitHandler: () => void; -}) => { - const supabase = useSupabaseClient(); - const session = useSession(); - const [formData, setFormData] = useState({ - password: '', - }); - const handleInputChange = (event: React.ChangeEvent) => { - const { name, value } = event.target; - setFormData({ - ...formData, - [name]: value, - }); - }; - const handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); - additionalSubmitHandler(); - const updatePassword = async () => { - const { data, error } = await supabase.auth.updateUser({ - password: formData.password, - }); - if (error) { - throw error; - } - if (data) { - console.log('Password updated'); - } - }; - updatePassword().catch(console.error); - }; - return ( - <> -
-

Passwort zurücksetzen für {session?.user?.email}

-
-
- - -
- -
- - ); -}; +import { SidebarAuth } from '../src/components/Sidebar/SidebarAuth'; +import { PasswordResetForm } from '../src/components/Sidebar/SidebarAuth/PasswordResetForm'; +import { UpdateUserDataForm } from '../src/components/Sidebar/SidebarAuth/UpdateUserDataForm'; +import { SignOut } from '../src/components/Sidebar/SidebarAuth/SignOut'; +import ButtonRound from '../src/components/ButtonRound'; +import { + StyledFlexContainer, + StyledFormRow, +} from '../src/components/Sidebar/SidebarAuth/Form'; +export type AuthView = 'signin' | 'signup' | 'recovery' | 'confirm'; const AuthPage: Page = () => { const supabase = useSupabaseClient(); const session = useSession(); const [showPasswordResetScreen, setShowPasswordResetScreen] = useState(false); + const [view, setView] = useState('signin'); useEffect(() => { const { @@ -509,6 +65,10 @@ const AuthPage: Page = () => { <> {' '} { + setShowPasswordResetScreen(false); + setView('signin'); + }} additionalSubmitHandler={() => { setShowPasswordResetScreen(false); }} @@ -518,23 +78,34 @@ const AuthPage: Page = () => { } return ( <> -

Auth

{!session ? ( - + ) : ( <> - { - e.preventDefault(); - setShowPasswordResetScreen(true); - }} - /> - - -
-            {JSON.stringify(session, null, 2)}
-          
+ + + { + e?.preventDefault(); + + setShowPasswordResetScreen(true); + }} + > + Passwort ändern? + + + + + + + +
+ Dev Info: Session +
+              {JSON.stringify(session, null, 2)}
+            
+
)} diff --git a/src/components/ButtonRound/ButtonSubmitRound.tsx b/src/components/ButtonRound/ButtonSubmitRound.tsx new file mode 100644 index 000000000..f9ce17f6f --- /dev/null +++ b/src/components/ButtonRound/ButtonSubmitRound.tsx @@ -0,0 +1,77 @@ +import React, { FC } from 'react'; +import styled from 'styled-components'; + +interface StyledButtonProps { + width?: string; + type?: string; + margin?: string; + fontSize?: string; + disabled?: boolean; +} +const StyledButton = styled.button` + width: ${p => (p.width !== undefined ? p.width : '-webkit-fill-available')}; + border-radius: 100px; + background-color: ${p => (p.disabled ? p.theme.colorLightGrey : '#FFFFFF')}; + padding: 12px 15px 12px 15px; + height: fit-content; + margin: ${p => p.margin}; + text-align: center; + cursor: ${p => (p.disabled ? 'default' : 'pointer')}; + font-size: ${p => (p.fontSize ? p.fontSize : p.theme.fontSizeLl)}; + border: 1px solid ${p => p.theme.colorTextDark}; + color: ${p => { + if (p.disabled) { + return p.theme.colorTextLight; + } + // if (p.type === 'submit') { + // return p.theme.colorPrimary; + // } + }}; + transition: ${p => p.theme.transition}, box-shadow 200ms ease-out; + box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); + + &:hover { + background-color: ${p => p.theme.colorTextDark}; + color: white; + transition: ${p => p.theme.transition}; + } + + &:focus { + outline: none; + box-shadow: 0 0 0 2px + ${p => { + if (p.type === 'submit') { + return p.theme.colorPrimary; + } + }}; + } +`; + +const ButtonSubmitRound: FC<{ + type?: 'button' | 'submit' | 'reset'; + width?: string; + fontSize?: string; + margin?: string; + disabled?: boolean; +}> = ({ + type, + children, + width, + fontSize, + margin = '0px', + disabled = false, +}) => ( + + {children} + +); + +export default ButtonSubmitRound; diff --git a/src/components/Sidebar/SidebarAuth/Form.tsx b/src/components/Sidebar/SidebarAuth/Form.tsx new file mode 100644 index 000000000..6bf5281e5 --- /dev/null +++ b/src/components/Sidebar/SidebarAuth/Form.tsx @@ -0,0 +1,124 @@ +import React from 'react'; +import styled from 'styled-components'; +import { CredentialsData } from './index'; +import ButtonSubmitRound from '../../ButtonRound/ButtonSubmitRound'; + +export const StyledH1 = styled.h1` + font-size: 1.5rem; + font-weight: 500; + margin: 0; +`; +export const StyledForm = styled.form` + display: flex; + flex-direction: column; + align-items: center; +`; + +export const StyledFlexContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; +`; +export const StyledFormLabel = styled.label``; + +export const StyledFormRow = styled.div` + display: flex; + flex-direction: column; + align-items: stretch; + width: 100%; + padding: 10px; +`; + +export const StyledFormTextInput = styled.input` + flex: 1; + padding: 10px; + margin-top: 8px; + border-radius: 4px; + border: 1px solid ${p => p.theme.colorTextMedium}; + color: ${p => p.theme.colorTextDark}; + &::selection { + background: ${p => p.theme.colorPrimary}; + color: white; + } + &:focus { + outline: none; + box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.1); + } +`; + +const StyledA = styled.a` + text-decoration: underline; +`; + +export const CredentialsSubline = ({ + text, + aText, + onClick, +}: { + text: string; + aText: string; + onClick: () => void; +}) => { + return ( +

+ {text} +
+ {aText} +

+ ); +}; + +export const CredentialsForm = ({ + formData, + handleInputChange, + handleSubmit, + buttonText, + isRecovery, + isReset, +}: { + formData: CredentialsData; + handleInputChange: (event: React.ChangeEvent) => void; + handleSubmit: (event: React.FormEvent) => void; + buttonText: string; + isRecovery?: boolean; + isReset?: boolean; +}) => { + return ( + <> + { + console.log('submitting'); + handleSubmit(e); + }} + > + {!isReset && ( + + Email + + + )} + {!isRecovery && ( + + Password + + + )} + + {buttonText} + + + + ); +}; diff --git a/src/components/Sidebar/SidebarAuth/Notification.tsx b/src/components/Sidebar/SidebarAuth/Notification.tsx new file mode 100644 index 000000000..16e1afd91 --- /dev/null +++ b/src/components/Sidebar/SidebarAuth/Notification.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import styled from 'styled-components'; + +const StyledSuccessDiv = styled.div` + background: ${p => p.theme.colorPrimary}; + color: ${p => p.theme.colorTextDark}; + padding: 10px; + border-radius: 5px; +`; +const StyledErrorDiv = styled.div` + background: ${p => p.theme.colorAlarm}}; + color: ${p => p.theme.colorTextDark}; + padding: 10px; + border-radius: 5px; +`; +export const UserNotification = ({ + message, + type = 'error', +}: { + message: string; + type?: 'success' | 'error'; +}) => { + if (type === 'success') return {message}; + + return ( + +

{message}

+
+ ); +}; diff --git a/src/components/Sidebar/SidebarAuth/PasswordResetForm.tsx b/src/components/Sidebar/SidebarAuth/PasswordResetForm.tsx new file mode 100644 index 000000000..a18a60380 --- /dev/null +++ b/src/components/Sidebar/SidebarAuth/PasswordResetForm.tsx @@ -0,0 +1,61 @@ +import { useSupabaseClient, useSession } from '@supabase/auth-helpers-react'; +import React, { useState } from 'react'; +import SmallParagraph from '../../SmallParagraph'; +import SidebarTitle from '../SidebarTitle'; +import { CredentialsForm, CredentialsSubline } from './Form'; + +export const PasswordResetForm = ({ + additionalSubmitHandler, + returnClickHandler, +}: { + additionalSubmitHandler: () => void; + returnClickHandler: () => void; +}) => { + const supabase = useSupabaseClient(); + const session = useSession(); + const [formData, setFormData] = useState({ + password: '', + email: '', + }); + const handleInputChange = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setFormData({ + ...formData, + [name]: value, + }); + }; + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + additionalSubmitHandler(); + const updatePassword = async () => { + const { data, error } = await supabase.auth.updateUser({ + password: formData.password, + }); + if (error) { + throw error; + } + if (data) { + console.log('Password updated'); + } + }; + updatePassword().catch(console.error); + }; + return ( + <> + Passwort ändern? + für {session?.user?.email} + + + + ); +}; diff --git a/src/components/Sidebar/SidebarAuth/SignOut.tsx b/src/components/Sidebar/SidebarAuth/SignOut.tsx new file mode 100644 index 000000000..382b73657 --- /dev/null +++ b/src/components/Sidebar/SidebarAuth/SignOut.tsx @@ -0,0 +1,29 @@ +import { useSupabaseClient } from '@supabase/auth-helpers-react'; +import Router from 'next/router'; +import React from 'react'; +import { AuthView } from '../../../../pages/auth'; +import ButtonRound from '../../ButtonRound'; +export const SignOut = ({ + setView, +}: { + setView: React.Dispatch>; +}) => { + const supabase = useSupabaseClient(); + return ( + <> + { + e?.preventDefault(); + supabase.auth.signOut().then(({ error }) => { + if (error) throw error; + console.log('Signed out'); + Router.push('/'); + setView('signin'); + }); + }} + > + Ausloggen + + + ); +}; diff --git a/src/components/Sidebar/SidebarAuth/TextInput.tsx b/src/components/Sidebar/SidebarAuth/TextInput.tsx new file mode 100644 index 000000000..9cbded05f --- /dev/null +++ b/src/components/Sidebar/SidebarAuth/TextInput.tsx @@ -0,0 +1,17 @@ +import styled from 'styled-components'; + +export const TextInput = styled.input` + padding: 10px; + margin-top: 8px; + border-radius: 4px; + border: 1px solid ${p => p.theme.colorTextMedium}; + color: ${p => p.theme.colorTextDark}; + &::selection { + background: ${p => p.theme.colorPrimary}; + color: white; + } + &:focus { + outline: none; + box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.1); + } +`; diff --git a/src/components/Sidebar/SidebarAuth/UpdateUserDataForm.tsx b/src/components/Sidebar/SidebarAuth/UpdateUserDataForm.tsx new file mode 100644 index 000000000..48ff1edb1 --- /dev/null +++ b/src/components/Sidebar/SidebarAuth/UpdateUserDataForm.tsx @@ -0,0 +1,150 @@ +import { useSupabaseClient, useSession } from '@supabase/auth-helpers-react'; +import React, { useState, useEffect } from 'react'; +import { Database } from '../../../common/database'; +import ButtonSubmitRound from '../../ButtonRound/ButtonSubmitRound'; +import SidebarTitle from '../SidebarTitle'; +import { + StyledForm, + StyledFormLabel, + StyledFormRow, + StyledFormTextInput, +} from './Form'; +import { UserNotification } from './Notification'; + +export const UpdateUserDataForm = () => { + const [message, setMessage] = useState(null); + const [errorMessage, setErrorMessage] = useState(null); + const supabase = useSupabaseClient(); + const session = useSession(); + const [formData, setFormData] = useState({ + name: '', + email: '', + }); + + useEffect(() => { + if (session) { + const getUserProfile = async () => { + const { data, error } = await supabase + .from('profiles') + .select('id,username') + .eq('id', session?.user?.id) + .single(); + if (error) { + setErrorMessage(error.message); + throw error; + } + if (data) { + setFormData({ + name: data.username ?? formData.email.split('@')[0], + email: session.user?.email || '', + }); + } + }; + setFormData({ + name: '', + email: session.user?.email || '', + }); + getUserProfile().catch(console.error); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleInputChange = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setFormData({ + ...formData, + [name]: value, + }); + }; + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + console.log('Update'); + + const updateUser = async () => { + if (formData.name.length < 3) { + setErrorMessage('Der Benutzername muss mindestens 3 Zeichen lang sein'); + return; + } + if (formData.email !== session?.user?.email) { + const { data, error } = await supabase.auth.updateUser({ + email: formData.email, + }); + if (error) { + setErrorMessage(error.message); + throw error; + } + if (data) { + setMessage('E-Mail geändert'); + } + } + const { data, error } = await supabase + .from('profiles') + .select('id,username') + .eq('id', session?.user?.id) + .single(); + if (error) { + setErrorMessage(error.message); + throw error; + } + if (data) { + if (data.username !== formData.name) { + const { data, error } = await supabase + .from('profiles') + .update({ username: formData.name }) + .eq('id', session?.user?.id) + .select(); + if (error) { + setErrorMessage(error.message); + throw error; + } + if (data) { + setMessage('Benutzername geändert'); + } + } + } + }; + updateUser().catch(console.error); + }; + return ( + <> + {session ? ( + <> + Account bearbeiten + + + + Name + + + + E-Mail + + + + + Update Account + + + + {message && } + {errorMessage && ( + + )} + + ) : null} + + ); +}; diff --git a/src/components/Sidebar/SidebarAuth/index.tsx b/src/components/Sidebar/SidebarAuth/index.tsx new file mode 100644 index 000000000..e5ff122c0 --- /dev/null +++ b/src/components/Sidebar/SidebarAuth/index.tsx @@ -0,0 +1,228 @@ +import { useSupabaseClient } from '@supabase/auth-helpers-react'; +import React, { useState } from 'react'; +import { AuthView } from '../../../../pages/auth'; +import SidebarTitle from '../SidebarTitle'; +import { UserNotification } from './Notification'; +import { CredentialsForm, CredentialsSubline } from './Form'; +export interface CredentialsData { + email: string; + password: string; +} +const linkStyle = { + textDecoration: 'underline', +}; + +enum titles { + signin = 'Anmelden', + signup = 'Registrieren', + recovery = 'Passwort vergessen', + confirm = 'Account Bestätigen', + change = 'Passwort ändern', +} + +export const SidebarAuth = ({ + view, + setView, +}: { + view: AuthView; + setView: React.Dispatch>; +}) => { + const supabase = useSupabaseClient(); + + const [errorMessage, setErrorMessage] = useState(null); + // const [view, setView] = useState('signin'); + const [formData, setFormData] = useState({ + email: '', + password: '', + }); + + const clearFields = () => { + setErrorMessage(null); + setFormData({ + email: '', + password: '', + }); + }; + + const handleSignInSubmit = (event: React.FormEvent) => { + event.preventDefault(); + console.log('signin'); + + signIn(formData.email, formData.password).catch(error => { + console.error(error); + setErrorMessage(error.message); + }); + clearFields(); + }; + const handleSignUpSubmit = (event: React.FormEvent) => { + event.preventDefault(); + console.log('signup'); + signUp(formData.email, formData.password).catch(error => { + console.error(error); + setErrorMessage(error.message); + }); + clearFields(); + }; + + const handleRecoverySubmit = (event: React.FormEvent) => { + event.preventDefault(); + console.log('recovery'); + recovery(formData.email).catch(error => { + console.error(error); + setErrorMessage(error.message); + }); + clearFields(); + }; + + const handleInputChange = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setErrorMessage(null); + setFormData({ + ...formData, + [name]: value, + }); + }; + + let form: JSX.Element | null = null; + let linkText: JSX.Element | null = null; + + const signUp = async (email: string, password: string) => { + const { data, error } = await supabase.auth.signUp({ + email, + password, + }); + if (error) { + if (error.message.includes('User already registered')) { + setErrorMessage('User already registered'); + console.error('User already registered'); + setView('signin'); + return; + } + throw error; + } + if (data.user) { + setErrorMessage('Check your email for the link!'); + // console.log('Autoconfirm is not active'); + setView('confirm'); + } + if (data.session) { + console.log('Session', data.session); + console.log('Autoconfirm is active'); + } + }; + + const signIn = async (email: string, password: string) => { + const { data, error } = await supabase.auth.signInWithPassword({ + email, + password, + }); + if (error) { + setErrorMessage(error.message); + console.error(error); + throw error; + } + if (!data.user) { + setErrorMessage('500 - Internal Server Error'); + throw new Error('No user'); + } + + if (!data.session) { + setErrorMessage('500 - Internal Server Error'); + throw new Error('No session'); + } + }; + + const recovery = async (email: string) => { + let { data, error } = await supabase.auth.resetPasswordForEmail(email, { + redirectTo: 'http://localhost:3000/auth', + }); + if (error) { + setErrorMessage(error.message); + throw error; + } + if (data) { + setErrorMessage('Check your email for the link!'); + } + }; + + switch (view) { + case 'signin': { + form = ( + + ); + linkText = ( + setView('signup')} + /> + ); + break; + } + case 'signup': { + form = ( + + ); + linkText = ( + setView('signin')} + /> + ); + + break; + } + case 'recovery': { + form = ( + + ); + linkText = ( + setView('signin')} + /> + ); + break; + } + + default: + form = null; + linkText = null; + } + + return ( + <> + {titles[view]} + {form} +
{errorMessage && }
+
+

{linkText}

+ {view !== 'recovery' && ( + setView('recovery')} + /> + )} +
+ + ); +}; From be534c2d491fbd80522d926acb72fa6f8a39fc77 Mon Sep 17 00:00:00 2001 From: ff6347 Date: Wed, 22 Mar 2023 16:55:35 +0100 Subject: [PATCH 017/121] refactor: Housekeeping --- src/utils/hooks/useSupabaseUser.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/utils/hooks/useSupabaseUser.ts b/src/utils/hooks/useSupabaseUser.ts index 59e44b241..f609df819 100644 --- a/src/utils/hooks/useSupabaseUser.ts +++ b/src/utils/hooks/useSupabaseUser.ts @@ -18,9 +18,7 @@ export const useSupabaseUser = () => { data: { user }, } = await supabase.auth.getUser(); - // const user = session.user; setUser(user ?? undefined); - console.log(user, 'user'); return user; } catch (err) { console.error(err); From 9729987e5211f0d40e21651bf5642cbaf8bc0860 Mon Sep 17 00:00:00 2001 From: ff6347 Date: Wed, 22 Mar 2023 16:55:57 +0100 Subject: [PATCH 018/121] refactor: Remove auth0 context --- src/Providers/index.tsx | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/Providers/index.tsx b/src/Providers/index.tsx index 9d04a6e08..f3d81165e 100644 --- a/src/Providers/index.tsx +++ b/src/Providers/index.tsx @@ -4,7 +4,6 @@ import { ReactQueryDevtools } from 'react-query/devtools'; import { ThemeProvider } from 'styled-components'; import { Provider } from 'unistore/react'; -import { Auth0Provider } from '@auth0/auth0-react'; import GlobalStyles from '../assets/Global'; import { theme } from '../assets/theme'; import ErrorBoundary from '../ErrorBoundary'; @@ -36,24 +35,15 @@ export const Providers: FC = ({ children }) => { supabaseClient={supabaseClient} initialSession={session} > - - - - - - - {children} - - - - + + + + + + {children} + + + ); From 27d3aa9c60319bfd4620d888d209fee8469973ea Mon Sep 17 00:00:00 2001 From: ff6347 Date: Wed, 22 Mar 2023 16:56:21 +0100 Subject: [PATCH 019/121] fix: Small error. Import React --- src/components/NumberInput/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/NumberInput/index.tsx b/src/components/NumberInput/index.tsx index 0cca9bb2a..68b7af121 100644 --- a/src/components/NumberInput/index.tsx +++ b/src/components/NumberInput/index.tsx @@ -1,4 +1,4 @@ -import { ChangeEvent, FC } from 'react'; +import React, { ChangeEvent, FC } from 'react'; import styled from 'styled-components'; const NumberInputContainer = styled.div` From 901e2158a24d625525e578afa260a595f9d6c96e Mon Sep 17 00:00:00 2001 From: ff6347 Date: Wed, 22 Mar 2023 16:57:03 +0100 Subject: [PATCH 020/121] refactor: Remove auth0 context --- src/components/Sidebar/SidebarProfile/index.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/components/Sidebar/SidebarProfile/index.tsx b/src/components/Sidebar/SidebarProfile/index.tsx index 704172a2a..12d14587f 100644 --- a/src/components/Sidebar/SidebarProfile/index.tsx +++ b/src/components/Sidebar/SidebarProfile/index.tsx @@ -44,19 +44,16 @@ const SidebarProfile: FC<{ }> = ({ userData: userDataProps, isLoading: isLoadingProps }) => { const { userData: userDataState } = useUserData(); const { deleteAccount } = useAccountActions(); - const { isLoading: isLoadingState, isAuthenticated } = useAuth0(); const userData = userDataProps || userDataState || false; - const isLoadingAuthInfo = isAuthenticated && !userData; - const isLoading = isLoadingProps || isLoadingState || isLoadingAuthInfo; const handleDeleteClick = (): void => { if (!confirmAccountDeletion()) return; void deleteAccount(); }; - if (isLoading) { - return ; - } + // if (userData === false) { + // return ; + // } if (!userData) { return ( @@ -103,7 +100,7 @@ const SidebarProfile: FC<{ Möchtest du deinen Account löschen? Damit werden alle von dir generierten Wässerungsdaten einem anonymen Benutzer zugeordnet. Dein - Benutzer bei unserem Authentifizierungsdienst Auth0.com wird sofort + Benutzer bei unserem Authentifizierungsdienst Supabase.com wird sofort und unwiderruflich gelöscht. Date: Wed, 22 Mar 2023 16:57:23 +0100 Subject: [PATCH 021/121] feat: Add link to auth section --- src/components/UserCredentials/index.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/UserCredentials/index.tsx b/src/components/UserCredentials/index.tsx index 680c438ea..a1c26831a 100644 --- a/src/components/UserCredentials/index.tsx +++ b/src/components/UserCredentials/index.tsx @@ -1,3 +1,4 @@ +import Link from 'next/link'; import React, { FC } from 'react'; import styled from 'styled-components'; import ExpandablePanel from '../ExpandablePanel'; @@ -17,6 +18,9 @@ const CardCredentials: FC<{ {username} {email} Registrierte E-Mail Adresse + + Account bearbeiten? + ); From 066de9d917b4d9417e04aa48eeec6de54a5e8ad1 Mon Sep 17 00:00:00 2001 From: ff6347 Date: Wed, 22 Mar 2023 16:57:45 +0100 Subject: [PATCH 022/121] refactor: Remove loading state. Was bound to auth0 --- src/components/Sidebar/SidebarProfile/index.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/components/Sidebar/SidebarProfile/index.tsx b/src/components/Sidebar/SidebarProfile/index.tsx index 12d14587f..af990d703 100644 --- a/src/components/Sidebar/SidebarProfile/index.tsx +++ b/src/components/Sidebar/SidebarProfile/index.tsx @@ -1,6 +1,5 @@ import React, { FC } from 'react'; import styled from 'styled-components'; -import { useAuth0 } from '@auth0/auth0-react'; import { useUserData } from '../../../utils/hooks/useUserData'; import Paragraph from '../../Paragraph'; @@ -15,7 +14,6 @@ import SidebarTitle from '../SidebarTitle/'; import { ParticipateButton } from '../../ParticipateButton'; import { useAccountActions } from '../../../utils/hooks/useAccountActions'; import { StyledComponentType, UserDataType } from '../../../common/interfaces'; -import { SidebarLoading } from '../SidebarLoading'; const LastButtonRound = styled(ButtonRound)` margin-bottom: 20px !important; @@ -50,11 +48,6 @@ const SidebarProfile: FC<{ if (!confirmAccountDeletion()) return; void deleteAccount(); }; - - // if (userData === false) { - // return ; - // } - if (!userData) { return ( <> From e3a9fccec6390ca5859b193d4d89e43ad2a08ade Mon Sep 17 00:00:00 2001 From: ff6347 Date: Wed, 22 Mar 2023 21:58:01 +0100 Subject: [PATCH 023/121] feat(Notifications): Make notifictions centralized --- pages/auth.tsx | 69 +++++++++--- .../Sidebar/SidebarAuth/Notification.tsx | 35 +++--- .../Sidebar/SidebarAuth/PasswordResetForm.tsx | 16 ++- .../Sidebar/SidebarAuth/TextInput.tsx | 17 --- .../SidebarAuth/UpdateUserDataForm.tsx | 55 ++++++--- src/components/Sidebar/SidebarAuth/index.tsx | 106 ++++++++++++------ 6 files changed, 197 insertions(+), 101 deletions(-) delete mode 100644 src/components/Sidebar/SidebarAuth/TextInput.tsx diff --git a/pages/auth.tsx b/pages/auth.tsx index d483d5b0a..56129e30e 100644 --- a/pages/auth.tsx +++ b/pages/auth.tsx @@ -11,6 +11,10 @@ import { StyledFlexContainer, StyledFormRow, } from '../src/components/Sidebar/SidebarAuth/Form'; +import { + UserNotification, + UserNotificationObjectType, +} from '../src/components/Sidebar/SidebarAuth/Notification'; export type AuthView = 'signin' | 'signup' | 'recovery' | 'confirm'; const AuthPage: Page = () => { @@ -19,6 +23,17 @@ const AuthPage: Page = () => { const [showPasswordResetScreen, setShowPasswordResetScreen] = useState(false); const [view, setView] = useState('signin'); + const [ + currentNotification, + setCurrentNotification, + ] = useState(null); + + useEffect(() => { + const timer = setTimeout(() => { + setCurrentNotification(null); + }, 5000); + return () => clearTimeout(timer); + }, [currentNotification]); useEffect(() => { const { data: { subscription: authListener }, @@ -57,14 +72,15 @@ const AuthPage: Page = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - useEffect(() => { - console.log('Session', session); - }, [session]); + // useEffect(() => { + // console.log('Session', session); + // }, [session]); if (showPasswordResetScreen) { return ( <> {' '} { setShowPasswordResetScreen(false); setView('signin'); @@ -77,12 +93,21 @@ const AuthPage: Page = () => { ); } return ( - <> +
{!session ? ( - + ) : ( <> - + { Passwort ändern? + + {process.env.NODE_ENV !== 'production' && ( +
+ Dev Info: Session +
+                    {JSON.stringify(session, null, 2)}
+                  
+
+ )} +
+
- -
- Dev Info: Session -
-              {JSON.stringify(session, null, 2)}
-            
-
)} - + + + {currentNotification && ( + + )} + + +
); }; AuthPage.layout = MapLayout; diff --git a/src/components/Sidebar/SidebarAuth/Notification.tsx b/src/components/Sidebar/SidebarAuth/Notification.tsx index 16e1afd91..f3ef60590 100644 --- a/src/components/Sidebar/SidebarAuth/Notification.tsx +++ b/src/components/Sidebar/SidebarAuth/Notification.tsx @@ -1,30 +1,27 @@ import React from 'react'; import styled from 'styled-components'; -const StyledSuccessDiv = styled.div` - background: ${p => p.theme.colorPrimary}; - color: ${p => p.theme.colorTextDark}; - padding: 10px; - border-radius: 5px; -`; -const StyledErrorDiv = styled.div` - background: ${p => p.theme.colorAlarm}}; +export type UserNotificationObjectType = { + message: string; + type: UserNotificationType; +}; +export type UserNotificationType = 'success' | 'error'; + +interface StyledMessageProps { + type: UserNotificationType; +} +const StyledDiv = styled.div` + background: ${p => + p.type === 'success' ? p.theme.colorPrimary : p.theme.colorAlarm}; color: ${p => p.theme.colorTextDark}; padding: 10px; border-radius: 5px; `; + export const UserNotification = ({ message, - type = 'error', + type, }: { message: string; - type?: 'success' | 'error'; -}) => { - if (type === 'success') return {message}; - - return ( - -

{message}

-
- ); -}; + type: UserNotificationType; +}) => {message}; diff --git a/src/components/Sidebar/SidebarAuth/PasswordResetForm.tsx b/src/components/Sidebar/SidebarAuth/PasswordResetForm.tsx index a18a60380..0969d988a 100644 --- a/src/components/Sidebar/SidebarAuth/PasswordResetForm.tsx +++ b/src/components/Sidebar/SidebarAuth/PasswordResetForm.tsx @@ -3,13 +3,18 @@ import React, { useState } from 'react'; import SmallParagraph from '../../SmallParagraph'; import SidebarTitle from '../SidebarTitle'; import { CredentialsForm, CredentialsSubline } from './Form'; +import { UserNotificationObjectType } from './Notification'; export const PasswordResetForm = ({ additionalSubmitHandler, returnClickHandler, + setNotification, }: { additionalSubmitHandler: () => void; returnClickHandler: () => void; + setNotification: React.Dispatch< + React.SetStateAction + >; }) => { const supabase = useSupabaseClient(); const session = useSession(); @@ -32,10 +37,17 @@ export const PasswordResetForm = ({ password: formData.password, }); if (error) { - throw error; + setNotification({ + message: error.message, + type: 'error', + }); + console.log('Error updating user:', error.message); } if (data) { - console.log('Password updated'); + setNotification({ + message: 'Passwort erfolgreich geändert', + type: 'success', + }); } }; updatePassword().catch(console.error); diff --git a/src/components/Sidebar/SidebarAuth/TextInput.tsx b/src/components/Sidebar/SidebarAuth/TextInput.tsx deleted file mode 100644 index 9cbded05f..000000000 --- a/src/components/Sidebar/SidebarAuth/TextInput.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import styled from 'styled-components'; - -export const TextInput = styled.input` - padding: 10px; - margin-top: 8px; - border-radius: 4px; - border: 1px solid ${p => p.theme.colorTextMedium}; - color: ${p => p.theme.colorTextDark}; - &::selection { - background: ${p => p.theme.colorPrimary}; - color: white; - } - &:focus { - outline: none; - box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.1); - } -`; diff --git a/src/components/Sidebar/SidebarAuth/UpdateUserDataForm.tsx b/src/components/Sidebar/SidebarAuth/UpdateUserDataForm.tsx index 48ff1edb1..bbf6c0a88 100644 --- a/src/components/Sidebar/SidebarAuth/UpdateUserDataForm.tsx +++ b/src/components/Sidebar/SidebarAuth/UpdateUserDataForm.tsx @@ -9,11 +9,17 @@ import { StyledFormRow, StyledFormTextInput, } from './Form'; -import { UserNotification } from './Notification'; +import { UserNotificationObjectType } from './Notification'; -export const UpdateUserDataForm = () => { - const [message, setMessage] = useState(null); - const [errorMessage, setErrorMessage] = useState(null); +export const UpdateUserDataForm = ({ + setNotification, +}: { + setNotification: React.Dispatch< + React.SetStateAction + >; +}) => { + // const [message, setMessage] = useState(null); + // const [errorMessage, setErrorMessage] = useState(null); const supabase = useSupabaseClient(); const session = useSession(); const [formData, setFormData] = useState({ @@ -30,7 +36,11 @@ export const UpdateUserDataForm = () => { .eq('id', session?.user?.id) .single(); if (error) { - setErrorMessage(error.message); + setNotification({ + message: error.message, + type: 'error', + }); + throw error; } if (data) { @@ -63,7 +73,10 @@ export const UpdateUserDataForm = () => { const updateUser = async () => { if (formData.name.length < 3) { - setErrorMessage('Der Benutzername muss mindestens 3 Zeichen lang sein'); + setNotification({ + message: 'Der Benutzername muss mindestens 3 Zeichen lang sein', + type: 'error', + }); return; } if (formData.email !== session?.user?.email) { @@ -71,11 +84,18 @@ export const UpdateUserDataForm = () => { email: formData.email, }); if (error) { - setErrorMessage(error.message); + setNotification({ + message: error.message, + type: 'error', + }); throw error; } if (data) { - setMessage('E-Mail geändert'); + setNotification({ + message: + 'E-Mail wurde geändert. Bitte bestätigen Sie die Änderung über den Link in der E-Mail, die wir Ihnen geschickt haben. Sie werden automatisch ausgeloggt.', + type: 'success', + }); } } const { data, error } = await supabase @@ -84,7 +104,10 @@ export const UpdateUserDataForm = () => { .eq('id', session?.user?.id) .single(); if (error) { - setErrorMessage(error.message); + setNotification({ + message: error.message, + type: 'error', + }); throw error; } if (data) { @@ -95,11 +118,17 @@ export const UpdateUserDataForm = () => { .eq('id', session?.user?.id) .select(); if (error) { - setErrorMessage(error.message); + setNotification({ + message: error.message, + type: 'error', + }); throw error; } if (data) { - setMessage('Benutzername geändert'); + setNotification({ + message: 'Benutzername geändert', + type: 'success', + }); } } } @@ -139,10 +168,6 @@ export const UpdateUserDataForm = () => { - {message && } - {errorMessage && ( - - )} ) : null} diff --git a/src/components/Sidebar/SidebarAuth/index.tsx b/src/components/Sidebar/SidebarAuth/index.tsx index e5ff122c0..c9e5b47de 100644 --- a/src/components/Sidebar/SidebarAuth/index.tsx +++ b/src/components/Sidebar/SidebarAuth/index.tsx @@ -2,15 +2,12 @@ import { useSupabaseClient } from '@supabase/auth-helpers-react'; import React, { useState } from 'react'; import { AuthView } from '../../../../pages/auth'; import SidebarTitle from '../SidebarTitle'; -import { UserNotification } from './Notification'; +import { UserNotificationObjectType } from './Notification'; import { CredentialsForm, CredentialsSubline } from './Form'; export interface CredentialsData { email: string; password: string; } -const linkStyle = { - textDecoration: 'underline', -}; enum titles { signin = 'Anmelden', @@ -23,21 +20,22 @@ enum titles { export const SidebarAuth = ({ view, setView, + setNotification, }: { + setNotification: React.Dispatch< + React.SetStateAction + >; view: AuthView; setView: React.Dispatch>; }) => { const supabase = useSupabaseClient(); - const [errorMessage, setErrorMessage] = useState(null); - // const [view, setView] = useState('signin'); const [formData, setFormData] = useState({ email: '', password: '', }); const clearFields = () => { - setErrorMessage(null); setFormData({ email: '', password: '', @@ -49,8 +47,11 @@ export const SidebarAuth = ({ console.log('signin'); signIn(formData.email, formData.password).catch(error => { - console.error(error); - setErrorMessage(error.message); + console.error(error, 'here we are'); + setNotification({ + message: error.message, + type: 'error', + }); }); clearFields(); }; @@ -59,7 +60,10 @@ export const SidebarAuth = ({ console.log('signup'); signUp(formData.email, formData.password).catch(error => { console.error(error); - setErrorMessage(error.message); + setNotification({ + message: error.message, + type: 'error', + }); }); clearFields(); }; @@ -69,14 +73,18 @@ export const SidebarAuth = ({ console.log('recovery'); recovery(formData.email).catch(error => { console.error(error); - setErrorMessage(error.message); + setNotification({ + message: error.message, + type: 'error', + }); }); clearFields(); }; const handleInputChange = (event: React.ChangeEvent) => { const { name, value } = event.target; - setErrorMessage(null); + setNotification(null); + setFormData({ ...formData, [name]: value, @@ -93,7 +101,10 @@ export const SidebarAuth = ({ }); if (error) { if (error.message.includes('User already registered')) { - setErrorMessage('User already registered'); + setNotification({ + message: 'Benutzer bereits registriert', + type: 'error', + }); console.error('User already registered'); setView('signin'); return; @@ -101,7 +112,11 @@ export const SidebarAuth = ({ throw error; } if (data.user) { - setErrorMessage('Check your email for the link!'); + setNotification({ + message: + 'Überprüfe deine E-Mails nach einem Link um deinen Account zu bestätigen', + type: 'success', + }); // console.log('Autoconfirm is not active'); setView('confirm'); } @@ -112,36 +127,60 @@ export const SidebarAuth = ({ }; const signIn = async (email: string, password: string) => { - const { data, error } = await supabase.auth.signInWithPassword({ - email, - password, - }); - if (error) { - setErrorMessage(error.message); - console.error(error); - throw error; - } - if (!data.user) { - setErrorMessage('500 - Internal Server Error'); - throw new Error('No user'); - } + try { + const { data, error } = await supabase.auth.signInWithPassword({ + email, + password, + }); + if (error) { + setNotification({ + message: error.message, + type: 'error', + }); + console.error(error); + return; + } + if (!data.user) { + setNotification({ + message: '500 - Interner Server Error', + type: 'error', + }); - if (!data.session) { - setErrorMessage('500 - Internal Server Error'); - throw new Error('No session'); + console.error('No user'); + return; + } + + if (!data.session) { + setNotification({ + message: '500 - Interner Server Error', + type: 'error', + }); + console.error('No session'); + return; + } + } catch (error) { + console.error(error, 'in catch'); } }; const recovery = async (email: string) => { let { data, error } = await supabase.auth.resetPasswordForEmail(email, { - redirectTo: 'http://localhost:3000/auth', + redirectTo: `${process.env.NEXT_PUBLIC_BASE_URL}/auth`, }); if (error) { - setErrorMessage(error.message); + setNotification({ + message: error.message, + type: 'error', + }); + throw error; } if (data) { - setErrorMessage('Check your email for the link!'); + setNotification({ + message: + 'Überprüfe deine E-Mails nach einem Link um dein Passwort zu ändern', + type: 'success', + }); } }; @@ -212,7 +251,6 @@ export const SidebarAuth = ({ <> {titles[view]} {form} -
{errorMessage && }

{linkText}

{view !== 'recovery' && ( From b30c91561d1f915e7c53ece457c13006878c84b8 Mon Sep 17 00:00:00 2001 From: ff6347 Date: Thu, 23 Mar 2023 08:09:35 +0100 Subject: [PATCH 024/121] fix(SidebarProfile): Loading state @vogelino was right. I was wrong. --- src/components/Sidebar/SidebarProfile/index.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/Sidebar/SidebarProfile/index.tsx b/src/components/Sidebar/SidebarProfile/index.tsx index af990d703..4c435c7fe 100644 --- a/src/components/Sidebar/SidebarProfile/index.tsx +++ b/src/components/Sidebar/SidebarProfile/index.tsx @@ -14,7 +14,8 @@ import SidebarTitle from '../SidebarTitle/'; import { ParticipateButton } from '../../ParticipateButton'; import { useAccountActions } from '../../../utils/hooks/useAccountActions'; import { StyledComponentType, UserDataType } from '../../../common/interfaces'; - +import { useSessionContext } from '@supabase/auth-helpers-react'; +import { SidebarLoading } from '../SidebarLoading'; const LastButtonRound = styled(ButtonRound)` margin-bottom: 20px !important; `; @@ -39,15 +40,23 @@ Alle deine Benutzerdaten werden damit sofort gelöscht!` const SidebarProfile: FC<{ isLoading?: boolean; userData?: UserDataType | undefined; -}> = ({ userData: userDataProps, isLoading: isLoadingProps }) => { +}> = ({ isLoading: isLoadingProps }) => { const { userData: userDataState } = useUserData(); const { deleteAccount } = useAccountActions(); - const userData = userDataProps || userDataState || false; + const userData = userDataState ?? false; + const { isLoading: isLoadingSupase, session } = useSessionContext(); + const isAuthenticated = session?.user?.id ? true : false; + const isLoadingAuthInfo = isAuthenticated && !userData; + const isLoading = isLoadingProps || isLoadingSupase || isLoadingAuthInfo; const handleDeleteClick = (): void => { if (!confirmAccountDeletion()) return; void deleteAccount(); }; + // TODO: get loading state right + if (isLoading) { + return ; + } if (!userData) { return ( <> From 8cb9190b81076c04049cb5b09a7dbe1b4273c4ac Mon Sep 17 00:00:00 2001 From: ff6347 Date: Thu, 23 Mar 2023 08:16:56 +0100 Subject: [PATCH 025/121] feat(Signin): Send user to profile after signin --- src/components/Sidebar/SidebarAuth/index.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/Sidebar/SidebarAuth/index.tsx b/src/components/Sidebar/SidebarAuth/index.tsx index c9e5b47de..ce909d7ed 100644 --- a/src/components/Sidebar/SidebarAuth/index.tsx +++ b/src/components/Sidebar/SidebarAuth/index.tsx @@ -4,6 +4,7 @@ import { AuthView } from '../../../../pages/auth'; import SidebarTitle from '../SidebarTitle'; import { UserNotificationObjectType } from './Notification'; import { CredentialsForm, CredentialsSubline } from './Form'; +import Router from 'next/router'; export interface CredentialsData { email: string; password: string; @@ -158,6 +159,9 @@ export const SidebarAuth = ({ console.error('No session'); return; } + if (data.session) { + Router.push('/profile'); + } } catch (error) { console.error(error, 'in catch'); } From 1dc21f35213160e69cc4fe2f724e9e163992c2b4 Mon Sep 17 00:00:00 2001 From: ff6347 Date: Thu, 23 Mar 2023 08:17:08 +0100 Subject: [PATCH 026/121] chore: Housekeeping --- pages/auth.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/pages/auth.tsx b/pages/auth.tsx index 56129e30e..e3701736b 100644 --- a/pages/auth.tsx +++ b/pages/auth.tsx @@ -72,9 +72,6 @@ const AuthPage: Page = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - // useEffect(() => { - // console.log('Session', session); - // }, [session]); if (showPasswordResetScreen) { return ( <> From aceff6e3a837d0a3f43bbabb5bcfba4b154483d0 Mon Sep 17 00:00:00 2001 From: ff6347 Date: Thu, 23 Mar 2023 08:26:13 +0100 Subject: [PATCH 027/121] chore: Housekeeping remove auth0 related stuff --- docs/users-stats/.editorconfig | 8 - docs/users-stats/.env.example | 8 - docs/users-stats/.eslintignore | 2 - docs/users-stats/.gitignore | 3 - docs/users-stats/.vscode/settings.json | 26 - docs/users-stats/README.md | 37 - docs/users-stats/explorer.ts | 61 - docs/users-stats/get_token.ts | 44 - docs/users-stats/global.d.ts | 3413 ------------------------ docs/users-stats/mod.ts | 3 - docs/users-stats/package-lock.json | 18 - docs/users-stats/package.json | 8 - docs/users-stats/scripts.yml | 3 - docs/users-stats/tsconfig.json | 20 - 14 files changed, 3654 deletions(-) delete mode 100644 docs/users-stats/.editorconfig delete mode 100644 docs/users-stats/.env.example delete mode 100644 docs/users-stats/.eslintignore delete mode 100644 docs/users-stats/.gitignore delete mode 100644 docs/users-stats/.vscode/settings.json delete mode 100644 docs/users-stats/README.md delete mode 100644 docs/users-stats/explorer.ts delete mode 100644 docs/users-stats/get_token.ts delete mode 100644 docs/users-stats/global.d.ts delete mode 100644 docs/users-stats/mod.ts delete mode 100644 docs/users-stats/package-lock.json delete mode 100644 docs/users-stats/package.json delete mode 100644 docs/users-stats/scripts.yml delete mode 100644 docs/users-stats/tsconfig.json diff --git a/docs/users-stats/.editorconfig b/docs/users-stats/.editorconfig deleted file mode 100644 index 91a196c94..000000000 --- a/docs/users-stats/.editorconfig +++ /dev/null @@ -1,8 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = false \ No newline at end of file diff --git a/docs/users-stats/.env.example b/docs/users-stats/.env.example deleted file mode 100644 index 752ab9029..000000000 --- a/docs/users-stats/.env.example +++ /dev/null @@ -1,8 +0,0 @@ -# fill in the blanks from your auth0.com application -# that has access to the auth0 management api -test= -client_id= -client_secret= -audience= -apiurl= -url= \ No newline at end of file diff --git a/docs/users-stats/.eslintignore b/docs/users-stats/.eslintignore deleted file mode 100644 index 9366cd394..000000000 --- a/docs/users-stats/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -docs/users-stats \ No newline at end of file diff --git a/docs/users-stats/.gitignore b/docs/users-stats/.gitignore deleted file mode 100644 index 56075de65..000000000 --- a/docs/users-stats/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.env -token -request.sh \ No newline at end of file diff --git a/docs/users-stats/.vscode/settings.json b/docs/users-stats/.vscode/settings.json deleted file mode 100644 index 869c4e39d..000000000 --- a/docs/users-stats/.vscode/settings.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "workbench.colorCustomizations": { - "activityBar.activeBackground": "#963ee7", - "activityBar.activeBorder": "#e69339", - "activityBar.background": "#963ee7", - "activityBar.foreground": "#e7e7e7", - "activityBar.inactiveForeground": "#e7e7e799", - "activityBarBadge.background": "#e69339", - "activityBarBadge.foreground": "#15202b", - "statusBar.background": "#7d1bd7", - "statusBar.border": "#7d1bd7", - "statusBar.foreground": "#e7e7e7", - "statusBarItem.hoverBackground": "#963ee7", - "titleBar.activeBackground": "#7d1bd7", - "titleBar.activeForeground": "#e7e7e7", - "titleBar.border": "#7d1bd7", - "titleBar.inactiveBackground": "#7d1bd799", - "titleBar.inactiveForeground": "#e7e7e799" - }, - "deno.enabled": true, - "peacock.color": "#7d1bd7", - "editor.codeActionsOnSave": { - "spellright": false - }, - "typescript.tsdk": "node_modules/typescript/lib" -} \ No newline at end of file diff --git a/docs/users-stats/README.md b/docs/users-stats/README.md deleted file mode 100644 index 22541b019..000000000 --- a/docs/users-stats/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Collection Stats and Managing Users - -These are some [Deno](https://deno.land/) scripts to use the management api. Currently it gets a token and makes a call to the users endpoint to retrieve data and identify unverified emails. - - -## Prerequisites - -- Rename `env.example` to `.env` and fill in the blanks. Needs an application on auth0.com with access to the users management. -- install [Deno](https://deno.land/) -- install [Velociraptor](https://github.com/umbopepato/velociraptor) - -Current used Deno and Velociraptor versions are: - -```bash -$ deno --version -> deno 1.0.2 -> v8 8.4.300 -> typescript 3.9.2 - - -$ vr help -> Version: v1.0.0-beta.8 -``` - -## Usage - -Get a token - -```bash -vr run token -``` - -run the script - -```bash -vr run start -``` diff --git a/docs/users-stats/explorer.ts b/docs/users-stats/explorer.ts deleted file mode 100644 index cc692356d..000000000 --- a/docs/users-stats/explorer.ts +++ /dev/null @@ -1,61 +0,0 @@ -//explorer.ts -import { readFileStr, load } from './mod.ts'; - -async function main(): Promise { - await load(); - const { apiurl } = Deno.env.toObject(); - const perpage = 50; - let page = 0; - - const token = await readFileStr('./token', { encoding: 'utf8' }); - let next = true; - - const users = []; - - const headers = new Headers({ - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }); - while (next) { - const url = `${apiurl}/users?page=${page}&per_page=${perpage}`; - const res = await fetch(url, { - method: 'GET', - headers, - }); - if (!res.ok) { - next = false; - break; - } else { - const json = (await res.json()) as any[]; - // console.log(json); - users.push(...json); - page++; - } - } - - const domains: string[] = []; - const notVerified = []; - users.forEach(user => { - if (user.email_verified === false) { - // console.log(user.email); - const domain = user.email.split('@')[1]; - - domains.push(domain); - notVerified.push(user); - } - }); - const mails = users.map(user => user.email); - const uniqueDomains = [...new Set(domains)]; - - console.log('-----------users-----------'); - console.log('users tried to signed up ', users.length); - console.log('----- email verification -------'); - console.log('number not verfied user mails ', notVerified.length); - console.log('unique not verified domains ', uniqueDomains.join(', ')); - - // const diskjson = (await readJson("./giessdenkiez-users.json")) as any[]; - - // console.log(diskjson.length); -} - -main().catch(console.error); diff --git a/docs/users-stats/get_token.ts b/docs/users-stats/get_token.ts deleted file mode 100644 index 84c3e6494..000000000 --- a/docs/users-stats/get_token.ts +++ /dev/null @@ -1,44 +0,0 @@ -async function getToken(options: { - url: string; - client_id: string; - client_secret: string; - audience: string; -}): Promise { - const { client_id, client_secret, audience, url } = options; - const body = { - client_id, - client_secret, - audience, - grant_type: 'client_credentials', - }; - try { - const res = await fetch(url, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify(body), - }); - if (res.ok) { - const json = await res.json(); - const { access_token } = json; - return access_token; - } else { - throw new Error('could not get toke from auth0'); - } - } catch (error) { - console.error(error); - throw error; - } -} - -if (import.meta.main) { - console.log('running standalone'); - import('./mod.ts') - .then(async module => { - await module.load(); - const { url, audience, client_id, client_secret } = Deno.env.toObject(); - const token = await getToken({ url, audience, client_id, client_secret }); - module.writeFileStrSync('./token', token); - return; - }) - .catch(console.error); -} diff --git a/docs/users-stats/global.d.ts b/docs/users-stats/global.d.ts deleted file mode 100644 index 462b640a6..000000000 --- a/docs/users-stats/global.d.ts +++ /dev/null @@ -1,3413 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -/// -/// - -declare namespace Deno { - /** A set of error constructors that are raised by Deno APIs. */ - export const errors: { - NotFound: ErrorConstructor; - PermissionDenied: ErrorConstructor; - ConnectionRefused: ErrorConstructor; - ConnectionReset: ErrorConstructor; - ConnectionAborted: ErrorConstructor; - NotConnected: ErrorConstructor; - AddrInUse: ErrorConstructor; - AddrNotAvailable: ErrorConstructor; - BrokenPipe: ErrorConstructor; - AlreadyExists: ErrorConstructor; - InvalidData: ErrorConstructor; - TimedOut: ErrorConstructor; - Interrupted: ErrorConstructor; - WriteZero: ErrorConstructor; - UnexpectedEof: ErrorConstructor; - BadResource: ErrorConstructor; - Http: ErrorConstructor; - Busy: ErrorConstructor; - }; - - /** The current process id of the runtime. */ - export const pid: number; - - /** Reflects the `NO_COLOR` environment variable. - * - * See: https://no-color.org/ */ - export const noColor: boolean; - - export interface TestDefinition { - fn: () => void | Promise; - name: string; - ignore?: boolean; - /** Check that the number of async completed ops after the test is the same - * as number of dispatched ops. Defaults to true.*/ - sanitizeOps?: boolean; - /** Ensure the test case does not "leak" resources - ie. the resource table - * after the test has exactly the same contents as before the test. Defaults - * to true. */ - sanitizeResources?: boolean; - } - - /** Register a test which will be run when `deno test` is used on the command - * line and the containing module looks like a test module. - * `fn` can be async if required. - * ```ts - * import {assert, fail, assertEquals} from "https://deno.land/std/testing/asserts.ts"; - * - * Deno.test({ - * name: "example test", - * fn(): void { - * assertEquals("world", "world"); - * }, - * }); - * - * Deno.test({ - * name: "example ignored test", - * ignore: Deno.build.os === "windows" - * fn(): void { - * // This test is ignored only on Windows machines - * }, - * }); - * - * Deno.test({ - * name: "example async test", - * async fn() { - * const decoder = new TextDecoder("utf-8"); - * const data = await Deno.readFile("hello_world.txt"); - * assertEquals(decoder.decode(data), "Hello world") - * } - * }); - * ``` - */ - export function test(t: TestDefinition): void; - - /** Register a test which will be run when `deno test` is used on the command - * line and the containing module looks like a test module. - * `fn` can be async if required. - * - * ```ts - * import {assert, fail, assertEquals} from "https://deno.land/std/testing/asserts.ts"; - * - * Deno.test("My test description", ():void => { - * assertEquals("hello", "hello"); - * }); - * - * Deno.test("My async test description", async ():Promise => { - * const decoder = new TextDecoder("utf-8"); - * const data = await Deno.readFile("hello_world.txt"); - * assertEquals(decoder.decode(data), "Hello world") - * }); - * ``` - * */ - export function test(name: string, fn: () => void | Promise): void; - - /** Exit the Deno process with optional exit code. If no exit code is supplied - * then Deno will exit with return code of 0. - * - * ```ts - * Deno.exit(5); - * ``` - */ - export function exit(code?: number): never; - - export const env: { - /** Retrieve the value of an environment variable. Returns undefined if that - * key doesn't exist. - * - * ```ts - * console.log(Deno.env.get("HOME")); // e.g. outputs "/home/alice" - * console.log(Deno.env.get("MADE_UP_VAR")); // outputs "Undefined" - * ``` - * Requires `allow-env` permission. */ - get(key: string): string | undefined; - - /** Set the value of an environment variable. - * - * ```ts - * Deno.env.set("SOME_VAR", "Value")); - * Deno.env.get("SOME_VAR"); // outputs "Value" - * ``` - * - * Requires `allow-env` permission. */ - set(key: string, value: string): void; - - /** Returns a snapshot of the environment variables at invocation. - * - * ```ts - * Deno.env.set("TEST_VAR", "A"); - * const myEnv = Deno.env.toObject(); - * console.log(myEnv.SHELL); - * Deno.env.set("TEST_VAR", "B"); - * console.log(myEnv.TEST_VAR); // outputs "A" - * ``` - * - * Requires `allow-env` permission. */ - toObject(): { [index: string]: string }; - }; - - /** - * Returns the path to the current deno executable. - * - * ```ts - * console.log(Deno.execPath()); // e.g. "/home/alice/.local/bin/deno" - * ``` - * - * Requires `allow-read` permission. - */ - export function execPath(): string; - - /** - * Change the current working directory to the specified path. - * - * ```ts - * Deno.chdir("/home/userA"); - * Deno.chdir("../userB"); - * Deno.chdir("C:\\Program Files (x86)\\Java"); - * ``` - * - * Throws `Deno.errors.NotFound` if directory not found. - * Throws `Deno.errors.PermissionDenied` if the user does not have access - * rights - * - * Requires --allow-read. - */ - export function chdir(directory: string): void; - - /** - * Return a string representing the current working directory. - * - * If the current directory can be reached via multiple paths (due to symbolic - * links), `cwd()` may return any one of them. - * - * ```ts - * const currentWorkingDirectory = Deno.cwd(); - * ``` - * - * Throws `Deno.errors.NotFound` if directory not available. - * - * Requires --allow-read - */ - export function cwd(): string; - - export enum SeekMode { - Start = 0, - Current = 1, - End = 2, - } - - export interface Reader { - /** Reads up to `p.byteLength` bytes into `p`. It resolves to the number of - * bytes read (`0` < `n` <= `p.byteLength`) and rejects if any error - * encountered. Even if `read()` resolves to `n` < `p.byteLength`, it may - * use all of `p` as scratch space during the call. If some data is - * available but not `p.byteLength` bytes, `read()` conventionally resolves - * to what is available instead of waiting for more. - * - * When `read()` encounters end-of-file condition, it resolves to EOF - * (`null`). - * - * When `read()` encounters an error, it rejects with an error. - * - * Callers should always process the `n` > `0` bytes returned before - * considering the EOF (`null`). Doing so correctly handles I/O errors that - * happen after reading some bytes and also both of the allowed EOF - * behaviors. - * - * Implementations should not retain a reference to `p`. - * - * Use Deno.iter() to turn a Reader into an AsyncIterator. - */ - read(p: Uint8Array): Promise; - } - - export interface ReaderSync { - /** Reads up to `p.byteLength` bytes into `p`. It resolves to the number - * of bytes read (`0` < `n` <= `p.byteLength`) and rejects if any error - * encountered. Even if `read()` returns `n` < `p.byteLength`, it may use - * all of `p` as scratch space during the call. If some data is available - * but not `p.byteLength` bytes, `read()` conventionally returns what is - * available instead of waiting for more. - * - * When `readSync()` encounters end-of-file condition, it returns EOF - * (`null`). - * - * When `readSync()` encounters an error, it throws with an error. - * - * Callers should always process the `n` > `0` bytes returned before - * considering the EOF (`null`). Doing so correctly handles I/O errors that happen - * after reading some bytes and also both of the allowed EOF behaviors. - * - * Implementations should not retain a reference to `p`. - * - * Use Deno.iterSync() to turn a ReaderSync into an Iterator. - */ - readSync(p: Uint8Array): number | null; - } - - export interface Writer { - /** Writes `p.byteLength` bytes from `p` to the underlying data stream. It - * resolves to the number of bytes written from `p` (`0` <= `n` <= - * `p.byteLength`) or reject with the error encountered that caused the - * write to stop early. `write()` must reject with a non-null error if - * would resolve to `n` < `p.byteLength`. `write()` must not modify the - * slice data, even temporarily. - * - * Implementations should not retain a reference to `p`. - */ - write(p: Uint8Array): Promise; - } - - export interface WriterSync { - /** Writes `p.byteLength` bytes from `p` to the underlying data - * stream. It returns the number of bytes written from `p` (`0` <= `n` - * <= `p.byteLength`) and any error encountered that caused the write to - * stop early. `writeSync()` must throw a non-null error if it returns `n` < - * `p.byteLength`. `writeSync()` must not modify the slice data, even - * temporarily. - * - * Implementations should not retain a reference to `p`. - */ - writeSync(p: Uint8Array): number; - } - - export interface Closer { - close(): void; - } - - export interface Seeker { - /** Seek sets the offset for the next `read()` or `write()` to offset, - * interpreted according to `whence`: `Start` means relative to the - * start of the file, `Current` means relative to the current offset, - * and `End` means relative to the end. Seek resolves to the new offset - * relative to the start of the file. - * - * Seeking to an offset before the start of the file is an error. Seeking to - * any positive offset is legal, but the behavior of subsequent I/O - * operations on the underlying object is implementation-dependent. - * It returns the number of cursor position. - */ - seek(offset: number, whence: SeekMode): Promise; - } - - export interface SeekerSync { - /** Seek sets the offset for the next `readSync()` or `writeSync()` to - * offset, interpreted according to `whence`: `Start` means relative - * to the start of the file, `Current` means relative to the current - * offset, and `End` means relative to the end. - * - * Seeking to an offset before the start of the file is an error. Seeking to - * any positive offset is legal, but the behavior of subsequent I/O - * operations on the underlying object is implementation-dependent. - */ - seekSync(offset: number, whence: SeekMode): number; - } - - /** Copies from `src` to `dst` until either EOF (`null`) is read from `src` or - * an error occurs. It resolves to the number of bytes copied or rejects with - * the first error encountered while copying. - * - * ```ts - * const source = await Deno.open("my_file.txt"); - * const buffer = new Deno.Buffer() - * const bytesCopied1 = await Deno.copy(source, Deno.stdout); - * const bytesCopied2 = await Deno.copy(source, buffer); - * ``` - * - * @param src The source to copy from - * @param dst The destination to copy to - * @param options Can be used to tune size of the buffer. Default size is 32kB - */ - export function copy( - src: Reader, - dst: Writer, - options?: { - bufSize?: number; - } - ): Promise; - - /** Turns a Reader, `r`, into an async iterator. - * - * ```ts - * let f = await Deno.open("/etc/passwd"); - * for await (const chunk of Deno.iter(f)) { - * console.log(chunk); - * } - * f.close(); - * ``` - * - * Second argument can be used to tune size of a buffer. - * Default size of the buffer is 32kB. - * - * ```ts - * let f = await Deno.open("/etc/passwd"); - * const iter = Deno.iter(f, { - * bufSize: 1024 * 1024 - * }); - * for await (const chunk of iter) { - * console.log(chunk); - * } - * f.close(); - * ``` - * - * Iterator uses an internal buffer of fixed size for efficiency; it returns - * a view on that buffer on each iteration. It is therefore caller's - * responsibility to copy contents of the buffer if needed; otherwise the - * next iteration will overwrite contents of previously returned chunk. - */ - export function iter( - r: Reader, - options?: { - bufSize?: number; - } - ): AsyncIterableIterator; - - /** Turns a ReaderSync, `r`, into an iterator. - * - * ```ts - * let f = Deno.openSync("/etc/passwd"); - * for (const chunk of Deno.iterSync(reader)) { - * console.log(chunk); - * } - * f.close(); - * ``` - * - * Second argument can be used to tune size of a buffer. - * Default size of the buffer is 32kB. - * - * ```ts - * let f = await Deno.open("/etc/passwd"); - * const iter = Deno.iterSync(f, { - * bufSize: 1024 * 1024 - * }); - * for (const chunk of iter) { - * console.log(chunk); - * } - * f.close(); - * ``` - * - * Iterator uses an internal buffer of fixed size for efficiency; it returns - * a view on that buffer on each iteration. It is therefore caller's - * responsibility to copy contents of the buffer if needed; otherwise the - * next iteration will overwrite contents of previously returned chunk. - */ - export function iterSync( - r: ReaderSync, - options?: { - bufSize?: number; - } - ): IterableIterator; - - /** Synchronously open a file and return an instance of `Deno.File`. The - * file does not need to previously exist if using the `create` or `createNew` - * open options. It is the callers responsibility to close the file when finished - * with it. - * - * ```ts - * const file = Deno.openSync("/foo/bar.txt", { read: true, write: true }); - * // Do work with file - * Deno.close(file.rid); - * ``` - * - * Requires `allow-read` and/or `allow-write` permissions depending on options. - */ - export function openSync(path: string, options?: OpenOptions): File; - - /** Open a file and resolve to an instance of `Deno.File`. The - * file does not need to previously exist if using the `create` or `createNew` - * open options. It is the callers responsibility to close the file when finished - * with it. - * - * ```ts - * const file = await Deno.open("/foo/bar.txt", { read: true, write: true }); - * // Do work with file - * Deno.close(file.rid); - * ``` - * - * Requires `allow-read` and/or `allow-write` permissions depending on options. - */ - export function open(path: string, options?: OpenOptions): Promise; - - /** Creates a file if none exists or truncates an existing file and returns - * an instance of `Deno.File`. - * - * ```ts - * const file = Deno.createSync("/foo/bar.txt"); - * ``` - * - * Requires `allow-read` and `allow-write` permissions. - */ - export function createSync(path: string): File; - - /** Creates a file if none exists or truncates an existing file and resolves to - * an instance of `Deno.File`. - * - * ```ts - * const file = await Deno.create("/foo/bar.txt"); - * ``` - * - * Requires `allow-read` and `allow-write` permissions. - */ - export function create(path: string): Promise; - - /** Synchronously read from a resource ID (`rid`) into an array buffer (`buffer`). - * - * Returns either the number of bytes read during the operation or EOF - * (`null`) if there was nothing more to read. - * - * It is possible for a read to successfully return with `0` bytes. This does - * not indicate EOF. - * - * This function is one of the lowest level APIs and most users should not - * work with this directly, but rather use Deno.readAllSync() instead. - * - * **It is not guaranteed that the full buffer will be read in a single call.** - * - * ```ts - * // if "/foo/bar.txt" contains the text "hello world": - * const file = Deno.openSync("/foo/bar.txt"); - * const buf = new Uint8Array(100); - * const numberOfBytesRead = Deno.readSync(file.rid, buf); // 11 bytes - * const text = new TextDecoder().decode(buf); // "hello world" - * Deno.close(file.rid); - * ``` - */ - export function readSync(rid: number, buffer: Uint8Array): number | null; - - /** Read from a resource ID (`rid`) into an array buffer (`buffer`). - * - * Resolves to either the number of bytes read during the operation or EOF - * (`null`) if there was nothing more to read. - * - * It is possible for a read to successfully return with `0` bytes. This does - * not indicate EOF. - * - * This function is one of the lowest level APIs and most users should not - * work with this directly, but rather use Deno.readAll() instead. - * - * **It is not guaranteed that the full buffer will be read in a single call.** - * - * ```ts - * // if "/foo/bar.txt" contains the text "hello world": - * const file = await Deno.open("/foo/bar.txt"); - * const buf = new Uint8Array(100); - * const numberOfBytesRead = await Deno.read(file.rid, buf); // 11 bytes - * const text = new TextDecoder().decode(buf); // "hello world" - * Deno.close(file.rid); - * ``` - */ - export function read(rid: number, buffer: Uint8Array): Promise; - - /** Synchronously write to the resource ID (`rid`) the contents of the array - * buffer (`data`). - * - * Returns the number of bytes written. This function is one of the lowest - * level APIs and most users should not work with this directly, but rather use - * Deno.writeAllSync() instead. - * - * **It is not guaranteed that the full buffer will be written in a single - * call.** - * - * ```ts - * const encoder = new TextEncoder(); - * const data = encoder.encode("Hello world"); - * const file = Deno.openSync("/foo/bar.txt"); - * const bytesWritten = Deno.writeSync(file.rid, data); // 11 - * Deno.close(file.rid); - * ``` - */ - export function writeSync(rid: number, data: Uint8Array): number; - - /** Write to the resource ID (`rid`) the contents of the array buffer (`data`). - * - * Resolves to the number of bytes written. This function is one of the lowest - * level APIs and most users should not work with this directly, but rather use - * Deno.writeAll() instead. - * - * **It is not guaranteed that the full buffer will be written in a single - * call.** - * - * ```ts - * const encoder = new TextEncoder(); - * const data = encoder.encode("Hello world"); - * const file = await Deno.open("/foo/bar.txt"); - * const bytesWritten = await Deno.write(file.rid, data); // 11 - * Deno.close(file.rid); - * ``` - */ - export function write(rid: number, data: Uint8Array): Promise; - - /** Synchronously seek a resource ID (`rid`) to the given `offset` under mode - * given by `whence`. The new position within the resource (bytes from the - * start) is returned. - * - * ```ts - * const file = Deno.openSync('hello.txt', {read: true, write: true, truncate: true, create: true}); - * Deno.writeSync(file.rid, new TextEncoder().encode("Hello world")); - * // advance cursor 6 bytes - * const cursorPosition = Deno.seekSync(file.rid, 6, Deno.SeekMode.Start); - * console.log(cursorPosition); // 6 - * const buf = new Uint8Array(100); - * file.readSync(buf); - * console.log(new TextDecoder().decode(buf)); // "world" - * ``` - * - * The seek modes work as follows: - * - * ```ts - * // Given file.rid pointing to file with "Hello world", which is 11 bytes long: - * // Seek 6 bytes from the start of the file - * console.log(Deno.seekSync(file.rid, 6, Deno.SeekMode.Start)); // "6" - * // Seek 2 more bytes from the current position - * console.log(Deno.seekSync(file.rid, 2, Deno.SeekMode.Current)); // "8" - * // Seek backwards 2 bytes from the end of the file - * console.log(Deno.seekSync(file.rid, -2, Deno.SeekMode.End)); // "9" (e.g. 11-2) - * ``` - */ - export function seekSync( - rid: number, - offset: number, - whence: SeekMode - ): number; - - /** Seek a resource ID (`rid`) to the given `offset` under mode given by `whence`. - * The call resolves to the new position within the resource (bytes from the start). - * - * ```ts - * const file = await Deno.open('hello.txt', {read: true, write: true, truncate: true, create: true}); - * await Deno.write(file.rid, new TextEncoder().encode("Hello world")); - * // advance cursor 6 bytes - * const cursorPosition = await Deno.seek(file.rid, 6, Deno.SeekMode.Start); - * console.log(cursorPosition); // 6 - * const buf = new Uint8Array(100); - * await file.read(buf); - * console.log(new TextDecoder().decode(buf)); // "world" - * ``` - * - * The seek modes work as follows: - * - * ```ts - * // Given file.rid pointing to file with "Hello world", which is 11 bytes long: - * // Seek 6 bytes from the start of the file - * console.log(await Deno.seek(file.rid, 6, Deno.SeekMode.Start)); // "6" - * // Seek 2 more bytes from the current position - * console.log(await Deno.seek(file.rid, 2, Deno.SeekMode.Current)); // "8" - * // Seek backwards 2 bytes from the end of the file - * console.log(await Deno.seek(file.rid, -2, Deno.SeekMode.End)); // "9" (e.g. 11-2) - * ``` - */ - export function seek( - rid: number, - offset: number, - whence: SeekMode - ): Promise; - - /** Close the given resource ID (rid) which has been previously opened, such - * as via opening or creating a file. Closing a file when you are finished - * with it is important to avoid leaking resources. - * - * ```ts - * const file = await Deno.open("my_file.txt"); - * // do work with "file" object - * Deno.close(file.rid); - * ```` - */ - export function close(rid: number): void; - - /** The Deno abstraction for reading and writing files. */ - export class File - implements - Reader, - ReaderSync, - Writer, - WriterSync, - Seeker, - SeekerSync, - Closer { - readonly rid: number; - constructor(rid: number); - write(p: Uint8Array): Promise; - writeSync(p: Uint8Array): number; - read(p: Uint8Array): Promise; - readSync(p: Uint8Array): number | null; - seek(offset: number, whence: SeekMode): Promise; - seekSync(offset: number, whence: SeekMode): number; - close(): void; - } - - /** A handle for `stdin`. */ - export const stdin: Reader & ReaderSync & Closer & { rid: number }; - /** A handle for `stdout`. */ - export const stdout: Writer & WriterSync & Closer & { rid: number }; - /** A handle for `stderr`. */ - export const stderr: Writer & WriterSync & Closer & { rid: number }; - - export interface OpenOptions { - /** Sets the option for read access. This option, when `true`, means that the - * file should be read-able if opened. */ - read?: boolean; - /** Sets the option for write access. This option, when `true`, means that - * the file should be write-able if opened. If the file already exists, - * any write calls on it will overwrite its contents, by default without - * truncating it. */ - write?: boolean; - /**Sets the option for the append mode. This option, when `true`, means that - * writes will append to a file instead of overwriting previous contents. - * Note that setting `{ write: true, append: true }` has the same effect as - * setting only `{ append: true }`. */ - append?: boolean; - /** Sets the option for truncating a previous file. If a file is - * successfully opened with this option set it will truncate the file to `0` - * size if it already exists. The file must be opened with write access - * for truncate to work. */ - truncate?: boolean; - /** Sets the option to allow creating a new file, if one doesn't already - * exist at the specified path. Requires write or append access to be - * used. */ - create?: boolean; - /** Defaults to `false`. If set to `true`, no file, directory, or symlink is - * allowed to exist at the target location. Requires write or append - * access to be used. When createNew is set to `true`, create and truncate - * are ignored. */ - createNew?: boolean; - /** Permissions to use if creating the file (defaults to `0o666`, before - * the process's umask). - * Ignored on Windows. */ - mode?: number; - } - - /** - * - * Check if a given resource id (`rid`) is a TTY. - * - * ```ts - * // This example is system and context specific - * const nonTTYRid = Deno.openSync("my_file.txt").rid; - * const ttyRid = Deno.openSync("/dev/tty6").rid; - * console.log(Deno.isatty(nonTTYRid)); // false - * console.log(Deno.isatty(ttyRid)); // true - * Deno.close(nonTTYRid); - * Deno.close(ttyRid); - * ``` - */ - export function isatty(rid: number): boolean; - - /** A variable-sized buffer of bytes with `read()` and `write()` methods. - * - * Deno.Buffer is almost always used with some I/O like files and sockets. It - * allows one to buffer up a download from a socket. Buffer grows and shrinks - * as necessary. - * - * Deno.Buffer is NOT the same thing as Node's Buffer. Node's Buffer was - * created in 2009 before JavaScript had the concept of ArrayBuffers. It's - * simply a non-standard ArrayBuffer. - * - * ArrayBuffer is a fixed memory allocation. Deno.Buffer is implemented on top - * of ArrayBuffer. - * - * Based on [Go Buffer](https://golang.org/pkg/bytes/#Buffer). */ - export class Buffer implements Reader, ReaderSync, Writer, WriterSync { - constructor(ab?: ArrayBuffer); - /** Returns a slice holding the unread portion of the buffer. - * - * The slice is valid for use only until the next buffer modification (that - * is, only until the next call to a method like `read()`, `write()`, - * `reset()`, or `truncate()`). The slice aliases the buffer content at - * least until the next buffer modification, so immediate changes to the - * slice will affect the result of future reads. */ - bytes(): Uint8Array; - /** Returns whether the unread portion of the buffer is empty. */ - empty(): boolean; - /** A read only number of bytes of the unread portion of the buffer. */ - readonly length: number; - /** The read only capacity of the buffer's underlying byte slice, that is, - * the total space allocated for the buffer's data. */ - readonly capacity: number; - /** Discards all but the first `n` unread bytes from the buffer but - * continues to use the same allocated storage. It throws if `n` is - * negative or greater than the length of the buffer. */ - truncate(n: number): void; - /** Resets the buffer to be empty, but it retains the underlying storage for - * use by future writes. `.reset()` is the same as `.truncate(0)`. */ - reset(): void; - /** Reads the next `p.length` bytes from the buffer or until the buffer is - * drained. Returns the number of bytes read. If the buffer has no data to - * return, the return is EOF (`null`). */ - readSync(p: Uint8Array): number | null; - /** Reads the next `p.length` bytes from the buffer or until the buffer is - * drained. Resolves to the number of bytes read. If the buffer has no - * data to return, resolves to EOF (`null`). - * - * NOTE: This methods reads bytes sychronously; it's provided for - * compatibility with `Reader` interfaces. - */ - read(p: Uint8Array): Promise; - writeSync(p: Uint8Array): number; - /** NOTE: This methods writes bytes sychronously; it's provided for - * compatibility with `Writer` interface. */ - write(p: Uint8Array): Promise; - /** Grows the buffer's capacity, if necessary, to guarantee space for - * another `n` bytes. After `.grow(n)`, at least `n` bytes can be written to - * the buffer without another allocation. If `n` is negative, `.grow()` will - * throw. If the buffer can't grow it will throw an error. - * - * Based on Go Lang's - * [Buffer.Grow](https://golang.org/pkg/bytes/#Buffer.Grow). */ - grow(n: number): void; - /** Reads data from `r` until EOF (`null`) and appends it to the buffer, - * growing the buffer as needed. It resolves to the number of bytes read. - * If the buffer becomes too large, `.readFrom()` will reject with an error. - * - * Based on Go Lang's - * [Buffer.ReadFrom](https://golang.org/pkg/bytes/#Buffer.ReadFrom). */ - readFrom(r: Reader): Promise; - /** Reads data from `r` until EOF (`null`) and appends it to the buffer, - * growing the buffer as needed. It returns the number of bytes read. If the - * buffer becomes too large, `.readFromSync()` will throw an error. - * - * Based on Go Lang's - * [Buffer.ReadFrom](https://golang.org/pkg/bytes/#Buffer.ReadFrom). */ - readFromSync(r: ReaderSync): number; - } - - /** Read Reader `r` until EOF (`null`) and resolve to the content as - * Uint8Array`. - * - * ```ts - * // Example from stdin - * const stdinContent = await Deno.readAll(Deno.stdin); - * - * // Example from file - * const file = await Deno.open("my_file.txt", {read: true}); - * const myFileContent = await Deno.readAll(file); - * Deno.close(file.rid); - * - * // Example from buffer - * const myData = new Uint8Array(100); - * // ... fill myData array with data - * const reader = new Deno.Buffer(myData.buffer as ArrayBuffer); - * const bufferContent = await Deno.readAll(reader); - * ``` - */ - export function readAll(r: Reader): Promise; - - /** Synchronously reads Reader `r` until EOF (`null`) and returns the content - * as `Uint8Array`. - * - * ```ts - * // Example from stdin - * const stdinContent = Deno.readAllSync(Deno.stdin); - * - * // Example from file - * const file = Deno.openSync("my_file.txt", {read: true}); - * const myFileContent = Deno.readAllSync(file); - * Deno.close(file.rid); - * - * // Example from buffer - * const myData = new Uint8Array(100); - * // ... fill myData array with data - * const reader = new Deno.Buffer(myData.buffer as ArrayBuffer); - * const bufferContent = Deno.readAllSync(reader); - * ``` - */ - export function readAllSync(r: ReaderSync): Uint8Array; - - /** Write all the content of the array buffer (`arr`) to the writer (`w`). - * - * ```ts - * // Example writing to stdout - * const contentBytes = new TextEncoder().encode("Hello World"); - * await Deno.writeAll(Deno.stdout, contentBytes); - * - * // Example writing to file - * const contentBytes = new TextEncoder().encode("Hello World"); - * const file = await Deno.open('test.file', {write: true}); - * await Deno.writeAll(file, contentBytes); - * Deno.close(file.rid); - * - * // Example writing to buffer - * const contentBytes = new TextEncoder().encode("Hello World"); - * const writer = new Deno.Buffer(); - * await Deno.writeAll(writer, contentBytes); - * console.log(writer.bytes().length); // 11 - * ``` - */ - export function writeAll(w: Writer, arr: Uint8Array): Promise; - - /** Synchronously write all the content of the array buffer (`arr`) to the - * writer (`w`). - * - * ```ts - * // Example writing to stdout - * const contentBytes = new TextEncoder().encode("Hello World"); - * Deno.writeAllSync(Deno.stdout, contentBytes); - * - * // Example writing to file - * const contentBytes = new TextEncoder().encode("Hello World"); - * const file = Deno.openSync('test.file', {write: true}); - * Deno.writeAllSync(file, contentBytes); - * Deno.close(file.rid); - * - * // Example writing to buffer - * const contentBytes = new TextEncoder().encode("Hello World"); - * const writer = new Deno.Buffer(); - * Deno.writeAllSync(writer, contentBytes); - * console.log(writer.bytes().length); // 11 - * ``` - */ - export function writeAllSync(w: WriterSync, arr: Uint8Array): void; - - export interface MkdirOptions { - /** Defaults to `false`. If set to `true`, means that any intermediate - * directories will also be created (as with the shell command `mkdir -p`). - * Intermediate directories are created with the same permissions. - * When recursive is set to `true`, succeeds silently (without changing any - * permissions) if a directory already exists at the path, or if the path - * is a symlink to an existing directory. */ - recursive?: boolean; - /** Permissions to use when creating the directory (defaults to `0o777`, - * before the process's umask). - * Ignored on Windows. */ - mode?: number; - } - - /** Synchronously creates a new directory with the specified path. - * - * ```ts - * Deno.mkdirSync("new_dir"); - * Deno.mkdirSync("nested/directories", { recursive: true }); - * Deno.mkdirSync("restricted_access_dir", { mode: 0o700 }); - * ``` - * - * Defaults to throwing error if the directory already exists. - * - * Requires `allow-write` permission. */ - export function mkdirSync(path: string, options?: MkdirOptions): void; - - /** Creates a new directory with the specified path. - * - * ```ts - * await Deno.mkdir("new_dir"); - * await Deno.mkdir("nested/directories", { recursive: true }); - * await Deno.mkdir("restricted_access_dir", { mode: 0o700 }); - * ``` - * - * Defaults to throwing error if the directory already exists. - * - * Requires `allow-write` permission. */ - export function mkdir(path: string, options?: MkdirOptions): Promise; - - export interface MakeTempOptions { - /** Directory where the temporary directory should be created (defaults to - * the env variable TMPDIR, or the system's default, usually /tmp). */ - dir?: string; - /** String that should precede the random portion of the temporary - * directory's name. */ - prefix?: string; - /** String that should follow the random portion of the temporary - * directory's name. */ - suffix?: string; - } - - /** Synchronously creates a new temporary directory in the default directory - * for temporary files (see also `Deno.dir("temp")`), unless `dir` is specified. - * Other optional options include prefixing and suffixing the directory name - * with `prefix` and `suffix` respectively. - * - * The full path to the newly created directory is returned. - * - * Multiple programs calling this function simultaneously will create different - * directories. It is the caller's responsibility to remove the directory when - * no longer needed. - * - * ```ts - * const tempDirName0 = Deno.makeTempDirSync(); // e.g. /tmp/2894ea76 - * const tempDirName1 = Deno.makeTempDirSync({ prefix: 'my_temp' }); // e.g. /tmp/my_temp339c944d - * ``` - * - * Requires `allow-write` permission. */ - // TODO(ry) Doesn't check permissions. - export function makeTempDirSync(options?: MakeTempOptions): string; - - /** Creates a new temporary directory in the default directory for temporary - * files (see also `Deno.dir("temp")`), unless `dir` is specified. Other - * optional options include prefixing and suffixing the directory name with - * `prefix` and `suffix` respectively. - * - * This call resolves to the full path to the newly created directory. - * - * Multiple programs calling this function simultaneously will create different - * directories. It is the caller's responsibility to remove the directory when - * no longer needed. - * - * ```ts - * const tempDirName0 = await Deno.makeTempDir(); // e.g. /tmp/2894ea76 - * const tempDirName1 = await Deno.makeTempDir({ prefix: 'my_temp' }); // e.g. /tmp/my_temp339c944d - * ``` - * - * Requires `allow-write` permission. */ - // TODO(ry) Doesn't check permissions. - export function makeTempDir(options?: MakeTempOptions): Promise; - - /** Synchronously creates a new temporary file in the default directory for - * temporary files (see also `Deno.dir("temp")`), unless `dir` is specified. - * Other optional options include prefixing and suffixing the directory name - * with `prefix` and `suffix` respectively. - * - * The full path to the newly created file is returned. - * - * Multiple programs calling this function simultaneously will create different - * files. It is the caller's responsibility to remove the file when no longer - * needed. - * - * ```ts - * const tempFileName0 = Deno.makeTempFileSync(); // e.g. /tmp/419e0bf2 - * const tempFileName1 = Deno.makeTempFileSync({ prefix: 'my_temp' }); // e.g. /tmp/my_temp754d3098 - * ``` - * - * Requires `allow-write` permission. */ - export function makeTempFileSync(options?: MakeTempOptions): string; - - /** Creates a new temporary file in the default directory for temporary - * files (see also `Deno.dir("temp")`), unless `dir` is specified. Other - * optional options include prefixing and suffixing the directory name with - * `prefix` and `suffix` respectively. - * - * This call resolves to the full path to the newly created file. - * - * Multiple programs calling this function simultaneously will create different - * files. It is the caller's responsibility to remove the file when no longer - * needed. - * - * ```ts - * const tmpFileName0 = await Deno.makeTempFile(); // e.g. /tmp/419e0bf2 - * const tmpFileName1 = await Deno.makeTempFile({ prefix: 'my_temp' }); // e.g. /tmp/my_temp754d3098 - * ``` - * - * Requires `allow-write` permission. */ - export function makeTempFile(options?: MakeTempOptions): Promise; - - /** Synchronously changes the permission of a specific file/directory of - * specified path. Ignores the process's umask. - * - * ```ts - * Deno.chmodSync("/path/to/file", 0o666); - * ``` - * - * For a full description, see [chmod](#chmod) - * - * NOTE: This API currently throws on Windows - * - * Requires `allow-write` permission. */ - export function chmodSync(path: string, mode: number): void; - - /** Changes the permission of a specific file/directory of specified path. - * Ignores the process's umask. - * - * ```ts - * await Deno.chmod("/path/to/file", 0o666); - * ``` - * - * The mode is a sequence of 3 octal numbers. The first/left-most number - * specifies the permissions for the owner. The second number specifies the - * permissions for the group. The last/right-most number specifies the - * permissions for others. For example, with a mode of 0o764, the owner (7) can - * read/write/execute, the group (6) can read/write and everyone else (4) can - * read only. - * - * | Number | Description | - * | ------ | ----------- | - * | 7 | read, write, and execute | - * | 6 | read and write | - * | 5 | read and execute | - * | 4 | read only | - * | 3 | write and execute | - * | 2 | write only | - * | 1 | execute only | - * | 0 | no permission | - * - * NOTE: This API currently throws on Windows - * - * Requires `allow-write` permission. */ - export function chmod(path: string, mode: number): Promise; - - /** Synchronously change owner of a regular file or directory. This functionality - * is not available on Windows. - * - * ```ts - * Deno.chownSync("myFile.txt", 1000, 1002); - * ``` - * - * Requires `allow-write` permission. - * - * Throws Error (not implemented) if executed on Windows - * - * @param path path to the file - * @param uid user id (UID) of the new owner - * @param gid group id (GID) of the new owner - */ - export function chownSync(path: string, uid: number, gid: number): void; - - /** Change owner of a regular file or directory. This functionality - * is not available on Windows. - * - * ```ts - * await Deno.chown("myFile.txt", 1000, 1002); - * ``` - * - * Requires `allow-write` permission. - * - * Throws Error (not implemented) if executed on Windows - * - * @param path path to the file - * @param uid user id (UID) of the new owner - * @param gid group id (GID) of the new owner - */ - export function chown(path: string, uid: number, gid: number): Promise; - - export interface RemoveOptions { - /** Defaults to `false`. If set to `true`, path will be removed even if - * it's a non-empty directory. */ - recursive?: boolean; - } - - /** Synchronously removes the named file or directory. - * - * ```ts - * Deno.removeSync("/path/to/empty_dir/or/file"); - * Deno.removeSync("/path/to/populated_dir/or/file", { recursive: true }); - * ``` - * - * Throws error if permission denied, path not found, or path is a non-empty - * directory and the `recursive` option isn't set to `true`. - * - * Requires `allow-write` permission. */ - export function removeSync(path: string, options?: RemoveOptions): void; - - /** Removes the named file or directory. - * - * ```ts - * await Deno.remove("/path/to/empty_dir/or/file"); - * await Deno.remove("/path/to/populated_dir/or/file", { recursive: true }); - * ``` - * - * Throws error if permission denied, path not found, or path is a non-empty - * directory and the `recursive` option isn't set to `true`. - * - * Requires `allow-write` permission. */ - export function remove(path: string, options?: RemoveOptions): Promise; - - /** Synchronously renames (moves) `oldpath` to `newpath`. Paths may be files or - * directories. If `newpath` already exists and is not a directory, - * `renameSync()` replaces it. OS-specific restrictions may apply when - * `oldpath` and `newpath` are in different directories. - * - * ```ts - * Deno.renameSync("old/path", "new/path"); - * ``` - * - * On Unix, this operation does not follow symlinks at either path. - * - * It varies between platforms when the operation throws errors, and if so what - * they are. It's always an error to rename anything to a non-empty directory. - * - * Requires `allow-read` and `allow-write` permissions. */ - export function renameSync(oldpath: string, newpath: string): void; - - /** Renames (moves) `oldpath` to `newpath`. Paths may be files or directories. - * If `newpath` already exists and is not a directory, `rename()` replaces it. - * OS-specific restrictions may apply when `oldpath` and `newpath` are in - * different directories. - * - * ```ts - * await Deno.rename("old/path", "new/path"); - * ``` - * - * On Unix, this operation does not follow symlinks at either path. - * - * It varies between platforms when the operation throws errors, and if so what - * they are. It's always an error to rename anything to a non-empty directory. - * - * Requires `allow-read` and `allow-write` permission. */ - export function rename(oldpath: string, newpath: string): Promise; - - /** Synchronously reads and returns the entire contents of a file as utf8 encoded string - * encoded string. Reading a directory returns an empty string. - * - * ```ts - * const data = Deno.readTextFileSync("hello.txt"); - * console.log(data); - * ``` - * - * Requires `allow-read` permission. */ - export function readTextFileSync(path: string): string; - - /** Asynchronously reads and returns the entire contents of a file as a utf8 - * encoded string. Reading a directory returns an empty data array. - * - * ```ts - * const data = await Deno.readTextFile("hello.txt"); - * console.log(data); - * ``` - * - * Requires `allow-read` permission. */ - export function readTextFile(path: string): Promise; - - /** Synchronously reads and returns the entire contents of a file as an array - * of bytes. `TextDecoder` can be used to transform the bytes to string if - * required. Reading a directory returns an empty data array. - * - * ```ts - * const decoder = new TextDecoder("utf-8"); - * const data = Deno.readFileSync("hello.txt"); - * console.log(decoder.decode(data)); - * ``` - * - * Requires `allow-read` permission. */ - export function readFileSync(path: string): Uint8Array; - - /** Reads and resolves to the entire contents of a file as an array of bytes. - * `TextDecoder` can be used to transform the bytes to string if required. - * Reading a directory returns an empty data array. - * - * ```ts - * const decoder = new TextDecoder("utf-8"); - * const data = await Deno.readFile("hello.txt"); - * console.log(decoder.decode(data)); - * ``` - * - * Requires `allow-read` permission. */ - export function readFile(path: string): Promise; - - /** A FileInfo describes a file and is returned by `stat`, `lstat`, - * `statSync`, `lstatSync`. */ - export interface FileInfo { - /** True if this is info for a regular file. Mutually exclusive to - * `FileInfo.isDirectory` and `FileInfo.isSymlink`. */ - isFile: boolean; - /** True if this is info for a regular directory. Mutually exclusive to - * `FileInfo.isFile` and `FileInfo.isSymlink`. */ - isDirectory: boolean; - /** True if this is info for a symlink. Mutually exclusive to - * `FileInfo.isFile` and `FileInfo.isDirectory`. */ - isSymlink: boolean; - /** The size of the file, in bytes. */ - size: number; - /** The last modification time of the file. This corresponds to the `mtime` - * field from `stat` on Linux/Mac OS and `ftLastWriteTime` on Windows. This - * may not be available on all platforms. */ - mtime: Date | null; - /** The last access time of the file. This corresponds to the `atime` - * field from `stat` on Unix and `ftLastAccessTime` on Windows. This may not - * be available on all platforms. */ - atime: Date | null; - /** The creation time of the file. This corresponds to the `birthtime` - * field from `stat` on Mac/BSD and `ftCreationTime` on Windows. This may - * not be available on all platforms. */ - birthtime: Date | null; - /** ID of the device containing the file. - * - * _Linux/Mac OS only._ */ - dev: number | null; - /** Inode number. - * - * _Linux/Mac OS only._ */ - ino: number | null; - /** **UNSTABLE**: Match behavior with Go on Windows for `mode`. - * - * The underlying raw `st_mode` bits that contain the standard Unix - * permissions for this file/directory. */ - mode: number | null; - /** Number of hard links pointing to this file. - * - * _Linux/Mac OS only._ */ - nlink: number | null; - /** User ID of the owner of this file. - * - * _Linux/Mac OS only._ */ - uid: number | null; - /** Group ID of the owner of this file. - * - * _Linux/Mac OS only._ */ - gid: number | null; - /** Device ID of this file. - * - * _Linux/Mac OS only._ */ - rdev: number | null; - /** Blocksize for filesystem I/O. - * - * _Linux/Mac OS only._ */ - blksize: number | null; - /** Number of blocks allocated to the file, in 512-byte units. - * - * _Linux/Mac OS only._ */ - blocks: number | null; - } - - /** Returns absolute normalized path, with symbolic links resolved. - * - * ```ts - * // e.g. given /home/alice/file.txt and current directory /home/alice - * Deno.symlinkSync("file.txt", "symlink_file.txt"); - * const realPath = Deno.realPathSync("./file.txt"); - * const realSymLinkPath = Deno.realPathSync("./symlink_file.txt"); - * console.log(realPath); // outputs "/home/alice/file.txt" - * console.log(realSymLinkPath); // outputs "/home/alice/file.txt" - * ``` - * - * Requires `allow-read` permission. */ - export function realPathSync(path: string): string; - - /** Resolves to the absolute normalized path, with symbolic links resolved. - * - * ```ts - * // e.g. given /home/alice/file.txt and current directory /home/alice - * await Deno.symlink("file.txt", "symlink_file.txt"); - * const realPath = await Deno.realPath("./file.txt"); - * const realSymLinkPath = await Deno.realPath("./symlink_file.txt"); - * console.log(realPath); // outputs "/home/alice/file.txt" - * console.log(realSymLinkPath); // outputs "/home/alice/file.txt" - * ``` - * - * Requires `allow-read` permission. */ - export function realPath(path: string): Promise; - - export interface DirEntry { - name: string; - isFile: boolean; - isDirectory: boolean; - isSymlink: boolean; - } - - /** Synchronously reads the directory given by `path` and returns an iterable - * of `Deno.DirEntry`. - * - * ```ts - * for (const dirEntry of Deno.readDirSync("/")) { - * console.log(dirEntry.name); - * } - * ``` - * - * Throws error if `path` is not a directory. - * - * Requires `allow-read` permission. */ - export function readDirSync(path: string): Iterable; - - /** Reads the directory given by `path` and returns an async iterable of - * `Deno.DirEntry`. - * - * ```ts - * for await (const dirEntry of Deno.readDir("/")) { - * console.log(dirEntry.name); - * } - * ``` - * - * Throws error if `path` is not a directory. - * - * Requires `allow-read` permission. */ - export function readDir(path: string): AsyncIterable; - - /** Synchronously copies the contents and permissions of one file to another - * specified path, by default creating a new file if needed, else overwriting. - * Fails if target path is a directory or is unwritable. - * - * ```ts - * Deno.copyFileSync("from.txt", "to.txt"); - * ``` - * - * Requires `allow-read` permission on fromPath. - * Requires `allow-write` permission on toPath. */ - export function copyFileSync(fromPath: string, toPath: string): void; - - /** Copies the contents and permissions of one file to another specified path, - * by default creating a new file if needed, else overwriting. Fails if target - * path is a directory or is unwritable. - * - * ```ts - * await Deno.copyFile("from.txt", "to.txt"); - * ``` - * - * Requires `allow-read` permission on fromPath. - * Requires `allow-write` permission on toPath. */ - export function copyFile(fromPath: string, toPath: string): Promise; - - /** Returns the full path destination of the named symbolic link. - * - * ```ts - * Deno.symlinkSync("./test.txt", "./test_link.txt"); - * const target = Deno.readLinkSync("./test_link.txt"); // full path of ./test.txt - * ``` - * - * Throws TypeError if called with a hard link - * - * Requires `allow-read` permission. */ - export function readLinkSync(path: string): string; - - /** Resolves to the full path destination of the named symbolic link. - * - * ```ts - * await Deno.symlink("./test.txt", "./test_link.txt"); - * const target = await Deno.readLink("./test_link.txt"); // full path of ./test.txt - * ``` - * - * Throws TypeError if called with a hard link - * - * Requires `allow-read` permission. */ - export function readLink(path: string): Promise; - - /** Resolves to a `Deno.FileInfo` for the specified `path`. If `path` is a - * symlink, information for the symlink will be returned instead of what it - * points to. - * - * ```ts - * const fileInfo = await Deno.lstat("hello.txt"); - * assert(fileInfo.isFile); - * ``` - * - * Requires `allow-read` permission. */ - export function lstat(path: string): Promise; - - /** Synchronously returns a `Deno.FileInfo` for the specified `path`. If - * `path` is a symlink, information for the symlink will be returned instead of - * what it points to.. - * - * ```ts - * const fileInfo = Deno.lstatSync("hello.txt"); - * assert(fileInfo.isFile); - * ``` - * - * Requires `allow-read` permission. */ - export function lstatSync(path: string): FileInfo; - - /** Resolves to a `Deno.FileInfo` for the specified `path`. Will always - * follow symlinks. - * - * ```ts - * const fileInfo = await Deno.stat("hello.txt"); - * assert(fileInfo.isFile); - * ``` - * - * Requires `allow-read` permission. */ - export function stat(path: string): Promise; - - /** Synchronously returns a `Deno.FileInfo` for the specified `path`. Will - * always follow symlinks. - * - * ```ts - * const fileInfo = Deno.statSync("hello.txt"); - * assert(fileInfo.isFile); - * ``` - * - * Requires `allow-read` permission. */ - export function statSync(path: string): FileInfo; - - /** Options for writing to a file. */ - export interface WriteFileOptions { - /** Defaults to `false`. If set to `true`, will append to a file instead of - * overwriting previous contents. */ - append?: boolean; - /** Sets the option to allow creating a new file, if one doesn't already - * exist at the specified path (defaults to `true`). */ - create?: boolean; - /** Permissions always applied to file. */ - mode?: number; - } - - /** Synchronously write `data` to the given `path`, by default creating a new - * file if needed, else overwriting. - * - * ```ts - * const encoder = new TextEncoder(); - * const data = encoder.encode("Hello world\n"); - * Deno.writeFileSync("hello1.txt", data); // overwrite "hello1.txt" or create it - * Deno.writeFileSync("hello2.txt", data, {create: false}); // only works if "hello2.txt" exists - * Deno.writeFileSync("hello3.txt", data, {mode: 0o777}); // set permissions on new file - * Deno.writeFileSync("hello4.txt", data, {append: true}); // add data to the end of the file - * ``` - * - * Requires `allow-write` permission, and `allow-read` if `options.create` is - * `false`. - */ - export function writeFileSync( - path: string, - data: Uint8Array, - options?: WriteFileOptions - ): void; - - /** Write `data` to the given `path`, by default creating a new file if needed, - * else overwriting. - * - * ```ts - * const encoder = new TextEncoder(); - * const data = encoder.encode("Hello world\n"); - * await Deno.writeFile("hello1.txt", data); // overwrite "hello1.txt" or create it - * await Deno.writeFile("hello2.txt", data, {create: false}); // only works if "hello2.txt" exists - * await Deno.writeFile("hello3.txt", data, {mode: 0o777}); // set permissions on new file - * await Deno.writeFile("hello4.txt", data, {append: true}); // add data to the end of the file - * ``` - * - * Requires `allow-write` permission, and `allow-read` if `options.create` is `false`. - */ - export function writeFile( - path: string, - data: Uint8Array, - options?: WriteFileOptions - ): Promise; - - /** Synchronously write string `data` to the given `path`, by default creating a new file if needed, - * else overwriting. - * - * ```ts - * await Deno.writeTextFileSync("hello1.txt", "Hello world\n"); // overwrite "hello1.txt" or create it - * ``` - * - * Requires `allow-write` permission, and `allow-read` if `options.create` is `false`. - */ - export function writeTextFileSync(path: string, data: string): void; - - /** Asynchronously write string `data` to the given `path`, by default creating a new file if needed, - * else overwriting. - * - * ```ts - * await Deno.writeTextFile("hello1.txt", "Hello world\n"); // overwrite "hello1.txt" or create it - * ``` - * - * Requires `allow-write` permission, and `allow-read` if `options.create` is `false`. - */ - export function writeTextFile(path: string, data: string): Promise; - - /** Synchronously truncates or extends the specified file, to reach the - * specified `len`. If `len` is not specified then the entire file contents - * are truncated. - * - * ```ts - * // truncate the entire file - * Deno.truncateSync("my_file.txt"); - * - * // truncate part of the file - * const file = Deno.makeTempFileSync(); - * Deno.writeFileSync(file, new TextEncoder().encode("Hello World")); - * Deno.truncateSync(file, 7); - * const data = Deno.readFileSync(file); - * console.log(new TextDecoder().decode(data)); - * ``` - * - * Requires `allow-write` permission. */ - export function truncateSync(name: string, len?: number): void; - - /** Truncates or extends the specified file, to reach the specified `len`. If - * `len` is not specified then the entire file contents are truncated. - * - * ```ts - * // truncate the entire file - * await Deno.truncate("my_file.txt"); - * - * // truncate part of the file - * const file = await Deno.makeTempFile(); - * await Deno.writeFile(file, new TextEncoder().encode("Hello World")); - * await Deno.truncate(file, 7); - * const data = await Deno.readFile(file); - * console.log(new TextDecoder().decode(data)); // "Hello W" - * ``` - * - * Requires `allow-write` permission. */ - export function truncate(name: string, len?: number): Promise; - - export interface NetAddr { - transport: 'tcp' | 'udp'; - hostname: string; - port: number; - } - - export interface UnixAddr { - transport: 'unix' | 'unixpacket'; - path: string; - } - - export type Addr = NetAddr | UnixAddr; - - /** A generic network listener for stream-oriented protocols. */ - export interface Listener extends AsyncIterable { - /** Waits for and resolves to the next connection to the `Listener`. */ - accept(): Promise; - /** Close closes the listener. Any pending accept promises will be rejected - * with errors. */ - close(): void; - /** Return the address of the `Listener`. */ - readonly addr: Addr; - - [Symbol.asyncIterator](): AsyncIterableIterator; - } - - export interface Conn extends Reader, Writer, Closer { - /** The local address of the connection. */ - readonly localAddr: Addr; - /** The remote address of the connection. */ - readonly remoteAddr: Addr; - /** The resource ID of the connection. */ - readonly rid: number; - /** Shuts down (`shutdown(2)`) the writing side of the TCP connection. Most - * callers should just use `close()`. - * - * **Unstable** because of lack of testing and because Deno.shutdown is also - * unstable. - * */ - closeWrite(): void; - } - - export interface ListenOptions { - /** The port to listen on. */ - port: number; - /** A literal IP address or host name that can be resolved to an IP address. - * If not specified, defaults to `0.0.0.0`. */ - hostname?: string; - } - - /** Listen announces on the local transport address. - * - * ```ts - * const listener1 = Deno.listen({ port: 80 }) - * const listener2 = Deno.listen({ hostname: "192.0.2.1", port: 80 }) - * const listener3 = Deno.listen({ hostname: "[2001:db8::1]", port: 80 }); - * const listener4 = Deno.listen({ hostname: "golang.org", port: 80, transport: "tcp" }); - * ``` - * - * Requires `allow-net` permission. */ - export function listen( - options: ListenOptions & { transport?: 'tcp' } - ): Listener; - - export interface ListenTlsOptions extends ListenOptions { - /** Server certificate file. */ - certFile: string; - /** Server public key file. */ - keyFile: string; - - transport?: 'tcp'; - } - - /** Listen announces on the local transport address over TLS (transport layer - * security). - * - * ```ts - * const lstnr = Deno.listenTls({ port: 443, certFile: "./server.crt", keyFile: "./server.key" }); - * ``` - * - * Requires `allow-net` permission. */ - export function listenTls(options: ListenTlsOptions): Listener; - - export interface ConnectOptions { - /** The port to connect to. */ - port: number; - /** A literal IP address or host name that can be resolved to an IP address. - * If not specified, defaults to `127.0.0.1`. */ - hostname?: string; - transport?: 'tcp'; - } - - /** - * Connects to the hostname (default is "127.0.0.1") and port on the named - * transport (default is "tcp"), and resolves to the connection (`Conn`). - * - * ```ts - * const conn1 = await Deno.connect({ port: 80 }); - * const conn2 = await Deno.connect({ hostname: "192.0.2.1", port: 80 }); - * const conn3 = await Deno.connect({ hostname: "[2001:db8::1]", port: 80 }); - * const conn4 = await Deno.connect({ hostname: "golang.org", port: 80, transport: "tcp" }); - * const conn5 = await Deno.connect({ path: "/foo/bar.sock", transport: "unix" }); - * ``` - * - * Requires `allow-net` permission for "tcp" and `allow-read` for unix. */ - export function connect(options: ConnectOptions): Promise; - - export interface ConnectTlsOptions { - /** The port to connect to. */ - port: number; - /** A literal IP address or host name that can be resolved to an IP address. - * If not specified, defaults to `127.0.0.1`. */ - hostname?: string; - /** Server certificate file. */ - certFile?: string; - } - - /** Establishes a secure connection over TLS (transport layer security) using - * an optional cert file, hostname (default is "127.0.0.1") and port. The - * cert file is optional and if not included Mozilla's root certificates will - * be used (see also https://github.com/ctz/webpki-roots for specifics) - * - * ```ts - * const conn1 = await Deno.connectTls({ port: 80 }); - * const conn2 = await Deno.connectTls({ certFile: "./certs/my_custom_root_CA.pem", hostname: "192.0.2.1", port: 80 }); - * const conn3 = await Deno.connectTls({ hostname: "[2001:db8::1]", port: 80 }); - * const conn4 = await Deno.connectTls({ certFile: "./certs/my_custom_root_CA.pem", hostname: "golang.org", port: 80}); - * ``` - * - * Requires `allow-net` permission. - */ - export function connectTls(options: ConnectTlsOptions): Promise; - - export interface Metrics { - opsDispatched: number; - opsDispatchedSync: number; - opsDispatchedAsync: number; - opsDispatchedAsyncUnref: number; - opsCompleted: number; - opsCompletedSync: number; - opsCompletedAsync: number; - opsCompletedAsyncUnref: number; - bytesSentControl: number; - bytesSentData: number; - bytesReceived: number; - } - - /** Receive metrics from the privileged side of Deno. This is primarily used - * in the development of Deno. 'Ops', also called 'bindings', are the go-between - * between Deno JavaScript and Deno Rust. - * - * > console.table(Deno.metrics()) - * ┌─────────────────────────┬────────┐ - * │ (index) │ Values │ - * ├─────────────────────────┼────────┤ - * │ opsDispatched │ 3 │ - * │ opsDispatchedSync │ 2 │ - * │ opsDispatchedAsync │ 1 │ - * │ opsDispatchedAsyncUnref │ 0 │ - * │ opsCompleted │ 3 │ - * │ opsCompletedSync │ 2 │ - * │ opsCompletedAsync │ 1 │ - * │ opsCompletedAsyncUnref │ 0 │ - * │ bytesSentControl │ 73 │ - * │ bytesSentData │ 0 │ - * │ bytesReceived │ 375 │ - * └─────────────────────────┴────────┘ - */ - export function metrics(): Metrics; - - interface ResourceMap { - [rid: number]: any; - } - - /** Returns a map of open resource ids (rid) along with their string - * representations. This is an internal API and as such resource - * representation has `any` type; that means it can change any time. - * - * ```ts - * console.log(Deno.resources()); - * // { 0: "stdin", 1: "stdout", 2: "stderr" } - * Deno.openSync('../test.file'); - * console.log(Deno.resources()); - * // { 0: "stdin", 1: "stdout", 2: "stderr", 3: "fsFile" } - * ``` - */ - export function resources(): ResourceMap; - - export interface FsEvent { - kind: 'any' | 'access' | 'create' | 'modify' | 'remove'; - paths: string[]; - } - - /** Watch for file system events against one or more `paths`, which can be files - * or directories. These paths must exist already. One user action (e.g. - * `touch test.file`) can generate multiple file system events. Likewise, - * one user action can result in multiple file paths in one event (e.g. `mv - * old_name.txt new_name.txt`). Recursive option is `true` by default and, - * for directories, will watch the specified directory and all sub directories. - * Note that the exact ordering of the events can vary between operating systems. - * - * ```ts - * const watcher = Deno.watchFs("/"); - * for await (const event of watcher) { - * console.log(">>>> event", event); - * // { kind: "create", paths: [ "/foo.txt" ] } - * } - *``` - * - * Requires `allow-read` permission. - */ - export function watchFs( - paths: string | string[], - options?: { recursive: boolean } - ): AsyncIterableIterator; - - export class Process { - readonly rid: number; - readonly pid: number; - readonly stdin?: Writer & Closer; - readonly stdout?: Reader & Closer; - readonly stderr?: Reader & Closer; - /** Resolves to the current status of the process. */ - status(): Promise; - /** Buffer the stdout until EOF and return it as `Uint8Array`. - * - * You must set stdout to `"piped"` when creating the process. - * - * This calls `close()` on stdout after its done. */ - output(): Promise; - /** Buffer the stderr until EOF and return it as `Uint8Array`. - * - * You must set stderr to `"piped"` when creating the process. - * - * This calls `close()` on stderr after its done. */ - stderrOutput(): Promise; - close(): void; - - /** **UNSTABLE**: The `signo` argument may change to require the Deno.Signal - * enum. - * - * Send a signal to process. This functionality currently only works on - * Linux and Mac OS. - */ - kill(signo: number): void; - } - - export type ProcessStatus = - | { - success: true; - code: 0; - signal?: undefined; - } - | { - success: false; - code: number; - signal?: number; - }; - - export interface RunOptions { - /** Arguments to pass. Note, the first element needs to be a path to the - * binary */ - cmd: string[]; - cwd?: string; - env?: { - [key: string]: string; - }; - stdout?: 'inherit' | 'piped' | 'null' | number; - stderr?: 'inherit' | 'piped' | 'null' | number; - stdin?: 'inherit' | 'piped' | 'null' | number; - } - - /** Spawns new subprocess. RunOptions must contain at a minimum the `opt.cmd`, - * an array of program arguments, the first of which is the binary. - * - * ```ts - * const p = Deno.run({ - * cmd: ["echo", "hello"], - * }); - * ``` - * - * Subprocess uses same working directory as parent process unless `opt.cwd` - * is specified. - * - * Environmental variables for subprocess can be specified using `opt.env` - * mapping. - * - * By default subprocess inherits stdio of parent process. To change that - * `opt.stdout`, `opt.stderr` and `opt.stdin` can be specified independently - - * they can be set to either an rid of open file or set to "inherit" "piped" - * or "null": - * - * `"inherit"` The default if unspecified. The child inherits from the - * corresponding parent descriptor. - * - * `"piped"` A new pipe should be arranged to connect the parent and child - * sub-processes. - * - * `"null"` This stream will be ignored. This is the equivalent of attaching - * the stream to `/dev/null`. - * - * Details of the spawned process are returned. - * - * Requires `allow-run` permission. */ - export function run(opt: RunOptions): Process; - - interface InspectOptions { - depth?: number; - } - - /** Converts the input into a string that has the same format as printed by - * `console.log()`. - * - * ```ts - * const obj = {}; - * obj.propA = 10; - * obj.propB = "hello" - * const objAsString = Deno.inspect(obj); // { propA: 10, propB: "hello" } - * console.log(obj); // prints same value as objAsString, e.g. { propA: 10, propB: "hello" } - * ``` - * - * You can also register custom inspect functions, via the `customInspect` Deno - * symbol on objects, to control and customize the output. - * - * ```ts - * class A { - * x = 10; - * y = "hello"; - * [Deno.customInspect](): string { - * return "x=" + this.x + ", y=" + this.y; - * } - * } - * ``` - * - * const inStringFormat = Deno.inspect(new A()); // "x=10, y=hello" - * console.log(inStringFormat); // prints "x=10, y=hello" - * - * Finally, a number of output options are also available. - * - * const out = Deno.inspect(obj, {showHidden: true, depth: 4, colors: true, indentLevel: 2}); - * - */ - export function inspect(value: unknown, options?: InspectOptions): string; - - /** Build related information. */ - export const build: { - /** The LLVM target triple */ - target: string; - /** Instruction set architecture */ - arch: 'x86_64'; - /** Operating system */ - os: 'darwin' | 'linux' | 'windows'; - /** Computer vendor */ - vendor: string; - /** Optional environment */ - env?: string; - }; - - interface Version { - deno: string; - v8: string; - typescript: string; - } - /** Version related information. */ - export const version: Version; - - /** Returns the script arguments to the program. If for example we run a - * program: - * - * deno run --allow-read https://deno.land/std/examples/cat.ts /etc/passwd - * - * Then `Deno.args` will contain: - * - * [ "/etc/passwd" ] - */ - export const args: string[]; - - /** A symbol which can be used as a key for a custom method which will be - * called when `Deno.inspect()` is called, or when the object is logged to - * the console. */ - export const customInspect: unique symbol; -} - -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, no-var */ - -/// -/// - -// This follows the WebIDL at: https://webassembly.github.io/spec/js-api/ -// and: https://webassembly.github.io/spec/web-api/ -declare namespace WebAssembly { - interface WebAssemblyInstantiatedSource { - module: Module; - instance: Instance; - } - - /** Compiles a `WebAssembly.Module` from WebAssembly binary code. This - * function is useful if it is necessary to a compile a module before it can - * be instantiated (otherwise, the `WebAssembly.instantiate()` function - * should be used). */ - function compile(bufferSource: BufferSource): Promise; - - /** Compiles a `WebAssembly.Module` directly from a streamed underlying - * source. This function is useful if it is necessary to a compile a module - * before it can be instantiated (otherwise, the - * `WebAssembly.instantiateStreaming()` function should be used). */ - function compileStreaming(source: Promise): Promise; - - /** Takes the WebAssembly binary code, in the form of a typed array or - * `ArrayBuffer`, and performs both compilation and instantiation in one step. - * The returned `Promise` resolves to both a compiled `WebAssembly.Module` and - * its first `WebAssembly.Instance`. */ - function instantiate( - bufferSource: BufferSource, - importObject?: object - ): Promise; - - /** Takes an already-compiled `WebAssembly.Module` and returns a `Promise` - * that resolves to an `Instance` of that `Module`. This overload is useful if - * the `Module` has already been compiled. */ - function instantiate( - module: Module, - importObject?: object - ): Promise; - - /** Compiles and instantiates a WebAssembly module directly from a streamed - * underlying source. This is the most efficient, optimized way to load wasm - * code. */ - function instantiateStreaming( - source: Promise, - importObject?: object - ): Promise; - - /** Validates a given typed array of WebAssembly binary code, returning - * whether the bytes form a valid wasm module (`true`) or not (`false`). */ - function validate(bufferSource: BufferSource): boolean; - - type ImportExportKind = 'function' | 'table' | 'memory' | 'global'; - - interface ModuleExportDescriptor { - name: string; - kind: ImportExportKind; - } - interface ModuleImportDescriptor { - module: string; - name: string; - kind: ImportExportKind; - } - - class Module { - constructor(bufferSource: BufferSource); - - /** Given a `Module` and string, returns a copy of the contents of all - * custom sections in the module with the given string name. */ - static customSections( - moduleObject: Module, - sectionName: string - ): ArrayBuffer; - - /** Given a `Module`, returns an array containing descriptions of all the - * declared exports. */ - static exports(moduleObject: Module): ModuleExportDescriptor[]; - - /** Given a `Module`, returns an array containing descriptions of all the - * declared imports. */ - static imports(moduleObject: Module): ModuleImportDescriptor[]; - } - - class Instance { - constructor(module: Module, importObject?: object); - - /** An object containing as its members all the functions exported from the - * WebAssembly module instance, to allow them to be accessed and used by - * JavaScript. */ - readonly exports: T; - } - - interface MemoryDescriptor { - initial: number; - maximum?: number; - } - - class Memory { - constructor(descriptor: MemoryDescriptor); - - /** An accessor property that returns the buffer contained in the memory. */ - readonly buffer: ArrayBuffer; - - /** Increases the size of the memory instance by a specified number of - * WebAssembly pages (each one is 64KB in size). */ - grow(delta: number): number; - } - - type TableKind = 'anyfunc'; - - interface TableDescriptor { - element: TableKind; - initial: number; - maximum?: number; - } - - class Table { - constructor(descriptor: TableDescriptor); - - /** Returns the length of the table, i.e. the number of elements. */ - readonly length: number; - - /** Accessor function — gets the element stored at a given index. */ - get(index: number): (...args: any[]) => any; - - /** Increases the size of the Table instance by a specified number of - * elements. */ - grow(delta: number): number; - - /** Sets an element stored at a given index to a given value. */ - set(index: number, value: (...args: any[]) => any): void; - } - - type ValueType = 'i32' | 'i64' | 'f32' | 'f64'; - - interface GlobalDescriptor { - value: ValueType; - mutable?: boolean; - } - - /** Represents a global variable instance, accessible from both JavaScript and - * importable/exportable across one or more `WebAssembly.Module` instances. - * This allows dynamic linking of multiple modules. */ - class Global { - constructor(descriptor: GlobalDescriptor, value?: any); - - /** Old-style method that returns the value contained inside the global - * variable. */ - valueOf(): any; - - /** The value contained inside the global variable — this can be used to - * directly set and get the global's value. */ - value: any; - } - - /** Indicates an error during WebAssembly decoding or validation */ - class CompileError extends Error { - constructor(message: string, fileName?: string, lineNumber?: string); - } - - /** Indicates an error during module instantiation (besides traps from the - * start function). */ - class LinkError extends Error { - constructor(message: string, fileName?: string, lineNumber?: string); - } - - /** Is thrown whenever WebAssembly specifies a trap. */ - class RuntimeError extends Error { - constructor(message: string, fileName?: string, lineNumber?: string); - } -} - -/** Sets a timer which executes a function once after the timer expires. */ -declare function setTimeout( - cb: (...args: any[]) => void, - delay?: number, - ...args: any[] -): number; - -/** Repeatedly calls a function , with a fixed time delay between each call. */ -declare function setInterval( - cb: (...args: any[]) => void, - delay?: number, - ...args: any[] -): number; -declare function clearTimeout(id?: number): void; -declare function clearInterval(id?: number): void; -declare function queueMicrotask(func: Function): void; - -declare var console: Console; -declare var crypto: Crypto; - -declare function addEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: boolean | AddEventListenerOptions | undefined -): void; - -declare function dispatchEvent(event: Event): boolean; - -declare function removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: boolean | EventListenerOptions | undefined -): void; - -declare interface ImportMeta { - url: string; - main: boolean; -} - -interface DomIterable { - keys(): IterableIterator; - values(): IterableIterator; - entries(): IterableIterator<[K, V]>; - [Symbol.iterator](): IterableIterator<[K, V]>; - forEach( - callback: (value: V, key: K, parent: this) => void, - thisArg?: any - ): void; -} - -interface ReadableStreamReadDoneResult { - done: true; - value?: T; -} - -interface ReadableStreamReadValueResult { - done: false; - value: T; -} - -type ReadableStreamReadResult = - | ReadableStreamReadValueResult - | ReadableStreamReadDoneResult; - -interface ReadableStreamDefaultReader { - readonly closed: Promise; - cancel(reason?: any): Promise; - read(): Promise>; - releaseLock(): void; -} - -interface ReadableStreamReader { - cancel(): Promise; - read(): Promise>; - releaseLock(): void; -} - -interface ReadableByteStreamControllerCallback { - (controller: ReadableByteStreamController): void | PromiseLike; -} - -interface UnderlyingByteSource { - autoAllocateChunkSize?: number; - cancel?: ReadableStreamErrorCallback; - pull?: ReadableByteStreamControllerCallback; - start?: ReadableByteStreamControllerCallback; - type: 'bytes'; -} - -interface UnderlyingSource { - cancel?: ReadableStreamErrorCallback; - pull?: ReadableStreamDefaultControllerCallback; - start?: ReadableStreamDefaultControllerCallback; - type?: undefined; -} - -interface ReadableStreamErrorCallback { - (reason: any): void | PromiseLike; -} - -interface ReadableStreamDefaultControllerCallback { - (controller: ReadableStreamDefaultController): void | PromiseLike; -} - -interface ReadableStreamDefaultController { - readonly desiredSize: number | null; - close(): void; - enqueue(chunk: R): void; - error(error?: any): void; -} - -interface ReadableByteStreamController { - readonly byobRequest: undefined; - readonly desiredSize: number | null; - close(): void; - enqueue(chunk: ArrayBufferView): void; - error(error?: any): void; -} - -interface PipeOptions { - preventAbort?: boolean; - preventCancel?: boolean; - preventClose?: boolean; - signal?: AbortSignal; -} - -interface QueuingStrategySizeCallback { - (chunk: T): number; -} - -interface QueuingStrategy { - highWaterMark?: number; - size?: QueuingStrategySizeCallback; -} - -/** This Streams API interface provides a built-in byte length queuing strategy - * that can be used when constructing streams. */ -declare class CountQueuingStrategy implements QueuingStrategy { - constructor(options: { highWaterMark: number }); - highWaterMark: number; - size(chunk: any): 1; -} - -declare class ByteLengthQueuingStrategy - implements QueuingStrategy { - constructor(options: { highWaterMark: number }); - highWaterMark: number; - size(chunk: ArrayBufferView): number; -} - -/** This Streams API interface represents a readable stream of byte data. The - * Fetch API offers a concrete instance of a ReadableStream through the body - * property of a Response object. */ -interface ReadableStream { - readonly locked: boolean; - cancel(reason?: any): Promise; - getIterator(options?: { preventCancel?: boolean }): AsyncIterableIterator; - // getReader(options: { mode: "byob" }): ReadableStreamBYOBReader; - getReader(): ReadableStreamDefaultReader; - pipeThrough( - { - writable, - readable, - }: { - writable: WritableStream; - readable: ReadableStream; - }, - options?: PipeOptions - ): ReadableStream; - pipeTo(dest: WritableStream, options?: PipeOptions): Promise; - tee(): [ReadableStream, ReadableStream]; - [Symbol.asyncIterator](options?: { - preventCancel?: boolean; - }): AsyncIterableIterator; -} - -declare var ReadableStream: { - prototype: ReadableStream; - new ( - underlyingSource: UnderlyingByteSource, - strategy?: { highWaterMark?: number; size?: undefined } - ): ReadableStream; - new ( - underlyingSource?: UnderlyingSource, - strategy?: QueuingStrategy - ): ReadableStream; -}; - -interface WritableStreamDefaultControllerCloseCallback { - (): void | PromiseLike; -} - -interface WritableStreamDefaultControllerStartCallback { - (controller: WritableStreamDefaultController): void | PromiseLike; -} - -interface WritableStreamDefaultControllerWriteCallback { - ( - chunk: W, - controller: WritableStreamDefaultController - ): void | PromiseLike; -} - -interface WritableStreamErrorCallback { - (reason: any): void | PromiseLike; -} - -interface UnderlyingSink { - abort?: WritableStreamErrorCallback; - close?: WritableStreamDefaultControllerCloseCallback; - start?: WritableStreamDefaultControllerStartCallback; - type?: undefined; - write?: WritableStreamDefaultControllerWriteCallback; -} - -/** This Streams API interface provides a standard abstraction for writing - * streaming data to a destination, known as a sink. This object comes with - * built-in backpressure and queuing. */ -declare class WritableStream { - constructor( - underlyingSink?: UnderlyingSink, - strategy?: QueuingStrategy - ); - readonly locked: boolean; - abort(reason?: any): Promise; - close(): Promise; - getWriter(): WritableStreamDefaultWriter; -} - -/** This Streams API interface represents a controller allowing control of a - * WritableStream's state. When constructing a WritableStream, the underlying - * sink is given a corresponding WritableStreamDefaultController instance to - * manipulate. */ -interface WritableStreamDefaultController { - error(error?: any): void; -} - -/** This Streams API interface is the object returned by - * WritableStream.getWriter() and once created locks the < writer to the - * WritableStream ensuring that no other streams can write to the underlying - * sink. */ -interface WritableStreamDefaultWriter { - readonly closed: Promise; - readonly desiredSize: number | null; - readonly ready: Promise; - abort(reason?: any): Promise; - close(): Promise; - releaseLock(): void; - write(chunk: W): Promise; -} - -declare class TransformStream { - constructor( - transformer?: Transformer, - writableStrategy?: QueuingStrategy, - readableStrategy?: QueuingStrategy - ); - readonly readable: ReadableStream; - readonly writable: WritableStream; -} - -interface TransformStreamDefaultController { - readonly desiredSize: number | null; - enqueue(chunk: O): void; - error(reason?: any): void; - terminate(): void; -} - -interface Transformer { - flush?: TransformStreamDefaultControllerCallback; - readableType?: undefined; - start?: TransformStreamDefaultControllerCallback; - transform?: TransformStreamDefaultControllerTransformCallback; - writableType?: undefined; -} - -interface TransformStreamDefaultControllerCallback { - (controller: TransformStreamDefaultController): void | PromiseLike; -} - -interface TransformStreamDefaultControllerTransformCallback { - ( - chunk: I, - controller: TransformStreamDefaultController - ): void | PromiseLike; -} - -interface DOMStringList { - /** Returns the number of strings in strings. */ - readonly length: number; - /** Returns true if strings contains string, and false otherwise. */ - contains(string: string): boolean; - /** Returns the string with index index from strings. */ - item(index: number): string | null; - [index: number]: string; -} - -declare class DOMException extends Error { - constructor(message?: string, name?: string); - readonly name: string; - readonly message: string; -} - -type BufferSource = ArrayBufferView | ArrayBuffer; -type BlobPart = BufferSource | Blob | string; - -interface BlobPropertyBag { - type?: string; - ending?: 'transparent' | 'native'; -} - -/** A file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system. */ -interface Blob { - readonly size: number; - readonly type: string; - arrayBuffer(): Promise; - slice(start?: number, end?: number, contentType?: string): Blob; - stream(): ReadableStream; - text(): Promise; -} - -declare const Blob: { - prototype: Blob; - new (blobParts?: BlobPart[], options?: BlobPropertyBag): Blob; -}; - -interface FilePropertyBag extends BlobPropertyBag { - lastModified?: number; -} - -/** Provides information about files and allows JavaScript in a web page to - * access their content. */ -interface File extends Blob { - readonly lastModified: number; - readonly name: string; -} - -declare const File: { - prototype: File; - new (fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File; -}; - -declare const isConsoleInstance: unique symbol; - -declare class Console { - indentLevel: number; - [isConsoleInstance]: boolean; - /** Writes the arguments to stdout */ - log: (...args: unknown[]) => void; - /** Writes the arguments to stdout */ - debug: (...args: unknown[]) => void; - /** Writes the arguments to stdout */ - info: (...args: unknown[]) => void; - /** Writes the properties of the supplied `obj` to stdout */ - dir: ( - obj: unknown, - options?: Partial<{ - depth: number; - indentLevel: number; - }> - ) => void; - - /** From MDN: - * Displays an interactive tree of the descendant elements of - * the specified XML/HTML element. If it is not possible to display - * as an element the JavaScript Object view is shown instead. - * The output is presented as a hierarchical listing of expandable - * nodes that let you see the contents of child nodes. - * - * Since we write to stdout, we can't display anything interactive - * we just fall back to `console.dir`. - */ - dirxml: ( - obj: unknown, - options?: Partial<{ - showHidden: boolean; - depth: number; - colors: boolean; - indentLevel: number; - }> - ) => void; - - /** Writes the arguments to stdout */ - warn: (...args: unknown[]) => void; - /** Writes the arguments to stdout */ - error: (...args: unknown[]) => void; - /** Writes an error message to stdout if the assertion is `false`. If the - * assertion is `true`, nothing happens. - * - * ref: https://console.spec.whatwg.org/#assert - */ - assert: (condition?: boolean, ...args: unknown[]) => void; - count: (label?: string) => void; - countReset: (label?: string) => void; - table: (data: unknown, properties?: string[] | undefined) => void; - time: (label?: string) => void; - timeLog: (label?: string, ...args: unknown[]) => void; - timeEnd: (label?: string) => void; - group: (...label: unknown[]) => void; - groupCollapsed: (...label: unknown[]) => void; - groupEnd: () => void; - clear: () => void; - trace: (...args: unknown[]) => void; - static [Symbol.hasInstance](instance: Console): boolean; -} - -declare interface Crypto { - readonly subtle: null; - getRandomValues< - T extends - | Int8Array - | Int16Array - | Int32Array - | Uint8Array - | Uint16Array - | Uint32Array - | Uint8ClampedArray - | Float32Array - | Float64Array - | DataView - | null - >( - array: T - ): T; -} - -type FormDataEntryValue = File | string; - -/** Provides a way to easily construct a set of key/value pairs representing - * form fields and their values, which can then be easily sent using the - * XMLHttpRequest.send() method. It uses the same format a form would use if the - * encoding type were set to "multipart/form-data". */ -interface FormData extends DomIterable { - append(name: string, value: string | Blob, fileName?: string): void; - delete(name: string): void; - get(name: string): FormDataEntryValue | null; - getAll(name: string): FormDataEntryValue[]; - has(name: string): boolean; - set(name: string, value: string | Blob, fileName?: string): void; -} - -declare const FormData: { - prototype: FormData; - // TODO(ry) FormData constructor is non-standard. - // new(form?: HTMLFormElement): FormData; - new (): FormData; -}; - -interface Body { - /** A simple getter used to expose a `ReadableStream` of the body contents. */ - readonly body: ReadableStream | null; - /** Stores a `Boolean` that declares whether the body has been used in a - * response yet. - */ - readonly bodyUsed: boolean; - /** Takes a `Response` stream and reads it to completion. It returns a promise - * that resolves with an `ArrayBuffer`. - */ - arrayBuffer(): Promise; - /** Takes a `Response` stream and reads it to completion. It returns a promise - * that resolves with a `Blob`. - */ - blob(): Promise; - /** Takes a `Response` stream and reads it to completion. It returns a promise - * that resolves with a `FormData` object. - */ - formData(): Promise; - /** Takes a `Response` stream and reads it to completion. It returns a promise - * that resolves with the result of parsing the body text as JSON. - */ - json(): Promise; - /** Takes a `Response` stream and reads it to completion. It returns a promise - * that resolves with a `USVString` (text). - */ - text(): Promise; -} - -type HeadersInit = Headers | string[][] | Record; - -/** This Fetch API interface allows you to perform various actions on HTTP - * request and response headers. These actions include retrieving, setting, - * adding to, and removing. A Headers object has an associated header list, - * which is initially empty and consists of zero or more name and value pairs. - *  You can add to this using methods like append() (see Examples.) In all - * methods of this interface, header names are matched by case-insensitive byte - * sequence. */ -interface Headers { - append(name: string, value: string): void; - delete(name: string): void; - get(name: string): string | null; - has(name: string): boolean; - set(name: string, value: string): void; - forEach( - callbackfn: (value: string, key: string, parent: Headers) => void, - thisArg?: any - ): void; -} - -interface Headers extends DomIterable { - /** Appends a new value onto an existing header inside a `Headers` object, or - * adds the header if it does not already exist. - */ - append(name: string, value: string): void; - /** Deletes a header from a `Headers` object. */ - delete(name: string): void; - /** Returns an iterator allowing to go through all key/value pairs - * contained in this Headers object. The both the key and value of each pairs - * are ByteString objects. - */ - entries(): IterableIterator<[string, string]>; - /** Returns a `ByteString` sequence of all the values of a header within a - * `Headers` object with a given name. - */ - get(name: string): string | null; - /** Returns a boolean stating whether a `Headers` object contains a certain - * header. - */ - has(name: string): boolean; - /** Returns an iterator allowing to go through all keys contained in - * this Headers object. The keys are ByteString objects. - */ - keys(): IterableIterator; - /** Sets a new value for an existing header inside a Headers object, or adds - * the header if it does not already exist. - */ - set(name: string, value: string): void; - /** Returns an iterator allowing to go through all values contained in - * this Headers object. The values are ByteString objects. - */ - values(): IterableIterator; - forEach( - callbackfn: (value: string, key: string, parent: this) => void, - thisArg?: any - ): void; - /** The Symbol.iterator well-known symbol specifies the default - * iterator for this Headers object - */ - [Symbol.iterator](): IterableIterator<[string, string]>; -} - -declare const Headers: { - prototype: Headers; - new (init?: HeadersInit): Headers; -}; - -type RequestInfo = Request | string; -type RequestCache = - | 'default' - | 'force-cache' - | 'no-cache' - | 'no-store' - | 'only-if-cached' - | 'reload'; -type RequestCredentials = 'include' | 'omit' | 'same-origin'; -type RequestMode = 'cors' | 'navigate' | 'no-cors' | 'same-origin'; -type RequestRedirect = 'error' | 'follow' | 'manual'; -type ReferrerPolicy = - | '' - | 'no-referrer' - | 'no-referrer-when-downgrade' - | 'origin' - | 'origin-when-cross-origin' - | 'same-origin' - | 'strict-origin' - | 'strict-origin-when-cross-origin' - | 'unsafe-url'; -type BodyInit = - | Blob - | BufferSource - | FormData - | URLSearchParams - | ReadableStream - | string; -type RequestDestination = - | '' - | 'audio' - | 'audioworklet' - | 'document' - | 'embed' - | 'font' - | 'image' - | 'manifest' - | 'object' - | 'paintworklet' - | 'report' - | 'script' - | 'sharedworker' - | 'style' - | 'track' - | 'video' - | 'worker' - | 'xslt'; - -interface RequestInit { - /** - * A BodyInit object or null to set request's body. - */ - body?: BodyInit | null; - /** - * A string indicating how the request will interact with the browser's cache - * to set request's cache. - */ - cache?: RequestCache; - /** - * A string indicating whether credentials will be sent with the request - * always, never, or only when sent to a same-origin URL. Sets request's - * credentials. - */ - credentials?: RequestCredentials; - /** - * A Headers object, an object literal, or an array of two-item arrays to set - * request's headers. - */ - headers?: HeadersInit; - /** - * A cryptographic hash of the resource to be fetched by request. Sets - * request's integrity. - */ - integrity?: string; - /** - * A boolean to set request's keepalive. - */ - keepalive?: boolean; - /** - * A string to set request's method. - */ - method?: string; - /** - * A string to indicate whether the request will use CORS, or will be - * restricted to same-origin URLs. Sets request's mode. - */ - mode?: RequestMode; - /** - * A string indicating whether request follows redirects, results in an error - * upon encountering a redirect, or returns the redirect (in an opaque - * fashion). Sets request's redirect. - */ - redirect?: RequestRedirect; - /** - * A string whose value is a same-origin URL, "about:client", or the empty - * string, to set request's referrer. - */ - referrer?: string; - /** - * A referrer policy to set request's referrerPolicy. - */ - referrerPolicy?: ReferrerPolicy; - /** - * An AbortSignal to set request's signal. - */ - signal?: AbortSignal | null; - /** - * Can only be null. Used to disassociate request from any Window. - */ - window?: any; -} - -/** This Fetch API interface represents a resource request. */ -interface Request extends Body { - /** - * Returns the cache mode associated with request, which is a string - * indicating how the request will interact with the browser's cache when - * fetching. - */ - readonly cache: RequestCache; - /** - * Returns the credentials mode associated with request, which is a string - * indicating whether credentials will be sent with the request always, never, - * or only when sent to a same-origin URL. - */ - readonly credentials: RequestCredentials; - /** - * Returns the kind of resource requested by request, e.g., "document" or "script". - */ - readonly destination: RequestDestination; - /** - * Returns a Headers object consisting of the headers associated with request. - * Note that headers added in the network layer by the user agent will not be - * accounted for in this object, e.g., the "Host" header. - */ - readonly headers: Headers; - /** - * Returns request's subresource integrity metadata, which is a cryptographic - * hash of the resource being fetched. Its value consists of multiple hashes - * separated by whitespace. [SRI] - */ - readonly integrity: string; - /** - * Returns a boolean indicating whether or not request is for a history - * navigation (a.k.a. back-forward navigation). - */ - readonly isHistoryNavigation: boolean; - /** - * Returns a boolean indicating whether or not request is for a reload - * navigation. - */ - readonly isReloadNavigation: boolean; - /** - * Returns a boolean indicating whether or not request can outlive the global - * in which it was created. - */ - readonly keepalive: boolean; - /** - * Returns request's HTTP method, which is "GET" by default. - */ - readonly method: string; - /** - * Returns the mode associated with request, which is a string indicating - * whether the request will use CORS, or will be restricted to same-origin - * URLs. - */ - readonly mode: RequestMode; - /** - * Returns the redirect mode associated with request, which is a string - * indicating how redirects for the request will be handled during fetching. A - * request will follow redirects by default. - */ - readonly redirect: RequestRedirect; - /** - * Returns the referrer of request. Its value can be a same-origin URL if - * explicitly set in init, the empty string to indicate no referrer, and - * "about:client" when defaulting to the global's default. This is used during - * fetching to determine the value of the `Referer` header of the request - * being made. - */ - readonly referrer: string; - /** - * Returns the referrer policy associated with request. This is used during - * fetching to compute the value of the request's referrer. - */ - readonly referrerPolicy: ReferrerPolicy; - /** - * Returns the signal associated with request, which is an AbortSignal object - * indicating whether or not request has been aborted, and its abort event - * handler. - */ - readonly signal: AbortSignal; - /** - * Returns the URL of request as a string. - */ - readonly url: string; - clone(): Request; -} - -declare const Request: { - prototype: Request; - new (input: RequestInfo, init?: RequestInit): Request; -}; - -type ResponseType = - | 'basic' - | 'cors' - | 'default' - | 'error' - | 'opaque' - | 'opaqueredirect'; - -/** This Fetch API interface represents the response to a request. */ -interface Response extends Body { - readonly headers: Headers; - readonly ok: boolean; - readonly redirected: boolean; - readonly status: number; - readonly statusText: string; - readonly trailer: Promise; - readonly type: ResponseType; - readonly url: string; - clone(): Response; -} - -declare const Response: { - prototype: Response; - - // TODO(#4667) Response constructor is non-standard. - // new(body?: BodyInit | null, init?: ResponseInit): Response; - new ( - url: string, - status: number, - statusText: string, - headersList: Array<[string, string]>, - rid: number, - redirected_: boolean, - type_?: null | ResponseType, - body_?: null | Body - ): Response; - - error(): Response; - redirect(url: string, status?: number): Response; -}; - -/** Fetch a resource from the network. */ -declare function fetch( - input: Request | URL | string, - init?: RequestInit -): Promise; - -declare function atob(s: string): string; - -/** Creates a base-64 ASCII string from the input string. */ -declare function btoa(s: string): string; - -declare class TextDecoder { - /** Returns encoding's name, lowercased. */ - readonly encoding: string; - /** Returns `true` if error mode is "fatal", and `false` otherwise. */ - readonly fatal: boolean; - /** Returns `true` if ignore BOM flag is set, and `false` otherwise. */ - readonly ignoreBOM = false; - constructor( - label?: string, - options?: { fatal?: boolean; ignoreBOM?: boolean } - ); - /** Returns the result of running encoding's decoder. */ - decode(input?: BufferSource, options?: { stream?: false }): string; - readonly [Symbol.toStringTag]: string; -} - -declare class TextEncoder { - /** Returns "utf-8". */ - readonly encoding = 'utf-8'; - /** Returns the result of running UTF-8's encoder. */ - encode(input?: string): Uint8Array; - encodeInto( - input: string, - dest: Uint8Array - ): { read: number; written: number }; - readonly [Symbol.toStringTag]: string; -} - -interface URLSearchParams { - /** Appends a specified key/value pair as a new search parameter. - * - * ```ts - * let searchParams = new URLSearchParams(); - * searchParams.append('name', 'first'); - * searchParams.append('name', 'second'); - * ``` - */ - append(name: string, value: string): void; - - /** Deletes the given search parameter and its associated value, - * from the list of all search parameters. - * - * ```ts - * let searchParams = new URLSearchParams([['name', 'value']]); - * searchParams.delete('name'); - * ``` - */ - delete(name: string): void; - - /** Returns all the values associated with a given search parameter - * as an array. - * - * ```ts - * searchParams.getAll('name'); - * ``` - */ - getAll(name: string): string[]; - - /** Returns the first value associated to the given search parameter. - * - * ```ts - * searchParams.get('name'); - * ``` - */ - get(name: string): string | null; - - /** Returns a Boolean that indicates whether a parameter with the - * specified name exists. - * - * ```ts - * searchParams.has('name'); - * ``` - */ - has(name: string): boolean; - - /** Sets the value associated with a given search parameter to the - * given value. If there were several matching values, this method - * deletes the others. If the search parameter doesn't exist, this - * method creates it. - * - * ```ts - * searchParams.set('name', 'value'); - * ``` - */ - set(name: string, value: string): void; - - /** Sort all key/value pairs contained in this object in place and - * return undefined. The sort order is according to Unicode code - * points of the keys. - * - * ```ts - * searchParams.sort(); - * ``` - */ - sort(): void; - - /** Calls a function for each element contained in this object in - * place and return undefined. Optionally accepts an object to use - * as this when executing callback as second argument. - * - * ```ts - * const params = new URLSearchParams([["a", "b"], ["c", "d"]]); - * params.forEach((value, key, parent) => { - * console.log(value, key, parent); - * }); - * ``` - * - */ - forEach( - callbackfn: (value: string, key: string, parent: this) => void, - thisArg?: any - ): void; - - /** Returns an iterator allowing to go through all keys contained - * in this object. - * - * ```ts - * const params = new URLSearchParams([["a", "b"], ["c", "d"]]); - * for (const key of params.keys()) { - * console.log(key); - * } - * ``` - */ - keys(): IterableIterator; - - /** Returns an iterator allowing to go through all values contained - * in this object. - * - * ```ts - * const params = new URLSearchParams([["a", "b"], ["c", "d"]]); - * for (const value of params.values()) { - * console.log(value); - * } - * ``` - */ - values(): IterableIterator; - - /** Returns an iterator allowing to go through all key/value - * pairs contained in this object. - * - * ```ts - * const params = new URLSearchParams([["a", "b"], ["c", "d"]]); - * for (const [key, value] of params.entries()) { - * console.log(key, value); - * } - * ``` - */ - entries(): IterableIterator<[string, string]>; - - /** Returns an iterator allowing to go through all key/value - * pairs contained in this object. - * - * ```ts - * const params = new URLSearchParams([["a", "b"], ["c", "d"]]); - * for (const [key, value] of params) { - * console.log(key, value); - * } - * ``` - */ - [Symbol.iterator](): IterableIterator<[string, string]>; - - /** Returns a query string suitable for use in a URL. - * - * ```ts - * searchParams.toString(); - * ``` - */ - toString(): string; -} - -declare const URLSearchParams: { - prototype: URLSearchParams; - new ( - init?: string[][] | Record | string | URLSearchParams - ): URLSearchParams; - toString(): string; -}; - -/** The URL interface represents an object providing static methods used for creating object URLs. */ -interface URL { - hash: string; - host: string; - hostname: string; - href: string; - toString(): string; - readonly origin: string; - password: string; - pathname: string; - port: string; - protocol: string; - search: string; - readonly searchParams: URLSearchParams; - username: string; - toJSON(): string; -} - -declare const URL: { - prototype: URL; - new (url: string | URL, base?: string | URL): URL; - createObjectURL(object: any): string; - revokeObjectURL(url: string): void; -}; - -interface MessageEventInit extends EventInit { - data?: any; - origin?: string; - lastEventId?: string; -} - -declare class MessageEvent extends Event { - readonly data: any; - readonly origin: string; - readonly lastEventId: string; - constructor(type: string, eventInitDict?: MessageEventInit); -} - -interface ErrorEventInit extends EventInit { - message?: string; - filename?: string; - lineno?: number; - colno?: number; - error?: any; -} - -declare class ErrorEvent extends Event { - readonly message: string; - readonly filename: string; - readonly lineno: number; - readonly colno: number; - readonly error: any; - constructor(type: string, eventInitDict?: ErrorEventInit); -} - -interface PostMessageOptions { - transfer?: any[]; -} - -declare class Worker extends EventTarget { - onerror?: (e: ErrorEvent) => void; - onmessage?: (e: MessageEvent) => void; - onmessageerror?: (e: MessageEvent) => void; - constructor( - specifier: string, - options?: { - type?: 'classic' | 'module'; - name?: string; - /** UNSTABLE: New API. Expect many changes; most likely this - * field will be made into an object for more granular - * configuration of worker thread (permissions, import map, etc.). - * - * Set to `true` to make `Deno` namespace and all of its methods - * available to worker thread. - * - * Currently worker inherits permissions from main thread (permissions - * given using `--allow-*` flags). - * Configurable permissions are on the roadmap to be implemented. - * - * Example: - * - * ```ts - * // mod.ts - * const worker = new Worker("./deno_worker.ts", { type: "module", deno: true }); - * worker.postMessage({ cmd: "readFile", fileName: "./log.txt" }); - * - * // deno_worker.ts - * - * - * self.onmessage = async function (e) { - * const { cmd, fileName } = e.data; - * if (cmd !== "readFile") { - * throw new Error("Invalid command"); - * } - * const buf = await Deno.readFile(fileName); - * const fileContents = new TextDecoder().decode(buf); - * console.log(fileContents); - * } - * ``` - * - * // log.txt - * hello world - * hello world 2 - * - * // run program - * $ deno run --allow-read mod.ts - * hello world - * hello world2 - * - */ - deno?: boolean; - } - ); - postMessage(message: any, transfer: ArrayBuffer[]): void; - postMessage(message: any, options?: PostMessageOptions): void; - terminate(): void; -} - -declare namespace performance { - /** Returns a current time from Deno's start in milliseconds. - * - * Use the flag --allow-hrtime return a precise value. - * - * ```ts - * const t = performance.now(); - * console.log(`${t} ms since start!`); - * ``` - */ - export function now(): number; -} - -interface EventInit { - bubbles?: boolean; - cancelable?: boolean; - composed?: boolean; -} - -/** An event which takes place in the DOM. */ -declare class Event { - constructor(type: string, eventInitDict?: EventInit); - /** Returns true or false depending on how event was initialized. True if - * event goes through its target's ancestors in reverse tree order, and - * false otherwise. */ - readonly bubbles: boolean; - cancelBubble: boolean; - /** Returns true or false depending on how event was initialized. Its return - * value does not always carry meaning, but true can indicate that part of the - * operation during which event was dispatched, can be canceled by invoking - * the preventDefault() method. */ - readonly cancelable: boolean; - /** Returns true or false depending on how event was initialized. True if - * event invokes listeners past a ShadowRoot node that is the root of its - * target, and false otherwise. */ - readonly composed: boolean; - /** Returns the object whose event listener's callback is currently being - * invoked. */ - readonly currentTarget: EventTarget | null; - /** Returns true if preventDefault() was invoked successfully to indicate - * cancellation, and false otherwise. */ - readonly defaultPrevented: boolean; - /** Returns the event's phase, which is one of NONE, CAPTURING_PHASE, - * AT_TARGET, and BUBBLING_PHASE. */ - readonly eventPhase: number; - /** Returns true if event was dispatched by the user agent, and false - * otherwise. */ - readonly isTrusted: boolean; - /** Returns the object to which event is dispatched (its target). */ - readonly target: EventTarget | null; - /** Returns the event's timestamp as the number of milliseconds measured - * relative to the time origin. */ - readonly timeStamp: number; - /** Returns the type of event, e.g. "click", "hashchange", or "submit". */ - readonly type: string; - /** Returns the invocation target objects of event's path (objects on which - * listeners will be invoked), except for any nodes in shadow trees of which - * the shadow root's mode is "closed" that are not reachable from event's - * currentTarget. */ - composedPath(): EventTarget[]; - /** If invoked when the cancelable attribute value is true, and while - * executing a listener for the event with passive set to false, signals to - * the operation that caused event to be dispatched that it needs to be - * canceled. */ - preventDefault(): void; - /** Invoking this method prevents event from reaching any registered event - * listeners after the current one finishes running and, when dispatched in a - * tree, also prevents event from reaching any other objects. */ - stopImmediatePropagation(): void; - /** When dispatched in a tree, invoking this method prevents event from - * reaching any objects other than the current object. */ - stopPropagation(): void; - readonly AT_TARGET: number; - readonly BUBBLING_PHASE: number; - readonly CAPTURING_PHASE: number; - readonly NONE: number; - static readonly AT_TARGET: number; - static readonly BUBBLING_PHASE: number; - static readonly CAPTURING_PHASE: number; - static readonly NONE: number; -} - -/** - * EventTarget is a DOM interface implemented by objects that can receive events - * and may have listeners for them. - */ -declare class EventTarget { - /** Appends an event listener for events whose type attribute value is type. - * The callback argument sets the callback that will be invoked when the event - * is dispatched. - * - * The options argument sets listener-specific options. For compatibility this - * can be a boolean, in which case the method behaves exactly as if the value - * was specified as options's capture. - * - * When set to true, options's capture prevents callback from being invoked - * when the event's eventPhase attribute value is BUBBLING_PHASE. When false - * (or not present), callback will not be invoked when event's eventPhase - * attribute value is CAPTURING_PHASE. Either way, callback will be invoked if - * event's eventPhase attribute value is AT_TARGET. - * - * When set to true, options's passive indicates that the callback will not - * cancel the event by invoking preventDefault(). This is used to enable - * performance optimizations described in § 2.8 Observing event listeners. - * - * When set to true, options's once indicates that the callback will only be - * invoked once after which the event listener will be removed. - * - * The event listener is appended to target's event listener list and is not - * appended if it has the same type, callback, and capture. */ - addEventListener( - type: string, - listener: EventListenerOrEventListenerObject | null, - options?: boolean | AddEventListenerOptions - ): void; - /** Dispatches a synthetic event event to target and returns true if either - * event's cancelable attribute value is false or its preventDefault() method - * was not invoked, and false otherwise. */ - dispatchEvent(event: Event): boolean; - /** Removes the event listener in target's event listener list with the same - * type, callback, and options. */ - removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean - ): void; - [Symbol.toStringTag]: string; -} - -interface EventListener { - (evt: Event): void | Promise; -} - -interface EventListenerObject { - handleEvent(evt: Event): void | Promise; -} - -declare type EventListenerOrEventListenerObject = - | EventListener - | EventListenerObject; - -interface AddEventListenerOptions extends EventListenerOptions { - once?: boolean; - passive?: boolean; -} - -interface EventListenerOptions { - capture?: boolean; -} - -/** Events measuring progress of an underlying process, like an HTTP request - * (for an XMLHttpRequest, or the loading of the underlying resource of an - * ,