├── .github ├── FUNDING.yml └── workflows │ └── diagram.yml ├── .gitignore ├── LICENSE ├── README.md ├── bun.lockb ├── diagram.svg ├── jsconfig.json ├── middleware.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.js ├── _document.js ├── api │ ├── v0 │ │ └── shorten.js │ ├── v1 │ │ └── shorten.js │ ├── v2 │ │ ├── domain.js │ │ └── shorten.js │ ├── v3 │ │ ├── count.js │ │ ├── domain.js │ │ └── shorten.js │ ├── v4 │ │ ├── count.js │ │ ├── dns.js │ │ ├── domain.js │ │ └── shorten.js │ ├── v5 │ │ ├── count.js │ │ ├── dns.js │ │ ├── domain.js │ │ └── shorten.js │ └── v6 │ │ ├── count.js │ │ ├── dns.js │ │ ├── domain.js │ │ └── shorten.js ├── index.js └── unlock.js ├── postcss.config.js ├── public └── img │ ├── city.jpg │ ├── cityNight.jpeg │ └── mountain.jpeg ├── styles └── globals.css └── tailwind.config.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: 1998code 4 | -------------------------------------------------------------------------------- /.github/workflows/diagram.yml: -------------------------------------------------------------------------------- 1 | name: Create diagram 2 | on: 3 | workflow_dispatch: {} 4 | push: 5 | branches: 6 | - "*" 7 | jobs: 8 | get_data: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@master 13 | - name: Update diagram 14 | uses: githubocto/repo-visualizer@main 15 | with: 16 | excluded_paths: "ignore,.github" 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 MING 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Magic Teleport - Shorten URL 2 | ### A New URL Shortener Solution - Powered by Vercel Storage 3 | 4 | [![Create diagram](https://github.com/1998code/shorten-url/actions/workflows/diagram.yml/badge.svg?branch=main)](https://github.com/1998code/shorten-url/actions/workflows/diagram.yml) 5 | 6 | Screenshot 2024-08-26 at 11 47 33 PM 7 | 8 | ## Tutorial 9 | https://post.1998.media/how-to-create-a-url-shorten-app-with-vercel-kv > https://shareby.vercel.app/j7ipk2 10 | 11 | ## API Doc 12 | https://1998code.gitbook.io/shortenurl-api/quick-start > https://shareby.vercel.app/q1onhr 13 | 14 | Screenshot 2024-08-27 at 12 09 44 AM 15 | 16 | ## Structure 17 | ![diagram](https://raw.githubusercontent.com/1998code/shorten-url/main/diagram.svg) 18 | 19 | ## License 20 | MIT 21 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1998code/shorten-url/dd19c13182e851384491d4756ff8d78124fff823/bun.lockb -------------------------------------------------------------------------------- /diagram.svg: -------------------------------------------------------------------------------- 1 | public/imgpublic/imgpagespagesapiapiv6v6v5v5v4v4v3v3v2v2mountain.jpegmountain.jpegmountain.jpegcityNight.jpegcityNight.jpegcityNight.jpegindex.jsindex.jsindex.jsunlock.jsunlock.jsunlock.jspackage-lock.jsonpackage-lock.jsonpackage-lock.jsonbun.lockbbun.lockbbun.lockbstyles/globals.cssstyles/globals.cssstyles/globals.cssmiddleware.tsmiddleware.tsmiddleware.tsLICENSELICENSELICENSEREADME.mdREADME.mdREADME.mdv1/shorten.jsv1/shorten.jsv1/shorten.jsv0/shorten.jsv0/shorten.jsv0/shorten.jscount.jscount.jscount.jsdomain.jsdomain.jsdomain.jsshorten.jsshorten.jsshorten.jsshorten.jsshorten.jsshorten.jsshorten.jsshorten.jsshorten.jsshorten.jsshorten.jsshorten.jsshorten.jsshorten.jsshorten.js.css.gitignore.js.json.md.svg.tseach dot sized by file size -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./*"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server' 2 | import { kv } from "@vercel/kv" 3 | 4 | /** 5 | * Configuration for URL path matching 6 | */ 7 | export const config = { 8 | matcher: '/:path*', 9 | } 10 | 11 | /** 12 | * URL redirection middleware 13 | * 14 | * Handles short URL redirection with support for: 15 | * - Direct URL redirection 16 | * - Password-protected URLs (using $ delimiter) 17 | * - Unlock page redirection for protected URLs 18 | */ 19 | export async function middleware(req: NextRequest) { 20 | const path = req.nextUrl.pathname.replace('/', '') 21 | 22 | // Skip middleware for system paths 23 | if (path.startsWith('api/') || path.startsWith('img/') || path === '' || path.startsWith('_next/static/')) { 24 | return NextResponse.next() 25 | } 26 | 27 | // Handle password-protected URLs (with $ delimiter) 28 | if (path.includes('$')) { 29 | const longUrl = await kv.get(path) 30 | 31 | if (longUrl) { 32 | // Ensure URL has protocol 33 | let finalUrl = longUrl 34 | if (!finalUrl.match(/^[a-zA-Z]+:\/\//)) { 35 | finalUrl = 'http://' + finalUrl 36 | } 37 | return NextResponse.redirect(new URL(finalUrl)) 38 | } 39 | } 40 | 41 | // Handle direct URL redirection 42 | const longUrl = await kv.get(path) 43 | if (longUrl) { 44 | // Ensure URL has protocol 45 | if (!longUrl.match(/^[a-zA-Z]+:\/\//)) { 46 | return NextResponse.redirect(new URL('http://' + longUrl)) 47 | } 48 | return NextResponse.redirect(new URL(longUrl)) 49 | } 50 | 51 | // Check for password-protected URLs 52 | const hasSecuredUrl = await kv.exists(`${path}$*`) 53 | if (hasSecuredUrl) { 54 | return NextResponse.redirect(new URL(`${req.nextUrl.origin}/unlock?key=${path}`)) 55 | } 56 | 57 | // Fallback pattern matching for KV implementations 58 | let securedUrlFound = false 59 | try { 60 | // Fast pattern-based scan 61 | for await (const key of kv.scanIterator({ match: `${path}$*`, count: 1 })) { 62 | securedUrlFound = true 63 | break; 64 | } 65 | 66 | if (securedUrlFound) { 67 | return NextResponse.redirect(new URL(`${req.nextUrl.origin}/unlock?key=${path}`)) 68 | } 69 | } catch (error) { 70 | // Progressive fallback for KV implementations without pattern matching 71 | for await (const key of kv.scanIterator()) { 72 | if (key.startsWith(`${path}$`)) { 73 | return NextResponse.redirect(new URL(`${req.nextUrl.origin}/unlock?key=${path}`)) 74 | } 75 | } 76 | } 77 | 78 | return NextResponse.next() 79 | } -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | } 5 | 6 | module.exports = nextConfig 7 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shorten-url", 3 | "version": "4.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "shorten-url", 9 | "version": "4.0.0", 10 | "dependencies": { 11 | "@vercel/kv": "^0.2.1", 12 | "autoprefixer": "10.4.14", 13 | "next": "13.4.3", 14 | "postcss": "8.4.23", 15 | "react": "18.2.0", 16 | "react-dom": "18.2.0", 17 | "tailwindcss": "3.3.2" 18 | } 19 | }, 20 | "node_modules/@alloc/quick-lru": { 21 | "version": "5.2.0", 22 | "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", 23 | "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", 24 | "engines": { 25 | "node": ">=10" 26 | }, 27 | "funding": { 28 | "url": "https://github.com/sponsors/sindresorhus" 29 | } 30 | }, 31 | "node_modules/@jridgewell/gen-mapping": { 32 | "version": "0.3.3", 33 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", 34 | "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", 35 | "dependencies": { 36 | "@jridgewell/set-array": "^1.0.1", 37 | "@jridgewell/sourcemap-codec": "^1.4.10", 38 | "@jridgewell/trace-mapping": "^0.3.9" 39 | }, 40 | "engines": { 41 | "node": ">=6.0.0" 42 | } 43 | }, 44 | "node_modules/@jridgewell/resolve-uri": { 45 | "version": "3.1.0", 46 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", 47 | "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", 48 | "engines": { 49 | "node": ">=6.0.0" 50 | } 51 | }, 52 | "node_modules/@jridgewell/set-array": { 53 | "version": "1.1.2", 54 | "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", 55 | "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", 56 | "engines": { 57 | "node": ">=6.0.0" 58 | } 59 | }, 60 | "node_modules/@jridgewell/sourcemap-codec": { 61 | "version": "1.4.15", 62 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 63 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" 64 | }, 65 | "node_modules/@jridgewell/trace-mapping": { 66 | "version": "0.3.18", 67 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", 68 | "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", 69 | "dependencies": { 70 | "@jridgewell/resolve-uri": "3.1.0", 71 | "@jridgewell/sourcemap-codec": "1.4.14" 72 | } 73 | }, 74 | "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { 75 | "version": "1.4.14", 76 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", 77 | "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" 78 | }, 79 | "node_modules/@next/env": { 80 | "version": "13.4.3", 81 | "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.3.tgz", 82 | "integrity": "sha512-pa1ErjyFensznttAk3EIv77vFbfSYT6cLzVRK5jx4uiRuCQo+m2wCFAREaHKIy63dlgvOyMlzh6R8Inu8H3KrQ==" 83 | }, 84 | "node_modules/@next/swc-darwin-arm64": { 85 | "version": "13.4.3", 86 | "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.3.tgz", 87 | "integrity": "sha512-yx18udH/ZmR4Bw4M6lIIPE3JxsAZwo04iaucEfA2GMt1unXr2iodHUX/LAKNyi6xoLP2ghi0E+Xi1f4Qb8f1LQ==", 88 | "cpu": [ 89 | "arm64" 90 | ], 91 | "optional": true, 92 | "os": [ 93 | "darwin" 94 | ], 95 | "engines": { 96 | "node": ">= 10" 97 | } 98 | }, 99 | "node_modules/@next/swc-darwin-x64": { 100 | "version": "13.4.3", 101 | "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.3.tgz", 102 | "integrity": "sha512-Mi8xJWh2IOjryAM1mx18vwmal9eokJ2njY4nDh04scy37F0LEGJ/diL6JL6kTXi0UfUCGbMsOItf7vpReNiD2A==", 103 | "cpu": [ 104 | "x64" 105 | ], 106 | "optional": true, 107 | "os": [ 108 | "darwin" 109 | ], 110 | "engines": { 111 | "node": ">= 10" 112 | } 113 | }, 114 | "node_modules/@next/swc-linux-arm64-gnu": { 115 | "version": "13.4.3", 116 | "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.3.tgz", 117 | "integrity": "sha512-aBvtry4bxJ1xwKZ/LVPeBGBwWVwxa4bTnNkRRw6YffJnn/f4Tv4EGDPaVeYHZGQVA56wsGbtA6nZMuWs/EIk4Q==", 118 | "cpu": [ 119 | "arm64" 120 | ], 121 | "optional": true, 122 | "os": [ 123 | "linux" 124 | ], 125 | "engines": { 126 | "node": ">= 10" 127 | } 128 | }, 129 | "node_modules/@next/swc-linux-arm64-musl": { 130 | "version": "13.4.3", 131 | "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.3.tgz", 132 | "integrity": "sha512-krT+2G3kEsEUvZoYte3/2IscscDraYPc2B+fDJFipPktJmrv088Pei/RjrhWm5TMIy5URYjZUoDZdh5k940Dyw==", 133 | "cpu": [ 134 | "arm64" 135 | ], 136 | "optional": true, 137 | "os": [ 138 | "linux" 139 | ], 140 | "engines": { 141 | "node": ">= 10" 142 | } 143 | }, 144 | "node_modules/@next/swc-linux-x64-gnu": { 145 | "version": "13.4.3", 146 | "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.3.tgz", 147 | "integrity": "sha512-AMdFX6EKJjC0G/CM6hJvkY8wUjCcbdj3Qg7uAQJ7PVejRWaVt0sDTMavbRfgMchx8h8KsAudUCtdFkG9hlEClw==", 148 | "cpu": [ 149 | "x64" 150 | ], 151 | "optional": true, 152 | "os": [ 153 | "linux" 154 | ], 155 | "engines": { 156 | "node": ">= 10" 157 | } 158 | }, 159 | "node_modules/@next/swc-linux-x64-musl": { 160 | "version": "13.4.3", 161 | "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.3.tgz", 162 | "integrity": "sha512-jySgSXE48shaLtcQbiFO9ajE9mqz7pcAVLnVLvRIlUHyQYR/WyZdK8ehLs65Mz6j9cLrJM+YdmdJPyV4WDaz2g==", 163 | "cpu": [ 164 | "x64" 165 | ], 166 | "optional": true, 167 | "os": [ 168 | "linux" 169 | ], 170 | "engines": { 171 | "node": ">= 10" 172 | } 173 | }, 174 | "node_modules/@next/swc-win32-arm64-msvc": { 175 | "version": "13.4.3", 176 | "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.3.tgz", 177 | "integrity": "sha512-5DxHo8uYcaADiE9pHrg8o28VMt/1kR8voDehmfs9AqS0qSClxAAl+CchjdboUvbCjdNWL1MISCvEfKY2InJ3JA==", 178 | "cpu": [ 179 | "arm64" 180 | ], 181 | "optional": true, 182 | "os": [ 183 | "win32" 184 | ], 185 | "engines": { 186 | "node": ">= 10" 187 | } 188 | }, 189 | "node_modules/@next/swc-win32-ia32-msvc": { 190 | "version": "13.4.3", 191 | "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.3.tgz", 192 | "integrity": "sha512-LaqkF3d+GXRA5X6zrUjQUrXm2MN/3E2arXBtn5C7avBCNYfm9G3Xc646AmmmpN3DJZVaMYliMyCIQCMDEzk80w==", 193 | "cpu": [ 194 | "ia32" 195 | ], 196 | "optional": true, 197 | "os": [ 198 | "win32" 199 | ], 200 | "engines": { 201 | "node": ">= 10" 202 | } 203 | }, 204 | "node_modules/@next/swc-win32-x64-msvc": { 205 | "version": "13.4.3", 206 | "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.3.tgz", 207 | "integrity": "sha512-jglUk/x7ZWeOJWlVoKyIAkHLTI+qEkOriOOV+3hr1GyiywzcqfI7TpFSiwC7kk1scOiH7NTFKp8mA3XPNO9bDw==", 208 | "cpu": [ 209 | "x64" 210 | ], 211 | "optional": true, 212 | "os": [ 213 | "win32" 214 | ], 215 | "engines": { 216 | "node": ">= 10" 217 | } 218 | }, 219 | "node_modules/@nodelib/fs.scandir": { 220 | "version": "2.1.5", 221 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 222 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 223 | "dependencies": { 224 | "@nodelib/fs.stat": "2.0.5", 225 | "run-parallel": "^1.1.9" 226 | }, 227 | "engines": { 228 | "node": ">= 8" 229 | } 230 | }, 231 | "node_modules/@nodelib/fs.stat": { 232 | "version": "2.0.5", 233 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 234 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 235 | "engines": { 236 | "node": ">= 8" 237 | } 238 | }, 239 | "node_modules/@nodelib/fs.walk": { 240 | "version": "1.2.8", 241 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 242 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 243 | "dependencies": { 244 | "@nodelib/fs.scandir": "2.1.5", 245 | "fastq": "^1.6.0" 246 | }, 247 | "engines": { 248 | "node": ">= 8" 249 | } 250 | }, 251 | "node_modules/@swc/helpers": { 252 | "version": "0.5.1", 253 | "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz", 254 | "integrity": "sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==", 255 | "dependencies": { 256 | "tslib": "^2.4.0" 257 | } 258 | }, 259 | "node_modules/@upstash/redis": { 260 | "version": "1.20.6", 261 | "resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.20.6.tgz", 262 | "integrity": "sha512-q1izaYEUsq/WiXNOjf4oOjFZe8fIeBSZN8d5cEyOD4nem+zxc4jccieorQQrNlEahKPE1ZYLzVEkMODRUfch2g==", 263 | "dependencies": { 264 | "isomorphic-fetch": "^3.0.0" 265 | } 266 | }, 267 | "node_modules/@vercel/kv": { 268 | "version": "0.2.1", 269 | "resolved": "https://registry.npmjs.org/@vercel/kv/-/kv-0.2.1.tgz", 270 | "integrity": "sha512-0O1CVh0maG/bduAE6DPKUTfGSnORgrcS5xBYZCb62sOU7PrVZrXhaPbUSBE4q5PXS5DC+cpN6FY2RWNlslUaWQ==", 271 | "dependencies": { 272 | "@upstash/redis": "1.20.6" 273 | }, 274 | "engines": { 275 | "node": ">=14.6" 276 | } 277 | }, 278 | "node_modules/any-promise": { 279 | "version": "1.3.0", 280 | "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", 281 | "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" 282 | }, 283 | "node_modules/anymatch": { 284 | "version": "3.1.3", 285 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 286 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 287 | "dependencies": { 288 | "normalize-path": "^3.0.0", 289 | "picomatch": "^2.0.4" 290 | }, 291 | "engines": { 292 | "node": ">= 8" 293 | } 294 | }, 295 | "node_modules/arg": { 296 | "version": "5.0.2", 297 | "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", 298 | "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" 299 | }, 300 | "node_modules/autoprefixer": { 301 | "version": "10.4.14", 302 | "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", 303 | "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", 304 | "funding": [ 305 | { 306 | "type": "opencollective", 307 | "url": "https://opencollective.com/postcss/" 308 | }, 309 | { 310 | "type": "tidelift", 311 | "url": "https://tidelift.com/funding/github/npm/autoprefixer" 312 | } 313 | ], 314 | "dependencies": { 315 | "browserslist": "^4.21.5", 316 | "caniuse-lite": "^1.0.30001464", 317 | "fraction.js": "^4.2.0", 318 | "normalize-range": "^0.1.2", 319 | "picocolors": "^1.0.0", 320 | "postcss-value-parser": "^4.2.0" 321 | }, 322 | "bin": { 323 | "autoprefixer": "bin/autoprefixer" 324 | }, 325 | "engines": { 326 | "node": "^10 || ^12 || >=14" 327 | }, 328 | "peerDependencies": { 329 | "postcss": "^8.1.0" 330 | } 331 | }, 332 | "node_modules/balanced-match": { 333 | "version": "1.0.2", 334 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 335 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 336 | }, 337 | "node_modules/binary-extensions": { 338 | "version": "2.2.0", 339 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 340 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 341 | "engines": { 342 | "node": ">=8" 343 | } 344 | }, 345 | "node_modules/brace-expansion": { 346 | "version": "1.1.11", 347 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 348 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 349 | "dependencies": { 350 | "balanced-match": "^1.0.0", 351 | "concat-map": "0.0.1" 352 | } 353 | }, 354 | "node_modules/braces": { 355 | "version": "3.0.2", 356 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 357 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 358 | "dependencies": { 359 | "fill-range": "^7.0.1" 360 | }, 361 | "engines": { 362 | "node": ">=8" 363 | } 364 | }, 365 | "node_modules/browserslist": { 366 | "version": "4.21.5", 367 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", 368 | "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", 369 | "funding": [ 370 | { 371 | "type": "opencollective", 372 | "url": "https://opencollective.com/browserslist" 373 | }, 374 | { 375 | "type": "tidelift", 376 | "url": "https://tidelift.com/funding/github/npm/browserslist" 377 | } 378 | ], 379 | "dependencies": { 380 | "caniuse-lite": "^1.0.30001449", 381 | "electron-to-chromium": "^1.4.284", 382 | "node-releases": "^2.0.8", 383 | "update-browserslist-db": "^1.0.10" 384 | }, 385 | "bin": { 386 | "browserslist": "cli.js" 387 | }, 388 | "engines": { 389 | "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 390 | } 391 | }, 392 | "node_modules/busboy": { 393 | "version": "1.6.0", 394 | "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", 395 | "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", 396 | "dependencies": { 397 | "streamsearch": "^1.1.0" 398 | }, 399 | "engines": { 400 | "node": ">=10.16.0" 401 | } 402 | }, 403 | "node_modules/camelcase-css": { 404 | "version": "2.0.1", 405 | "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", 406 | "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", 407 | "engines": { 408 | "node": ">= 6" 409 | } 410 | }, 411 | "node_modules/caniuse-lite": { 412 | "version": "1.0.30001488", 413 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001488.tgz", 414 | "integrity": "sha512-NORIQuuL4xGpIy6iCCQGN4iFjlBXtfKWIenlUuyZJumLRIindLb7wXM+GO8erEhb7vXfcnf4BAg2PrSDN5TNLQ==", 415 | "funding": [ 416 | { 417 | "type": "opencollective", 418 | "url": "https://opencollective.com/browserslist" 419 | }, 420 | { 421 | "type": "tidelift", 422 | "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 423 | }, 424 | { 425 | "type": "github", 426 | "url": "https://github.com/sponsors/ai" 427 | } 428 | ] 429 | }, 430 | "node_modules/chokidar": { 431 | "version": "3.5.3", 432 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 433 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 434 | "funding": [ 435 | { 436 | "type": "individual", 437 | "url": "https://paulmillr.com/funding/" 438 | } 439 | ], 440 | "dependencies": { 441 | "anymatch": "~3.1.2", 442 | "braces": "~3.0.2", 443 | "glob-parent": "~5.1.2", 444 | "is-binary-path": "~2.1.0", 445 | "is-glob": "~4.0.1", 446 | "normalize-path": "~3.0.0", 447 | "readdirp": "~3.6.0" 448 | }, 449 | "engines": { 450 | "node": ">= 8.10.0" 451 | }, 452 | "optionalDependencies": { 453 | "fsevents": "~2.3.2" 454 | } 455 | }, 456 | "node_modules/chokidar/node_modules/glob-parent": { 457 | "version": "5.1.2", 458 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 459 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 460 | "dependencies": { 461 | "is-glob": "^4.0.1" 462 | }, 463 | "engines": { 464 | "node": ">= 6" 465 | } 466 | }, 467 | "node_modules/client-only": { 468 | "version": "0.0.1", 469 | "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", 470 | "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" 471 | }, 472 | "node_modules/commander": { 473 | "version": "4.1.1", 474 | "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", 475 | "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", 476 | "engines": { 477 | "node": ">= 6" 478 | } 479 | }, 480 | "node_modules/concat-map": { 481 | "version": "0.0.1", 482 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 483 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" 484 | }, 485 | "node_modules/cssesc": { 486 | "version": "3.0.0", 487 | "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", 488 | "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", 489 | "bin": { 490 | "cssesc": "bin/cssesc" 491 | }, 492 | "engines": { 493 | "node": ">=4" 494 | } 495 | }, 496 | "node_modules/didyoumean": { 497 | "version": "1.2.2", 498 | "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", 499 | "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" 500 | }, 501 | "node_modules/dlv": { 502 | "version": "1.1.3", 503 | "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", 504 | "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" 505 | }, 506 | "node_modules/electron-to-chromium": { 507 | "version": "1.4.402", 508 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.402.tgz", 509 | "integrity": "sha512-gWYvJSkohOiBE6ecVYXkrDgNaUjo47QEKK0kQzmWyhkH+yoYiG44bwuicTGNSIQRG3WDMsWVZJLRnJnLNkbWvA==" 510 | }, 511 | "node_modules/escalade": { 512 | "version": "3.1.1", 513 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 514 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 515 | "engines": { 516 | "node": ">=6" 517 | } 518 | }, 519 | "node_modules/fast-glob": { 520 | "version": "3.2.12", 521 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", 522 | "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", 523 | "dependencies": { 524 | "@nodelib/fs.stat": "^2.0.2", 525 | "@nodelib/fs.walk": "^1.2.3", 526 | "glob-parent": "^5.1.2", 527 | "merge2": "^1.3.0", 528 | "micromatch": "^4.0.4" 529 | }, 530 | "engines": { 531 | "node": ">=8.6.0" 532 | } 533 | }, 534 | "node_modules/fast-glob/node_modules/glob-parent": { 535 | "version": "5.1.2", 536 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 537 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 538 | "dependencies": { 539 | "is-glob": "^4.0.1" 540 | }, 541 | "engines": { 542 | "node": ">= 6" 543 | } 544 | }, 545 | "node_modules/fastq": { 546 | "version": "1.15.0", 547 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", 548 | "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", 549 | "dependencies": { 550 | "reusify": "^1.0.4" 551 | } 552 | }, 553 | "node_modules/fill-range": { 554 | "version": "7.0.1", 555 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 556 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 557 | "dependencies": { 558 | "to-regex-range": "^5.0.1" 559 | }, 560 | "engines": { 561 | "node": ">=8" 562 | } 563 | }, 564 | "node_modules/fraction.js": { 565 | "version": "4.2.0", 566 | "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", 567 | "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", 568 | "engines": { 569 | "node": "*" 570 | }, 571 | "funding": { 572 | "type": "patreon", 573 | "url": "https://www.patreon.com/infusion" 574 | } 575 | }, 576 | "node_modules/fs.realpath": { 577 | "version": "1.0.0", 578 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 579 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" 580 | }, 581 | "node_modules/fsevents": { 582 | "version": "2.3.2", 583 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 584 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 585 | "hasInstallScript": true, 586 | "optional": true, 587 | "os": [ 588 | "darwin" 589 | ], 590 | "engines": { 591 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 592 | } 593 | }, 594 | "node_modules/function-bind": { 595 | "version": "1.1.1", 596 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 597 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 598 | }, 599 | "node_modules/glob": { 600 | "version": "7.1.6", 601 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 602 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 603 | "dependencies": { 604 | "fs.realpath": "^1.0.0", 605 | "inflight": "^1.0.4", 606 | "inherits": "2", 607 | "minimatch": "^3.0.4", 608 | "once": "^1.3.0", 609 | "path-is-absolute": "^1.0.0" 610 | }, 611 | "engines": { 612 | "node": "*" 613 | }, 614 | "funding": { 615 | "url": "https://github.com/sponsors/isaacs" 616 | } 617 | }, 618 | "node_modules/glob-parent": { 619 | "version": "6.0.2", 620 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 621 | "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 622 | "dependencies": { 623 | "is-glob": "^4.0.3" 624 | }, 625 | "engines": { 626 | "node": ">=10.13.0" 627 | } 628 | }, 629 | "node_modules/has": { 630 | "version": "1.0.3", 631 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 632 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 633 | "dependencies": { 634 | "function-bind": "^1.1.1" 635 | }, 636 | "engines": { 637 | "node": ">= 0.4.0" 638 | } 639 | }, 640 | "node_modules/inflight": { 641 | "version": "1.0.6", 642 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 643 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 644 | "dependencies": { 645 | "once": "^1.3.0", 646 | "wrappy": "1" 647 | } 648 | }, 649 | "node_modules/inherits": { 650 | "version": "2.0.4", 651 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 652 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 653 | }, 654 | "node_modules/is-binary-path": { 655 | "version": "2.1.0", 656 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 657 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 658 | "dependencies": { 659 | "binary-extensions": "^2.0.0" 660 | }, 661 | "engines": { 662 | "node": ">=8" 663 | } 664 | }, 665 | "node_modules/is-core-module": { 666 | "version": "2.12.1", 667 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", 668 | "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", 669 | "dependencies": { 670 | "has": "^1.0.3" 671 | }, 672 | "funding": { 673 | "url": "https://github.com/sponsors/ljharb" 674 | } 675 | }, 676 | "node_modules/is-extglob": { 677 | "version": "2.1.1", 678 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 679 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 680 | "engines": { 681 | "node": ">=0.10.0" 682 | } 683 | }, 684 | "node_modules/is-glob": { 685 | "version": "4.0.3", 686 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 687 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 688 | "dependencies": { 689 | "is-extglob": "^2.1.1" 690 | }, 691 | "engines": { 692 | "node": ">=0.10.0" 693 | } 694 | }, 695 | "node_modules/is-number": { 696 | "version": "7.0.0", 697 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 698 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 699 | "engines": { 700 | "node": ">=0.12.0" 701 | } 702 | }, 703 | "node_modules/isomorphic-fetch": { 704 | "version": "3.0.0", 705 | "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", 706 | "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", 707 | "dependencies": { 708 | "node-fetch": "^2.6.1", 709 | "whatwg-fetch": "^3.4.1" 710 | } 711 | }, 712 | "node_modules/jiti": { 713 | "version": "1.18.2", 714 | "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", 715 | "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", 716 | "bin": { 717 | "jiti": "bin/jiti.js" 718 | } 719 | }, 720 | "node_modules/js-tokens": { 721 | "version": "4.0.0", 722 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 723 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 724 | }, 725 | "node_modules/lilconfig": { 726 | "version": "2.1.0", 727 | "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", 728 | "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", 729 | "engines": { 730 | "node": ">=10" 731 | } 732 | }, 733 | "node_modules/lines-and-columns": { 734 | "version": "1.2.4", 735 | "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", 736 | "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" 737 | }, 738 | "node_modules/loose-envify": { 739 | "version": "1.4.0", 740 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 741 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 742 | "dependencies": { 743 | "js-tokens": "^3.0.0 || ^4.0.0" 744 | }, 745 | "bin": { 746 | "loose-envify": "cli.js" 747 | } 748 | }, 749 | "node_modules/merge2": { 750 | "version": "1.4.1", 751 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 752 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 753 | "engines": { 754 | "node": ">= 8" 755 | } 756 | }, 757 | "node_modules/micromatch": { 758 | "version": "4.0.5", 759 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", 760 | "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", 761 | "dependencies": { 762 | "braces": "^3.0.2", 763 | "picomatch": "^2.3.1" 764 | }, 765 | "engines": { 766 | "node": ">=8.6" 767 | } 768 | }, 769 | "node_modules/minimatch": { 770 | "version": "3.1.2", 771 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 772 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 773 | "dependencies": { 774 | "brace-expansion": "^1.1.7" 775 | }, 776 | "engines": { 777 | "node": "*" 778 | } 779 | }, 780 | "node_modules/mz": { 781 | "version": "2.7.0", 782 | "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", 783 | "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", 784 | "dependencies": { 785 | "any-promise": "^1.0.0", 786 | "object-assign": "^4.0.1", 787 | "thenify-all": "^1.0.0" 788 | } 789 | }, 790 | "node_modules/nanoid": { 791 | "version": "3.3.6", 792 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", 793 | "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", 794 | "funding": [ 795 | { 796 | "type": "github", 797 | "url": "https://github.com/sponsors/ai" 798 | } 799 | ], 800 | "bin": { 801 | "nanoid": "bin/nanoid.cjs" 802 | }, 803 | "engines": { 804 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 805 | } 806 | }, 807 | "node_modules/next": { 808 | "version": "13.4.3", 809 | "resolved": "https://registry.npmjs.org/next/-/next-13.4.3.tgz", 810 | "integrity": "sha512-FV3pBrAAnAIfOclTvncw9dDohyeuEEXPe5KNcva91anT/rdycWbgtu3IjUj4n5yHnWK8YEPo0vrUecHmnmUNbA==", 811 | "dependencies": { 812 | "@next/env": "13.4.3", 813 | "@swc/helpers": "0.5.1", 814 | "busboy": "1.6.0", 815 | "caniuse-lite": "^1.0.30001406", 816 | "postcss": "8.4.14", 817 | "styled-jsx": "5.1.1", 818 | "zod": "3.21.4" 819 | }, 820 | "bin": { 821 | "next": "dist/bin/next" 822 | }, 823 | "engines": { 824 | "node": ">=16.8.0" 825 | }, 826 | "optionalDependencies": { 827 | "@next/swc-darwin-arm64": "13.4.3", 828 | "@next/swc-darwin-x64": "13.4.3", 829 | "@next/swc-linux-arm64-gnu": "13.4.3", 830 | "@next/swc-linux-arm64-musl": "13.4.3", 831 | "@next/swc-linux-x64-gnu": "13.4.3", 832 | "@next/swc-linux-x64-musl": "13.4.3", 833 | "@next/swc-win32-arm64-msvc": "13.4.3", 834 | "@next/swc-win32-ia32-msvc": "13.4.3", 835 | "@next/swc-win32-x64-msvc": "13.4.3" 836 | }, 837 | "peerDependencies": { 838 | "@opentelemetry/api": "^1.1.0", 839 | "fibers": ">= 3.1.0", 840 | "node-sass": "^6.0.0 || ^7.0.0", 841 | "react": "^18.2.0", 842 | "react-dom": "^18.2.0", 843 | "sass": "^1.3.0" 844 | }, 845 | "peerDependenciesMeta": { 846 | "@opentelemetry/api": { 847 | "optional": true 848 | }, 849 | "fibers": { 850 | "optional": true 851 | }, 852 | "node-sass": { 853 | "optional": true 854 | }, 855 | "sass": { 856 | "optional": true 857 | } 858 | } 859 | }, 860 | "node_modules/next/node_modules/postcss": { 861 | "version": "8.4.14", 862 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", 863 | "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", 864 | "funding": [ 865 | { 866 | "type": "opencollective", 867 | "url": "https://opencollective.com/postcss/" 868 | }, 869 | { 870 | "type": "tidelift", 871 | "url": "https://tidelift.com/funding/github/npm/postcss" 872 | } 873 | ], 874 | "dependencies": { 875 | "nanoid": "^3.3.4", 876 | "picocolors": "^1.0.0", 877 | "source-map-js": "^1.0.2" 878 | }, 879 | "engines": { 880 | "node": "^10 || ^12 || >=14" 881 | } 882 | }, 883 | "node_modules/node-fetch": { 884 | "version": "2.6.11", 885 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", 886 | "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", 887 | "dependencies": { 888 | "whatwg-url": "^5.0.0" 889 | }, 890 | "engines": { 891 | "node": "4.x || >=6.0.0" 892 | }, 893 | "peerDependencies": { 894 | "encoding": "^0.1.0" 895 | }, 896 | "peerDependenciesMeta": { 897 | "encoding": { 898 | "optional": true 899 | } 900 | } 901 | }, 902 | "node_modules/node-releases": { 903 | "version": "2.0.10", 904 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", 905 | "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==" 906 | }, 907 | "node_modules/normalize-path": { 908 | "version": "3.0.0", 909 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 910 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 911 | "engines": { 912 | "node": ">=0.10.0" 913 | } 914 | }, 915 | "node_modules/normalize-range": { 916 | "version": "0.1.2", 917 | "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", 918 | "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", 919 | "engines": { 920 | "node": ">=0.10.0" 921 | } 922 | }, 923 | "node_modules/object-assign": { 924 | "version": "4.1.1", 925 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 926 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 927 | "engines": { 928 | "node": ">=0.10.0" 929 | } 930 | }, 931 | "node_modules/object-hash": { 932 | "version": "3.0.0", 933 | "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", 934 | "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", 935 | "engines": { 936 | "node": ">= 6" 937 | } 938 | }, 939 | "node_modules/once": { 940 | "version": "1.4.0", 941 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 942 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 943 | "dependencies": { 944 | "wrappy": "1" 945 | } 946 | }, 947 | "node_modules/path-is-absolute": { 948 | "version": "1.0.1", 949 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 950 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 951 | "engines": { 952 | "node": ">=0.10.0" 953 | } 954 | }, 955 | "node_modules/path-parse": { 956 | "version": "1.0.7", 957 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 958 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" 959 | }, 960 | "node_modules/picocolors": { 961 | "version": "1.0.0", 962 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 963 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" 964 | }, 965 | "node_modules/picomatch": { 966 | "version": "2.3.1", 967 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 968 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 969 | "engines": { 970 | "node": ">=8.6" 971 | }, 972 | "funding": { 973 | "url": "https://github.com/sponsors/jonschlinkert" 974 | } 975 | }, 976 | "node_modules/pify": { 977 | "version": "2.3.0", 978 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", 979 | "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", 980 | "engines": { 981 | "node": ">=0.10.0" 982 | } 983 | }, 984 | "node_modules/pirates": { 985 | "version": "4.0.5", 986 | "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", 987 | "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", 988 | "engines": { 989 | "node": ">= 6" 990 | } 991 | }, 992 | "node_modules/postcss": { 993 | "version": "8.4.23", 994 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", 995 | "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", 996 | "funding": [ 997 | { 998 | "type": "opencollective", 999 | "url": "https://opencollective.com/postcss/" 1000 | }, 1001 | { 1002 | "type": "tidelift", 1003 | "url": "https://tidelift.com/funding/github/npm/postcss" 1004 | }, 1005 | { 1006 | "type": "github", 1007 | "url": "https://github.com/sponsors/ai" 1008 | } 1009 | ], 1010 | "dependencies": { 1011 | "nanoid": "^3.3.6", 1012 | "picocolors": "^1.0.0", 1013 | "source-map-js": "^1.0.2" 1014 | }, 1015 | "engines": { 1016 | "node": "^10 || ^12 || >=14" 1017 | } 1018 | }, 1019 | "node_modules/postcss-import": { 1020 | "version": "15.1.0", 1021 | "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", 1022 | "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", 1023 | "dependencies": { 1024 | "postcss-value-parser": "^4.0.0", 1025 | "read-cache": "^1.0.0", 1026 | "resolve": "^1.1.7" 1027 | }, 1028 | "engines": { 1029 | "node": ">=14.0.0" 1030 | }, 1031 | "peerDependencies": { 1032 | "postcss": "^8.0.0" 1033 | } 1034 | }, 1035 | "node_modules/postcss-js": { 1036 | "version": "4.0.1", 1037 | "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", 1038 | "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", 1039 | "dependencies": { 1040 | "camelcase-css": "^2.0.1" 1041 | }, 1042 | "engines": { 1043 | "node": "^12 || ^14 || >= 16" 1044 | }, 1045 | "funding": { 1046 | "type": "opencollective", 1047 | "url": "https://opencollective.com/postcss/" 1048 | }, 1049 | "peerDependencies": { 1050 | "postcss": "^8.4.21" 1051 | } 1052 | }, 1053 | "node_modules/postcss-load-config": { 1054 | "version": "4.0.1", 1055 | "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", 1056 | "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", 1057 | "dependencies": { 1058 | "lilconfig": "^2.0.5", 1059 | "yaml": "^2.1.1" 1060 | }, 1061 | "engines": { 1062 | "node": ">= 14" 1063 | }, 1064 | "funding": { 1065 | "type": "opencollective", 1066 | "url": "https://opencollective.com/postcss/" 1067 | }, 1068 | "peerDependencies": { 1069 | "postcss": ">=8.0.9", 1070 | "ts-node": ">=9.0.0" 1071 | }, 1072 | "peerDependenciesMeta": { 1073 | "postcss": { 1074 | "optional": true 1075 | }, 1076 | "ts-node": { 1077 | "optional": true 1078 | } 1079 | } 1080 | }, 1081 | "node_modules/postcss-nested": { 1082 | "version": "6.0.1", 1083 | "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", 1084 | "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", 1085 | "dependencies": { 1086 | "postcss-selector-parser": "^6.0.11" 1087 | }, 1088 | "engines": { 1089 | "node": ">=12.0" 1090 | }, 1091 | "funding": { 1092 | "type": "opencollective", 1093 | "url": "https://opencollective.com/postcss/" 1094 | }, 1095 | "peerDependencies": { 1096 | "postcss": "^8.2.14" 1097 | } 1098 | }, 1099 | "node_modules/postcss-selector-parser": { 1100 | "version": "6.0.13", 1101 | "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", 1102 | "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", 1103 | "dependencies": { 1104 | "cssesc": "^3.0.0", 1105 | "util-deprecate": "^1.0.2" 1106 | }, 1107 | "engines": { 1108 | "node": ">=4" 1109 | } 1110 | }, 1111 | "node_modules/postcss-value-parser": { 1112 | "version": "4.2.0", 1113 | "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", 1114 | "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" 1115 | }, 1116 | "node_modules/queue-microtask": { 1117 | "version": "1.2.3", 1118 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 1119 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 1120 | "funding": [ 1121 | { 1122 | "type": "github", 1123 | "url": "https://github.com/sponsors/feross" 1124 | }, 1125 | { 1126 | "type": "patreon", 1127 | "url": "https://www.patreon.com/feross" 1128 | }, 1129 | { 1130 | "type": "consulting", 1131 | "url": "https://feross.org/support" 1132 | } 1133 | ] 1134 | }, 1135 | "node_modules/react": { 1136 | "version": "18.2.0", 1137 | "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", 1138 | "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", 1139 | "dependencies": { 1140 | "loose-envify": "^1.1.0" 1141 | }, 1142 | "engines": { 1143 | "node": ">=0.10.0" 1144 | } 1145 | }, 1146 | "node_modules/react-dom": { 1147 | "version": "18.2.0", 1148 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", 1149 | "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", 1150 | "dependencies": { 1151 | "loose-envify": "^1.1.0", 1152 | "scheduler": "^0.23.0" 1153 | }, 1154 | "peerDependencies": { 1155 | "react": "^18.2.0" 1156 | } 1157 | }, 1158 | "node_modules/read-cache": { 1159 | "version": "1.0.0", 1160 | "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", 1161 | "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", 1162 | "dependencies": { 1163 | "pify": "^2.3.0" 1164 | } 1165 | }, 1166 | "node_modules/readdirp": { 1167 | "version": "3.6.0", 1168 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 1169 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 1170 | "dependencies": { 1171 | "picomatch": "^2.2.1" 1172 | }, 1173 | "engines": { 1174 | "node": ">=8.10.0" 1175 | } 1176 | }, 1177 | "node_modules/resolve": { 1178 | "version": "1.22.2", 1179 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", 1180 | "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", 1181 | "dependencies": { 1182 | "is-core-module": "^2.11.0", 1183 | "path-parse": "^1.0.7", 1184 | "supports-preserve-symlinks-flag": "^1.0.0" 1185 | }, 1186 | "bin": { 1187 | "resolve": "bin/resolve" 1188 | }, 1189 | "funding": { 1190 | "url": "https://github.com/sponsors/ljharb" 1191 | } 1192 | }, 1193 | "node_modules/reusify": { 1194 | "version": "1.0.4", 1195 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 1196 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 1197 | "engines": { 1198 | "iojs": ">=1.0.0", 1199 | "node": ">=0.10.0" 1200 | } 1201 | }, 1202 | "node_modules/run-parallel": { 1203 | "version": "1.2.0", 1204 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 1205 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 1206 | "funding": [ 1207 | { 1208 | "type": "github", 1209 | "url": "https://github.com/sponsors/feross" 1210 | }, 1211 | { 1212 | "type": "patreon", 1213 | "url": "https://www.patreon.com/feross" 1214 | }, 1215 | { 1216 | "type": "consulting", 1217 | "url": "https://feross.org/support" 1218 | } 1219 | ], 1220 | "dependencies": { 1221 | "queue-microtask": "^1.2.2" 1222 | } 1223 | }, 1224 | "node_modules/scheduler": { 1225 | "version": "0.23.0", 1226 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", 1227 | "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", 1228 | "dependencies": { 1229 | "loose-envify": "^1.1.0" 1230 | } 1231 | }, 1232 | "node_modules/source-map-js": { 1233 | "version": "1.0.2", 1234 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 1235 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 1236 | "engines": { 1237 | "node": ">=0.10.0" 1238 | } 1239 | }, 1240 | "node_modules/streamsearch": { 1241 | "version": "1.1.0", 1242 | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", 1243 | "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", 1244 | "engines": { 1245 | "node": ">=10.0.0" 1246 | } 1247 | }, 1248 | "node_modules/styled-jsx": { 1249 | "version": "5.1.1", 1250 | "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", 1251 | "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", 1252 | "dependencies": { 1253 | "client-only": "0.0.1" 1254 | }, 1255 | "engines": { 1256 | "node": ">= 12.0.0" 1257 | }, 1258 | "peerDependencies": { 1259 | "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" 1260 | }, 1261 | "peerDependenciesMeta": { 1262 | "@babel/core": { 1263 | "optional": true 1264 | }, 1265 | "babel-plugin-macros": { 1266 | "optional": true 1267 | } 1268 | } 1269 | }, 1270 | "node_modules/sucrase": { 1271 | "version": "3.32.0", 1272 | "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", 1273 | "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", 1274 | "dependencies": { 1275 | "@jridgewell/gen-mapping": "^0.3.2", 1276 | "commander": "^4.0.0", 1277 | "glob": "7.1.6", 1278 | "lines-and-columns": "^1.1.6", 1279 | "mz": "^2.7.0", 1280 | "pirates": "^4.0.1", 1281 | "ts-interface-checker": "^0.1.9" 1282 | }, 1283 | "bin": { 1284 | "sucrase": "bin/sucrase", 1285 | "sucrase-node": "bin/sucrase-node" 1286 | }, 1287 | "engines": { 1288 | "node": ">=8" 1289 | } 1290 | }, 1291 | "node_modules/supports-preserve-symlinks-flag": { 1292 | "version": "1.0.0", 1293 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 1294 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 1295 | "engines": { 1296 | "node": ">= 0.4" 1297 | }, 1298 | "funding": { 1299 | "url": "https://github.com/sponsors/ljharb" 1300 | } 1301 | }, 1302 | "node_modules/tailwindcss": { 1303 | "version": "3.3.2", 1304 | "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz", 1305 | "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==", 1306 | "dependencies": { 1307 | "@alloc/quick-lru": "^5.2.0", 1308 | "arg": "^5.0.2", 1309 | "chokidar": "^3.5.3", 1310 | "didyoumean": "^1.2.2", 1311 | "dlv": "^1.1.3", 1312 | "fast-glob": "^3.2.12", 1313 | "glob-parent": "^6.0.2", 1314 | "is-glob": "^4.0.3", 1315 | "jiti": "^1.18.2", 1316 | "lilconfig": "^2.1.0", 1317 | "micromatch": "^4.0.5", 1318 | "normalize-path": "^3.0.0", 1319 | "object-hash": "^3.0.0", 1320 | "picocolors": "^1.0.0", 1321 | "postcss": "^8.4.23", 1322 | "postcss-import": "^15.1.0", 1323 | "postcss-js": "^4.0.1", 1324 | "postcss-load-config": "^4.0.1", 1325 | "postcss-nested": "^6.0.1", 1326 | "postcss-selector-parser": "^6.0.11", 1327 | "postcss-value-parser": "^4.2.0", 1328 | "resolve": "^1.22.2", 1329 | "sucrase": "^3.32.0" 1330 | }, 1331 | "bin": { 1332 | "tailwind": "lib/cli.js", 1333 | "tailwindcss": "lib/cli.js" 1334 | }, 1335 | "engines": { 1336 | "node": ">=14.0.0" 1337 | } 1338 | }, 1339 | "node_modules/thenify": { 1340 | "version": "3.3.1", 1341 | "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", 1342 | "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", 1343 | "dependencies": { 1344 | "any-promise": "^1.0.0" 1345 | } 1346 | }, 1347 | "node_modules/thenify-all": { 1348 | "version": "1.6.0", 1349 | "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", 1350 | "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", 1351 | "dependencies": { 1352 | "thenify": ">= 3.1.0 < 4" 1353 | }, 1354 | "engines": { 1355 | "node": ">=0.8" 1356 | } 1357 | }, 1358 | "node_modules/to-regex-range": { 1359 | "version": "5.0.1", 1360 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1361 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1362 | "dependencies": { 1363 | "is-number": "^7.0.0" 1364 | }, 1365 | "engines": { 1366 | "node": ">=8.0" 1367 | } 1368 | }, 1369 | "node_modules/tr46": { 1370 | "version": "0.0.3", 1371 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 1372 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 1373 | }, 1374 | "node_modules/ts-interface-checker": { 1375 | "version": "0.1.13", 1376 | "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", 1377 | "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" 1378 | }, 1379 | "node_modules/tslib": { 1380 | "version": "2.5.2", 1381 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz", 1382 | "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==" 1383 | }, 1384 | "node_modules/update-browserslist-db": { 1385 | "version": "1.0.11", 1386 | "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", 1387 | "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", 1388 | "funding": [ 1389 | { 1390 | "type": "opencollective", 1391 | "url": "https://opencollective.com/browserslist" 1392 | }, 1393 | { 1394 | "type": "tidelift", 1395 | "url": "https://tidelift.com/funding/github/npm/browserslist" 1396 | }, 1397 | { 1398 | "type": "github", 1399 | "url": "https://github.com/sponsors/ai" 1400 | } 1401 | ], 1402 | "dependencies": { 1403 | "escalade": "^3.1.1", 1404 | "picocolors": "^1.0.0" 1405 | }, 1406 | "bin": { 1407 | "update-browserslist-db": "cli.js" 1408 | }, 1409 | "peerDependencies": { 1410 | "browserslist": ">= 4.21.0" 1411 | } 1412 | }, 1413 | "node_modules/util-deprecate": { 1414 | "version": "1.0.2", 1415 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1416 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 1417 | }, 1418 | "node_modules/webidl-conversions": { 1419 | "version": "3.0.1", 1420 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 1421 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 1422 | }, 1423 | "node_modules/whatwg-fetch": { 1424 | "version": "3.6.2", 1425 | "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", 1426 | "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" 1427 | }, 1428 | "node_modules/whatwg-url": { 1429 | "version": "5.0.0", 1430 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 1431 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 1432 | "dependencies": { 1433 | "tr46": "~0.0.3", 1434 | "webidl-conversions": "^3.0.0" 1435 | } 1436 | }, 1437 | "node_modules/wrappy": { 1438 | "version": "1.0.2", 1439 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1440 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 1441 | }, 1442 | "node_modules/yaml": { 1443 | "version": "2.2.2", 1444 | "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", 1445 | "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", 1446 | "engines": { 1447 | "node": ">= 14" 1448 | } 1449 | }, 1450 | "node_modules/zod": { 1451 | "version": "3.21.4", 1452 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", 1453 | "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", 1454 | "funding": { 1455 | "url": "https://github.com/sponsors/colinhacks" 1456 | } 1457 | } 1458 | } 1459 | } 1460 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shorten-url", 3 | "version": "6.0.0", 4 | "private": false, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@upstash/redis": "^1.34.9", 13 | "@vercel/kv": "^0.2.1", 14 | "autoprefixer": "10.4.14", 15 | "next": "^14.2.29", 16 | "postcss": "8.4.23", 17 | "react": "18.2.0", 18 | "react-dom": "18.2.0", 19 | "tailwindcss": "3.3.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import '@/styles/globals.css' 2 | 3 | export default function App({ Component, pageProps }) { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /pages/_document.js: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from 'next/document' 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | Magic Teleport 8 | 9 | 10 |
11 | 12 | 13 | 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /pages/api/v0/shorten.js: -------------------------------------------------------------------------------- 1 | import { kv } from "@vercel/kv" 2 | 3 | export default async function handler(req, res) { 4 | 5 | // GET urls from POST body 6 | const urls = req.body.trim() 7 | 8 | // Split the URLs into an array, line break or , 9 | const urlsArray = urls.split(/[\n,]+/) 10 | 11 | const results = [] 12 | 13 | // Shorten each URL 14 | for (const url of urlsArray) { 15 | if (url === '') continue 16 | 17 | // radomly generate a key 18 | let key = Math.random().toString(36).substring(2, 5) + Math.random().toString(36).substring(2, 5) 19 | 20 | // check if key already exists 21 | const checkKey = await kv.get(key) 22 | 23 | // while check key 24 | while (checkKey) { 25 | key = Math.random().toString(36).substring(2, 5) + Math.random().toString(36).substring(2, 5) 26 | } 27 | 28 | // set the key 29 | await kv.set(key, url) 30 | 31 | // push the result to the array 32 | results.push({ 33 | key: key, 34 | url: url 35 | }) 36 | } 37 | 38 | // Return the results 39 | res.status(200).json(results) 40 | } 41 | -------------------------------------------------------------------------------- /pages/api/v1/shorten.js: -------------------------------------------------------------------------------- 1 | import { kv } from "@vercel/kv" 2 | 3 | export default async function handler(req, res) { 4 | 5 | const body = JSON.parse(req.body) 6 | 7 | // GET urls from POST body 8 | const urls = body.urls.trim() 9 | 10 | const password = body.password 11 | 12 | // Split the URLs into an array, line break or , 13 | const urlsArray = urls.split(/[\n,]+/) 14 | 15 | const results = [] 16 | 17 | // Shorten each URL 18 | for (const url of urlsArray) { 19 | if (url === '') continue 20 | 21 | // radomly generate a key 22 | let key = Math.random().toString(36).substring(2, 5) + Math.random().toString(36).substring(2, 5) 23 | 24 | // check if key already exists 25 | const checkKey = await kv.get(key) 26 | 27 | // while check key 28 | while (checkKey) { 29 | key = Math.random().toString(36).substring(2, 5) + Math.random().toString(36).substring(2, 5) 30 | } 31 | 32 | // if password is not null, set the password 33 | if (password !== "") { 34 | key = key + "$" + password 35 | } 36 | 37 | // set the key 38 | await kv.set(key, url) 39 | 40 | // push the result to the array 41 | results.push({ 42 | key: key.split("$")[0], 43 | url: url 44 | }) 45 | } 46 | 47 | // Return the results 48 | res.status(200).json(results) 49 | } 50 | -------------------------------------------------------------------------------- /pages/api/v2/domain.js: -------------------------------------------------------------------------------- 1 | export default async function handler(req, res) { 2 | const { add } = req.query 3 | 4 | const response = await fetch( 5 | `https://api.vercel.com/v9/projects/${process.env.PROJECT_ID_VERCEL}/domains?teamId=${process.env.TEAM_ID_VERCEL}`, 6 | { 7 | body: `{\n "name": "${add.replaceAll('http://','').replaceAll('https://','')}"\n}`, 8 | headers: { 9 | Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN}`, 10 | 'Content-Type': 'application/json', 11 | }, 12 | method: 'POST', 13 | } 14 | ) 15 | 16 | const data = await response.json() 17 | 18 | if (data.error?.code == 'forbidden') { 19 | res.status(403).end() 20 | } else if (data.error?.code == 'domain_taken') { 21 | res.status(409).end() 22 | } else { 23 | res.status(200).end() 24 | } 25 | } -------------------------------------------------------------------------------- /pages/api/v2/shorten.js: -------------------------------------------------------------------------------- 1 | import { kv } from "@vercel/kv" 2 | 3 | export default async function handler(req, res) { 4 | 5 | const body = JSON.parse(req.body) 6 | 7 | // GET urls from POST body 8 | const urls = body.urls.trim() 9 | 10 | const password = body.password 11 | 12 | // Split the URLs into an array, line break or , 13 | const urlsArray = urls.split(/[\n,]+/) 14 | 15 | const results = [] 16 | 17 | // Shorten each URL 18 | for (const url of urlsArray) { 19 | if (url === '') continue 20 | 21 | // radomly generate a key 22 | let key = Math.random().toString(36).substring(2, 5) + Math.random().toString(36).substring(2, 5) 23 | 24 | // check if key already exists 25 | const checkKey = await kv.get(key) 26 | 27 | // while check key 28 | while (checkKey) { 29 | key = Math.random().toString(36).substring(2, 5) + Math.random().toString(36).substring(2, 5) 30 | } 31 | 32 | // if password is not null, set the password 33 | if (password !== "") { 34 | key = key + "$" + password 35 | } 36 | 37 | // set the key 38 | await kv.set(key, url) 39 | 40 | // push the result to the array 41 | results.push({ 42 | key: key.split("$")[0], 43 | url: url 44 | }) 45 | } 46 | 47 | // Return the results 48 | res.status(200).json(results) 49 | } 50 | -------------------------------------------------------------------------------- /pages/api/v3/count.js: -------------------------------------------------------------------------------- 1 | import { kv } from "@vercel/kv" 2 | 3 | export default async function handler(req, res) { 4 | 5 | let count = 0 6 | 7 | for await (const key of kv.scanIterator()) { 8 | count++ 9 | } 10 | 11 | // Return the results 12 | res.status(200).json(count) 13 | 14 | } 15 | -------------------------------------------------------------------------------- /pages/api/v3/domain.js: -------------------------------------------------------------------------------- 1 | export default async function handler(req, res) { 2 | const { add } = req.query 3 | 4 | const response = await fetch( 5 | `https://api.vercel.com/v9/projects/${process.env.PROJECT_ID_VERCEL}/domains?teamId=${process.env.TEAM_ID_VERCEL}`, 6 | { 7 | body: `{\n "name": "${add.replaceAll('http://','').replaceAll('https://','')}"\n}`, 8 | headers: { 9 | Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN}`, 10 | 'Content-Type': 'application/json', 11 | }, 12 | method: 'POST', 13 | } 14 | ) 15 | 16 | const data = await response.json() 17 | 18 | if (data.error?.code == 'forbidden') { 19 | res.status(403).end() 20 | } else if (data.error?.code == 'domain_taken') { 21 | res.status(409).end() 22 | } else { 23 | res.status(200).end() 24 | } 25 | } -------------------------------------------------------------------------------- /pages/api/v3/shorten.js: -------------------------------------------------------------------------------- 1 | import { kv } from "@vercel/kv" 2 | import { stringify } from "postcss" 3 | 4 | export default async function handler(req, res) { 5 | console.log(req.body) 6 | // const body = req.body 7 | // parse body if string 8 | const body = typeof req.body === "string" ? JSON.parse(req.body) : req.body 9 | 10 | // GET urls from POST body 11 | const urls = body.urls.trim() 12 | 13 | const password = body.password 14 | 15 | // Split the URLs into an array, line break or , 16 | const urlsArray = urls.split(/[\n,]+/) 17 | 18 | const results = [] 19 | 20 | // Shorten each URL 21 | for (const url of urlsArray) { 22 | if (url === '') continue 23 | 24 | // radomly generate a key 25 | let key = Math.random().toString(36).substring(2, 5) + Math.random().toString(36).substring(2, 5) 26 | 27 | // check if key already exists 28 | const checkKey = await kv.get(key) 29 | 30 | // while check key 31 | while (checkKey) { 32 | key = Math.random().toString(36).substring(2, 5) + Math.random().toString(36).substring(2, 5) 33 | } 34 | 35 | // if password is not null, set the password 36 | if (password !== "") { 37 | key = key + "$" + password 38 | } 39 | 40 | // set the key 41 | await kv.set(key, url) 42 | 43 | // push the result to the array 44 | results.push({ 45 | key: key.split("$")[0], 46 | url: url 47 | }) 48 | } 49 | 50 | // Return the results 51 | res.status(200).json(results) 52 | } 53 | -------------------------------------------------------------------------------- /pages/api/v4/count.js: -------------------------------------------------------------------------------- 1 | import { kv } from "@vercel/kv" 2 | 3 | export default async function handler(req, res) { 4 | 5 | let count = 0 6 | 7 | for await (const key of kv.scanIterator()) { 8 | count++ 9 | } 10 | 11 | // Return the results 12 | res.status(200).json(count) 13 | 14 | } 15 | -------------------------------------------------------------------------------- /pages/api/v4/dns.js: -------------------------------------------------------------------------------- 1 | import dns from 'dns'; 2 | 3 | export default async function handler(req, res) { 4 | const { domain } = req.query; 5 | const { address: ip } = await dns.promises.lookup(domain); 6 | 7 | if (typeof ip !== 'string') { 8 | res.status(400).send('Invalid IP address'); 9 | return; 10 | } 11 | 12 | // IP string start with 76.76.21. 13 | const validIP = ip.startsWith('76.76.21.'); 14 | 15 | if (validIP) { 16 | res.status(200).send('Setup Completed'); 17 | } else { 18 | res.status(400).send('Pending for Configuring DNS'); 19 | } 20 | } -------------------------------------------------------------------------------- /pages/api/v4/domain.js: -------------------------------------------------------------------------------- 1 | export default async function handler(req, res) { 2 | const { add } = req.query 3 | 4 | const response = await fetch( 5 | `https://api.vercel.com/v9/projects/${process.env.PROJECT_ID_VERCEL}/domains?teamId=${process.env.TEAM_ID_VERCEL}`, 6 | { 7 | body: `{\n "name": "${add.replaceAll('http://','').replaceAll('https://','')}"\n}`, 8 | headers: { 9 | Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN}`, 10 | 'Content-Type': 'application/json', 11 | }, 12 | method: 'POST', 13 | } 14 | ) 15 | 16 | const data = await response.json() 17 | 18 | if (data.error?.code == 'forbidden') { 19 | res.status(403).end() 20 | } else if (data.error?.code == 'domain_taken') { 21 | res.status(409).end() 22 | } else { 23 | res.status(200).end() 24 | } 25 | } -------------------------------------------------------------------------------- /pages/api/v4/shorten.js: -------------------------------------------------------------------------------- 1 | import { kv } from "@vercel/kv" 2 | 3 | export default async function handler(req, res) { 4 | const body = typeof req.body === "string" ? JSON.parse(req.body) : req.body 5 | 6 | // GET urls from POST body 7 | const urls = body.urls.trim() 8 | 9 | const password = body.password 10 | 11 | // Split the URLs into an array, line break or , 12 | const urlsArray = urls.split(/[\n,]+/) 13 | 14 | const results = [] 15 | 16 | // Shorten each URL 17 | for (const url of urlsArray) { 18 | if (url === '') continue 19 | 20 | // Randomly generate a key 21 | let key = Math.random().toString(36).substring(2, 5) + Math.random().toString(36).substring(2, 5) 22 | 23 | //Check if the key already exists 24 | const checkKey = await kv.get(key) 25 | 26 | while (checkKey) { 27 | key = Math.random().toString(36).substring(2, 5) + Math.random().toString(36).substring(2, 5) 28 | } 29 | 30 | // If a password is not null, set the password 31 | if (password !== "") { 32 | key = key + "$" + password 33 | } 34 | 35 | // Set the key 36 | await kv.set(key, url) 37 | 38 | // Push the result to the array 39 | results.push({ 40 | key: key.split("$")[0], 41 | url: url 42 | }) 43 | } 44 | 45 | // Return the results 46 | res.status(200).json(results) 47 | } 48 | -------------------------------------------------------------------------------- /pages/api/v5/count.js: -------------------------------------------------------------------------------- 1 | import { kv } from "@vercel/kv" 2 | 3 | export default async function handler(req, res) { 4 | 5 | let count = 0 6 | 7 | for await (const key of kv.scanIterator()) { 8 | count++ 9 | } 10 | 11 | // Return the results 12 | res.status(200).json(count) 13 | 14 | } 15 | -------------------------------------------------------------------------------- /pages/api/v5/dns.js: -------------------------------------------------------------------------------- 1 | import dns from 'dns'; 2 | 3 | export default async function handler(req, res) { 4 | const { domain } = req.query; 5 | const { address: ip } = await dns.promises.lookup(domain); 6 | 7 | if (typeof ip !== 'string') { 8 | res.status(400).send('Invalid IP address'); 9 | return; 10 | } 11 | 12 | // IP string start with 76.76.21. 13 | const validIP = ip.startsWith('76.76.21.'); 14 | 15 | if (validIP) { 16 | res.status(200).send('Setup Completed'); 17 | } else { 18 | res.status(400).send('Pending for Configuring DNS'); 19 | } 20 | } -------------------------------------------------------------------------------- /pages/api/v5/domain.js: -------------------------------------------------------------------------------- 1 | export default async function handler(req, res) { 2 | const { add } = req.query 3 | 4 | const response = await fetch( 5 | `https://api.vercel.com/v9/projects/${process.env.PROJECT_ID_VERCEL}/domains?teamId=${process.env.TEAM_ID_VERCEL}`, 6 | { 7 | body: `{\n "name": "${add.replaceAll('http://','').replaceAll('https://','')}"\n}`, 8 | headers: { 9 | Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN}`, 10 | 'Content-Type': 'application/json', 11 | }, 12 | method: 'POST', 13 | } 14 | ) 15 | 16 | const data = await response.json() 17 | 18 | if (data.error?.code == 'forbidden') { 19 | res.status(403).end() 20 | } else if (data.error?.code == 'domain_taken') { 21 | res.status(409).end() 22 | } else { 23 | res.status(200).end() 24 | } 25 | } -------------------------------------------------------------------------------- /pages/api/v5/shorten.js: -------------------------------------------------------------------------------- 1 | import { kv } from "@vercel/kv" 2 | 3 | export default async function handler(req, res) { 4 | const body = typeof req.body === "string" ? JSON.parse(req.body) : req.body 5 | const urls = body.urls.trim() 6 | const password = body.password 7 | const urlsArray = urls.split(/[\n,]+/) 8 | 9 | const promises = urlsArray.map(async (url) => { 10 | if (url === '') return null 11 | 12 | let keys = Array.from({length: 10}, () => Math.random().toString(36).substring(2, 5) + Math.random().toString(36).substring(2, 5)) 13 | let existingKeys = await Promise.all(keys.map(key => kv.get(key))) 14 | let key = keys.find((key, index) => !existingKeys[index]) 15 | 16 | if (password !== "") { 17 | key = key + "$" + password 18 | } 19 | 20 | await kv.set(key, url) 21 | 22 | return { 23 | key: key.split("$")[0], 24 | url: url 25 | } 26 | }) 27 | 28 | const results = await Promise.all(promises) 29 | 30 | res.status(200).json(results.filter(result => result !== null)) 31 | } -------------------------------------------------------------------------------- /pages/api/v6/count.js: -------------------------------------------------------------------------------- 1 | import { Redis } from '@upstash/redis'; 2 | 3 | const redis = Redis.fromEnv(); 4 | const CACHE_KEY = 'url_count_cache'; 5 | const CACHE_EXPIRY = 60 * 5; // 5 minutes in seconds 6 | 7 | export default async function handler(req, res) { 8 | try { 9 | // Set Cache-Control header for HTTP caching (CDN and browsers) 10 | res.setHeader('Cache-Control', 'public, s-maxage=300, stale-while-revalidate=59'); 11 | 12 | // Try to get the count from cache first 13 | const cachedCount = await redis.get(CACHE_KEY); 14 | 15 | if (cachedCount !== null) { 16 | // Return cached count if available 17 | return res.status(200).json(parseInt(cachedCount)); 18 | } 19 | 20 | // If no cached value, calculate the count 21 | let count = 0; 22 | let cursor = "0"; 23 | do { 24 | const [nextCursor, keys] = await redis.scan(cursor, { count: 100 }); 25 | count += keys.length; 26 | cursor = nextCursor; 27 | } while (cursor !== "0"); 28 | 29 | // Cache the new count 30 | await redis.set(CACHE_KEY, count.toString(), { ex: CACHE_EXPIRY }); 31 | 32 | res.status(200).json(count); 33 | } catch (err) { 34 | res.status(500).json({ error: 'Internal Server Error', details: err.message }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /pages/api/v6/dns.js: -------------------------------------------------------------------------------- 1 | import dns from 'dns'; 2 | 3 | export default async function handler(req, res) { 4 | const { domain } = req.query; 5 | const { address: ip } = await dns.promises.lookup(domain); 6 | 7 | if (typeof ip !== 'string') { 8 | res.status(400).send('Invalid IP address'); 9 | return; 10 | } 11 | 12 | // IP string start with 76.76.21. 13 | const validIP = ip.startsWith('76.76.21.') || ip.startsWith('216.198.79.') || ip.startsWith('64.29.17.'); 14 | 15 | if (validIP) { 16 | res.status(200).send('Setup Completed'); 17 | } else { 18 | res.status(400).send('Pending for Configuring DNS'); 19 | } 20 | } -------------------------------------------------------------------------------- /pages/api/v6/domain.js: -------------------------------------------------------------------------------- 1 | export default async function handler(req, res) { 2 | const { add } = req.query 3 | 4 | try { 5 | const response = await fetch( 6 | `https://api.vercel.com/v9/projects/${process.env.PROJECT_ID_VERCEL}/domains?teamId=${process.env.TEAM_ID_VERCEL}`, 7 | { 8 | body: `{\n "name": "${add.replaceAll('http://','').replaceAll('https://','')}"\n}`, 9 | headers: { 10 | Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN}`, 11 | 'Content-Type': 'application/json', 12 | }, 13 | method: 'POST', 14 | } 15 | ) 16 | 17 | var data = await response.json() 18 | 19 | if (data.error) { 20 | const { projectName, teamName, ...cleanedData } = data.error 21 | data = cleanedData 22 | } else { 23 | const { projectId, ...cleanedData } = data 24 | data = cleanedData 25 | } 26 | 27 | return res.status(response.status).json(data) 28 | 29 | } catch (err) { 30 | return res.status(500).json({ error: 'Unexpected error occurred!', details: err.message }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pages/api/v6/shorten.js: -------------------------------------------------------------------------------- 1 | import { Redis } from "@upstash/redis" 2 | 3 | const redis = Redis.fromEnv(); 4 | 5 | export default async function handler(req, res) { 6 | const body = typeof req.body === "string" ? JSON.parse(req.body) : req.body 7 | const urls = body.urls.trim() 8 | const password = body.password 9 | const urlsArray = urls.split(/[\n,]+/) 10 | 11 | const promises = urlsArray.map(async (url) => { 12 | if (url === '') return null 13 | 14 | let keys = Array.from({length: 10}, () => Math.random().toString(36).substring(2, 5) + Math.random().toString(36).substring(2, 5)) 15 | let existingKeys = await Promise.all(keys.map(key => redis.get(key))) 16 | let key = keys.find((key, index) => !existingKeys[index]) 17 | 18 | if (password !== "") { 19 | key = key + "$" + password 20 | } 21 | 22 | await redis.set(key, url) 23 | 24 | return { 25 | key: key.split("$")[0], 26 | url: url 27 | } 28 | }) 29 | 30 | const results = await Promise.all(promises) 31 | 32 | res.status(200).json(results.filter(result => result !== null)) 33 | } -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | 3 | export default function Home() { 4 | 5 | const [results, setResults] = useState([]) 6 | const [loading, setLoading] = useState(false) 7 | 8 | // Handle form submit 9 | const handleSubmit = async () => { 10 | // Prevent 11 | event.preventDefault() 12 | 13 | // Set loading 14 | setLoading(true) 15 | 16 | // Get the form data 17 | const form = document.getElementById('form') 18 | const formData = new FormData(form) 19 | const urls = formData.get('urls') 20 | const password = formData.get('password') 21 | const domain = formData.get('domain') 22 | const ref = formData.get('ref') 23 | 24 | // GET /api/{API Version}/domain?add={domain} 25 | if (domain !== "") { 26 | await fetch(`/api/v6/domain?add=${domain}`) 27 | .then(res => { 28 | if (res.status === 403) { 29 | alert('You are not authorized to add this domain.') 30 | } else if (res.status === 409) { 31 | alert('This domain/subdomain is already taken. Please remove it from your Vercel account and try again.') 32 | } else if (res.status === 200) { 33 | // check domain ip CNAME to cname.vercel-dns.com 34 | fetch(`https://dns.google/resolve?name=${domain}&type=CNAME`) 35 | .then(res => res.json()) 36 | .then(data => { 37 | if ( (data.Answer && data.Answer[0].data === 'cname.vercel-dns.com.') || (data.Authority && data.Authority[0].name === 'vercel.app.') ) { 38 | } 39 | else { 40 | alert('Domain/Subdomain added successfully. Please use CNAME and point to cname.vercel-dns.com.') 41 | } 42 | }) 43 | } else { 44 | alert('Something went wrong, please try again later.') 45 | } 46 | }) 47 | .catch(err => { 48 | console.error(err) 49 | alert('Something went wrong, please try again later.') 50 | }) 51 | } 52 | 53 | // POST to /api/{API Version}/shorten 54 | await fetch('/api/v6/shorten', { 55 | method: 'POST', 56 | body: JSON.stringify({ 57 | urls: urls, 58 | password: password 59 | }) 60 | }) 61 | .then(res => res.json()) 62 | .then(data => { 63 | setResults(data) 64 | setLoading(false) 65 | }) 66 | .catch(err => { 67 | console.error(err) 68 | alert('Something went wrong, please try again later.') 69 | setLoading(false) 70 | }) 71 | } 72 | 73 | // Download results as CSV 74 | const downloadCSV = () => { 75 | const csv = results.map(result => `${result.url},${window.location.origin}/${result.key}`).join('\n') 76 | const blob = new Blob([csv], { type: 'text/csv' }) 77 | const url = window.URL.createObjectURL(blob) 78 | const a = document.createElement('a') 79 | a.setAttribute('hidden', '') 80 | a.setAttribute('href', url) 81 | a.setAttribute('download', 'shortenurl.csv') 82 | document.body.appendChild(a) 83 | a.click() 84 | document.body.removeChild(a) 85 | } 86 | 87 | // Download results as xlsx 88 | const downloadXLSX = () => { 89 | const xlsx = results.map(result => `${result.url},${window.location.origin}/${result.key}`).join('\n') 90 | const blob = new Blob([xlsx], { type: 'text/xlsx' }) 91 | const url = window.URL.createObjectURL(blob) 92 | const a = document.createElement('a') 93 | a.setAttribute('hidden', '') 94 | a.setAttribute('href', url) 95 | a.setAttribute('download', 'shortenurl.xlsx') 96 | document.body.appendChild(a) 97 | a.click() 98 | document.body.removeChild(a) 99 | } 100 | 101 | // Download results as JSON 102 | const downloadJSON = () => { 103 | const json = JSON.stringify(results, null, 2) 104 | const blob = new Blob([json], { type: 'text/json' }) 105 | const url = window.URL.createObjectURL(blob) 106 | const a = document.createElement('a') 107 | a.setAttribute('hidden', '') 108 | a.setAttribute('href', url) 109 | a.setAttribute('download', 'shortenurl.json') 110 | document.body.appendChild(a) 111 | a.click() 112 | document.body.removeChild(a) 113 | } 114 | 115 | 116 | // Get custom domain 117 | const customDomain = () => { 118 | const form = document.getElementById('form') 119 | const formData = new FormData(form) 120 | const domain = formData.get('domain') 121 | if (domain !== "") { 122 | return domain 123 | } else { 124 | return null 125 | } 126 | } 127 | 128 | const randomBG = () => { 129 | const bgList = [ 130 | "img/city.jpg", 131 | "img/cityNight.jpeg", 132 | "img/mountain.jpeg", 133 | ] 134 | return bgList[Math.floor(Math.random() * bgList.length)]; 135 | } 136 | 137 | useEffect(() => { 138 | // Autogrow textarea 139 | const textarea = document.getElementById('textarea') 140 | textarea.addEventListener('input', () => { 141 | textarea.style.height = 'auto' 142 | textarea.style.height = textarea.scrollHeight + 'px' 143 | }) 144 | 145 | const interval = setInterval(() => { 146 | // Fade in and out 147 | document.getElementById("bg").classList.add("opacity-0"); 148 | setTimeout(() => { 149 | document.getElementById("bg").classList.remove("opacity-0"); 150 | }, 1000); 151 | 152 | document.getElementById("bg").src = randomBG(); 153 | }, 30000); 154 | return () => clearInterval(interval); 155 | }, []) 156 | 157 | return ( 158 |
159 |
160 |
161 |
162 |
163 |
164 | 172 | 173 | {/* Headings */} 174 |

Magic Teleport

175 |

176 | An URL Shortener Solution. 177 |

178 | 179 | {/* Form */} 180 |
181 | {/* URL Input */} 182 |
183 | 184 |
185 | 186 |
187 | 188 | {/*
189 | */} 190 |
191 |
192 | 196 | 200 | 201 | API 202 | 203 | 204 |
205 |
206 |
207 | 208 | {/* Results */} 209 |
210 | 211 |
212 | {results.length > 0 ? 'Here are your shortened URLs:' : 'Your shortened URLs will appear here.'} 213 | 214 | 215 | {results.map((result, index) => ( 216 | 217 | 223 | 245 | 246 | ))} 247 | 248 |
218 | 219 | 220 | {result.url.replaceAll('http://','').replaceAll('https://','')} 221 | 222 | 224 | {/* domain */} 225 | {/*
226 | { (customDomain() ?? window.location.origin).replaceAll('http://','').replaceAll('https://','') }/ 227 |
*/} 228 | {/* Preview */} 229 | 230 | {`${result.key}`} 231 | 232 | 233 | / 234 | {/* Copy btn */} 235 | 244 |
249 | {results.length > 0 && ( 250 |
251 | 255 | 259 | 263 |
264 | )} 265 |
266 |
267 |
268 |
269 |
270 | Background 272 |
273 | ) 274 | } 275 | -------------------------------------------------------------------------------- /pages/unlock.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | import { useRouter } from 'next/router' 3 | 4 | export default function Unlock() { 5 | const router = useRouter() 6 | 7 | const randomBG = () => { 8 | const bgList = [ 9 | "img/city.jpg", 10 | "img/cityNight.jpeg", 11 | "img/mountain.jpeg", 12 | ] 13 | return bgList[Math.floor(Math.random() * bgList.length)]; 14 | } 15 | 16 | // handleSubmit redirect to /{key}${password} 17 | const handleSubmit = async () => { 18 | // Prevent 19 | event.preventDefault() 20 | 21 | // Get the form data 22 | const form = document.getElementById('form') 23 | const formData = new FormData(form) 24 | const key = router.query.key 25 | 26 | const password = formData.get('password') 27 | 28 | // redirect to /${key}$${password} 29 | window.location.href = `/${key}$${password}` 30 | } 31 | 32 | useEffect(() => { 33 | const interval = setInterval(() => { 34 | // Fade in and out 35 | document.getElementById("bg").classList.add("opacity-0"); 36 | setTimeout(() => { 37 | document.getElementById("bg").classList.remove("opacity-0"); 38 | }, 1000); 39 | 40 | document.getElementById("bg").src = randomBG(); 41 | }, 30000); 42 | return () => clearInterval(interval); 43 | }, []) 44 | 45 | return ( 46 |
47 |
48 |
49 | 50 |
51 | 52 |
53 | 54 | 62 | 63 | {/* Headings */} 64 | 65 |

Magic Teleport Lock

66 |

67 | This page is secured. Please enter the password to continue. 68 |

69 | 70 | {/* Form */} 71 |
72 |
73 | 74 |
75 |
76 | 80 |
81 |
82 | 83 |
84 |
85 |
86 | 87 |
88 | ) 89 | } 90 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/img/city.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1998code/shorten-url/dd19c13182e851384491d4756ff8d78124fff823/public/img/city.jpg -------------------------------------------------------------------------------- /public/img/cityNight.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1998code/shorten-url/dd19c13182e851384491d4756ff8d78124fff823/public/img/cityNight.jpeg -------------------------------------------------------------------------------- /public/img/mountain.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1998code/shorten-url/dd19c13182e851384491d4756ff8d78124fff823/public/img/mountain.jpeg -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './pages/**/*.{js,ts,jsx,tsx,mdx}', 5 | './components/**/*.{js,ts,jsx,tsx,mdx}', 6 | './app/**/*.{js,ts,jsx,tsx,mdx}', 7 | ], 8 | theme: { 9 | extend: { 10 | backgroundImage: { 11 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 12 | 'gradient-conic': 13 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 14 | }, 15 | }, 16 | }, 17 | plugins: [], 18 | } 19 | --------------------------------------------------------------------------------