mirror of
https://github.com/iLay1678/i-tools.git
synced 2025-07-03 10:42:15 +08:00
改为使用pages
This commit is contained in:
parent
f06209f8da
commit
f4cf6451f8
@ -1,12 +0,0 @@
|
|||||||
# http://editorconfig.org
|
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
indent_style = tab
|
|
||||||
end_of_line = lf
|
|
||||||
charset = utf-8
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[*.yml]
|
|
||||||
indent_style = space
|
|
17
.github/workflows/deploy.yml
vendored
17
.github/workflows/deploy.yml
vendored
@ -1,17 +0,0 @@
|
|||||||
name: Deploy
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Deploy
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Deploy
|
|
||||||
uses: cloudflare/wrangler-action@v3
|
|
||||||
with:
|
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
196
.gitignore
vendored
196
.gitignore
vendored
@ -1,172 +1,40 @@
|
|||||||
# Logs
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
logs
|
# dependencies
|
||||||
_.log
|
/node_modules
|
||||||
npm-debug.log_
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
.yarn/install-state.gz
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
lerna-debug.log*
|
|
||||||
.pnpm-debug.log*
|
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
# local env files
|
||||||
|
.env*.local
|
||||||
|
|
||||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
# Runtime data
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
pids
|
next-env.d.ts
|
||||||
_.pid
|
|
||||||
_.seed
|
|
||||||
\*.pid.lock
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
||||||
|
|
||||||
lib-cov
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
|
|
||||||
coverage
|
|
||||||
\*.lcov
|
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
|
||||||
|
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
||||||
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
|
|
||||||
node_modules/
|
|
||||||
jspm_packages/
|
|
||||||
|
|
||||||
# Snowpack dependency directory (https://snowpack.dev/)
|
|
||||||
|
|
||||||
web_modules/
|
|
||||||
|
|
||||||
# TypeScript cache
|
|
||||||
|
|
||||||
\*.tsbuildinfo
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Optional stylelint cache
|
|
||||||
|
|
||||||
.stylelintcache
|
|
||||||
|
|
||||||
# Microbundle cache
|
|
||||||
|
|
||||||
.rpt2_cache/
|
|
||||||
.rts2_cache_cjs/
|
|
||||||
.rts2_cache_es/
|
|
||||||
.rts2_cache_umd/
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
|
|
||||||
\*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# dotenv environment variable files
|
|
||||||
|
|
||||||
.env
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
.env.local
|
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
|
||||||
|
|
||||||
.cache
|
|
||||||
.parcel-cache
|
|
||||||
|
|
||||||
# Next.js build output
|
|
||||||
|
|
||||||
.next
|
|
||||||
out
|
|
||||||
|
|
||||||
# Nuxt.js build / generate output
|
|
||||||
|
|
||||||
.nuxt
|
|
||||||
dist
|
|
||||||
|
|
||||||
# Gatsby files
|
|
||||||
|
|
||||||
.cache/
|
|
||||||
|
|
||||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
|
||||||
|
|
||||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
|
||||||
|
|
||||||
# public
|
|
||||||
|
|
||||||
# vuepress build output
|
|
||||||
|
|
||||||
.vuepress/dist
|
|
||||||
|
|
||||||
# vuepress v2.x temp and cache directory
|
|
||||||
|
|
||||||
.temp
|
|
||||||
.cache
|
|
||||||
|
|
||||||
# Docusaurus cache and generated files
|
|
||||||
|
|
||||||
.docusaurus
|
|
||||||
|
|
||||||
# Serverless directories
|
|
||||||
|
|
||||||
.serverless/
|
|
||||||
|
|
||||||
# FuseBox cache
|
|
||||||
|
|
||||||
.fusebox/
|
|
||||||
|
|
||||||
# DynamoDB Local files
|
|
||||||
|
|
||||||
.dynamodb/
|
|
||||||
|
|
||||||
# TernJS port file
|
|
||||||
|
|
||||||
.tern-port
|
|
||||||
|
|
||||||
# Stores VSCode versions used for testing VSCode extensions
|
|
||||||
|
|
||||||
.vscode-test
|
|
||||||
|
|
||||||
# yarn v2
|
|
||||||
|
|
||||||
.yarn/cache
|
|
||||||
.yarn/unplugged
|
|
||||||
.yarn/build-state.yml
|
|
||||||
.yarn/install-state.gz
|
|
||||||
.pnp.\*
|
|
||||||
|
|
||||||
# wrangler project
|
|
||||||
|
|
||||||
|
# wrangler files
|
||||||
|
.wrangler
|
||||||
.dev.vars
|
.dev.vars
|
||||||
.wrangler/
|
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"printWidth": 140,
|
|
||||||
"singleQuote": true,
|
|
||||||
"semi": true,
|
|
||||||
"useTabs": true
|
|
||||||
}
|
|
71
README.md
71
README.md
@ -4,3 +4,74 @@
|
|||||||
|
|
||||||
- `/` 在线扫码
|
- `/` 在线扫码
|
||||||
- `/refresh` 刷新令牌
|
- `/refresh` 刷新令牌
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`c3`](https://developers.cloudflare.com/pages/get-started/c3).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
First, run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
yarn dev
|
||||||
|
# or
|
||||||
|
pnpm dev
|
||||||
|
# or
|
||||||
|
bun dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
## Cloudflare integration
|
||||||
|
|
||||||
|
Besides the `dev` script mentioned above `c3` has added a few extra scripts that allow you to integrate the application with the [Cloudflare Pages](https://pages.cloudflare.com/) environment, these are:
|
||||||
|
- `pages:build` to build the application for Pages using the [`@cloudflare/next-on-pages`](https://github.com/cloudflare/next-on-pages) CLI
|
||||||
|
- `preview` to locally preview your Pages application using the [Wrangler](https://developers.cloudflare.com/workers/wrangler/) CLI
|
||||||
|
- `deploy` to deploy your Pages application using the [Wrangler](https://developers.cloudflare.com/workers/wrangler/) CLI
|
||||||
|
|
||||||
|
> __Note:__ while the `dev` script is optimal for local development you should preview your Pages application as well (periodically or before deployments) in order to make sure that it can properly work in the Pages environment (for more details see the [`@cloudflare/next-on-pages` recommended workflow](https://github.com/cloudflare/next-on-pages/blob/main/internal-packages/next-dev/README.md#recommended-development-workflow))
|
||||||
|
|
||||||
|
### Bindings
|
||||||
|
|
||||||
|
Cloudflare [Bindings](https://developers.cloudflare.com/pages/functions/bindings/) are what allows you to interact with resources available in the Cloudflare Platform.
|
||||||
|
|
||||||
|
You can use bindings during development, when previewing locally your application and of course in the deployed application:
|
||||||
|
|
||||||
|
- To use bindings in dev mode you need to define them in the `next.config.js` file under `setupDevBindings`, this mode uses the `next-dev` `@cloudflare/next-on-pages` submodule. For more details see its [documentation](https://github.com/cloudflare/next-on-pages/blob/05b6256/internal-packages/next-dev/README.md).
|
||||||
|
|
||||||
|
- To use bindings in the preview mode you need to add them to the `pages:preview` script accordingly to the `wrangler pages dev` command. For more details see its [documentation](https://developers.cloudflare.com/workers/wrangler/commands/#dev-1) or the [Pages Bindings documentation](https://developers.cloudflare.com/pages/functions/bindings/).
|
||||||
|
|
||||||
|
- To use bindings in the deployed application you will need to configure them in the Cloudflare [dashboard](https://dash.cloudflare.com/). For more details see the [Pages Bindings documentation](https://developers.cloudflare.com/pages/functions/bindings/).
|
||||||
|
|
||||||
|
#### KV Example
|
||||||
|
|
||||||
|
`c3` has added for you an example showing how you can use a KV binding.
|
||||||
|
|
||||||
|
In order to enable the example:
|
||||||
|
- Search for javascript/typescript lines containing the following comment:
|
||||||
|
```ts
|
||||||
|
// KV Example:
|
||||||
|
```
|
||||||
|
and uncomment the commented lines below it.
|
||||||
|
- Do the same in the `wrangler.toml` file, where
|
||||||
|
the comment is:
|
||||||
|
```
|
||||||
|
# KV Example:
|
||||||
|
```
|
||||||
|
- If you're using TypeScript run the `cf-typegen` script to update the `env.d.ts` file:
|
||||||
|
```bash
|
||||||
|
npm run cf-typegen
|
||||||
|
# or
|
||||||
|
yarn cf-typegen
|
||||||
|
# or
|
||||||
|
pnpm cf-typegen
|
||||||
|
# or
|
||||||
|
bun cf-typegen
|
||||||
|
```
|
||||||
|
|
||||||
|
After doing this you can run the `dev` or `preview` script and visit the `/api/hello` route to see the example in action.
|
||||||
|
|
||||||
|
Finally, if you also want to see the example work in the deployed application make sure to add a `MY_KV_NAMESPACE` binding to your Pages application in its [dashboard kv bindings settings section](https://dash.cloudflare.com/?to=/:account/pages/view/:pages-project/settings/functions#kv_namespace_bindings_section). After having configured it make sure to re-deploy your application.
|
||||||
|
7
jsconfig.json
Normal file
7
jsconfig.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
next.config.mjs
Normal file
13
next.config.mjs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { setupDevPlatform } from '@cloudflare/next-on-pages/next-dev';
|
||||||
|
|
||||||
|
// Here we use the @cloudflare/next-on-pages next-dev module to allow us to use bindings during local development
|
||||||
|
// (when running the application with `next dev`), for more information see:
|
||||||
|
// https://github.com/cloudflare/next-on-pages/blob/main/internal-packages/next-dev/README.md
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
await setupDevPlatform();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {};
|
||||||
|
|
||||||
|
export default nextConfig;
|
6033
package-lock.json
generated
6033
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
@ -1,20 +1,29 @@
|
|||||||
{
|
{
|
||||||
"name": "alipan-tv-token",
|
"name": "alipan-tv-token-next",
|
||||||
"version": "0.0.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"deploy": "wrangler deploy",
|
"dev": "next dev",
|
||||||
"dev": "wrangler dev",
|
"build": "next build",
|
||||||
"start": "wrangler dev",
|
"start": "next start",
|
||||||
"test": "vitest"
|
"lint": "next lint",
|
||||||
|
"pages:build": "npx @cloudflare/next-on-pages",
|
||||||
|
"preview": "npm run pages:build && wrangler pages dev",
|
||||||
|
"deploy": "npm run pages:build && wrangler pages deploy"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"next": "14.2.5",
|
||||||
|
"react": "^18",
|
||||||
|
"react-dom": "^18",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"js-md5": "^0.8.3"
|
"js-md5": "^0.8.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/vitest-pool-workers": "^0.4.5",
|
"@cloudflare/next-on-pages": "^1.13.6",
|
||||||
"wrangler": "^3.60.3",
|
"@cloudflare/workers-types": "^4.20241202.0",
|
||||||
"vitest": "1.5.0"
|
"postcss": "^8",
|
||||||
|
"tailwindcss": "^3.4.1",
|
||||||
|
"vercel": "^39.1.3",
|
||||||
|
"wrangler": "^3.92.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
8
postcss.config.mjs
Normal file
8
postcss.config.mjs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/** @type {import('postcss-load-config').Config} */
|
||||||
|
const config = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
1
public/next.svg
Normal file
1
public/next.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
1
public/vercel.svg
Normal file
1
public/vercel.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|
After Width: | Height: | Size: 629 B |
21
src/app/api/hello/route.js
Normal file
21
src/app/api/hello/route.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { getRequestContext } from '@cloudflare/next-on-pages'
|
||||||
|
|
||||||
|
export const runtime = 'edge'
|
||||||
|
|
||||||
|
export async function GET(request) {
|
||||||
|
let responseText = 'Hello World'
|
||||||
|
|
||||||
|
// In the edge runtime you can use Bindings that are available in your application
|
||||||
|
// (for more details see:
|
||||||
|
// - https://developers.cloudflare.com/pages/framework-guides/deploy-a-nextjs-site/#use-bindings-in-your-nextjs-application
|
||||||
|
// - https://developers.cloudflare.com/pages/functions/bindings/
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// KV Example:
|
||||||
|
// const myKv = getRequestContext().env.MY_KV_NAMESPACE
|
||||||
|
// await myKv.put('suffix', ' from a KV store!')
|
||||||
|
// const suffix = await myKv.get('suffix')
|
||||||
|
// responseText += suffix
|
||||||
|
|
||||||
|
return new Response(responseText)
|
||||||
|
}
|
49
src/app/check_status/[sid]/route.js
Normal file
49
src/app/check_status/[sid]/route.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
import { decrypt, getParams } from '../../utils/decode';
|
||||||
|
|
||||||
|
export const runtime = 'edge'
|
||||||
|
|
||||||
|
export async function GET(request, { params }) {
|
||||||
|
try {
|
||||||
|
const { sid } = params;
|
||||||
|
const response = await fetch(`https://openapi.alipan.com/oauth/qrcode/${sid}/status`);
|
||||||
|
const statusData = await response.json();
|
||||||
|
|
||||||
|
if (statusData.status === 'LoginSuccess') {
|
||||||
|
try {
|
||||||
|
const authCode = statusData.authCode;
|
||||||
|
const t = Math.floor(Date.now() / 1000);
|
||||||
|
const sendData = { ...getParams(t), code: authCode, "Content-Type": "application/json"};
|
||||||
|
const headers = Object.fromEntries(
|
||||||
|
Object.entries(sendData).map(([k, v]) => [k, String(v)])
|
||||||
|
);
|
||||||
|
|
||||||
|
const tokenResponse = await fetch('http://api.extscreen.com/aliyundrive/v3/token', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: headers,
|
||||||
|
body: JSON.stringify(sendData)
|
||||||
|
});
|
||||||
|
|
||||||
|
const tokenData = await tokenResponse.json();
|
||||||
|
const jsonp = tokenData.data;
|
||||||
|
const plainData = decrypt(jsonp.ciphertext, jsonp.iv, t);
|
||||||
|
const tokenInfo = JSON.parse(plainData);
|
||||||
|
|
||||||
|
return Response.json({
|
||||||
|
status: 'LoginSuccess',
|
||||||
|
refresh_token: tokenInfo.refresh_token,
|
||||||
|
access_token: tokenInfo.access_token
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return Response.json({ status: 'LoginFailed' });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Response.json({ status: statusData.status });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return Response.json(
|
||||||
|
{ error: error.message },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
BIN
src/app/favicon.ico
Normal file
BIN
src/app/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
26
src/app/generate_qr/route.js
Normal file
26
src/app/generate_qr/route.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
export const runtime = 'edge'
|
||||||
|
|
||||||
|
export async function POST() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('http://api.extscreen.com/aliyundrive/qrcode', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
scopes: ["user:base", "file:all:read", "file:all:write"].join(','),
|
||||||
|
width: 500,
|
||||||
|
height: 500,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
return Response.json({
|
||||||
|
qr_link: data.data.qrCodeUrl,
|
||||||
|
sid: data.data.sid
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return Response.json(
|
||||||
|
{ error: error.message },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
33
src/app/globals.css
Normal file
33
src/app/globals.css
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--foreground-rgb: 0, 0, 0;
|
||||||
|
--background-start-rgb: 214, 219, 220;
|
||||||
|
--background-end-rgb: 255, 255, 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--foreground-rgb: 255, 255, 255;
|
||||||
|
--background-start-rgb: 0, 0, 0;
|
||||||
|
--background-end-rgb: 0, 0, 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: rgb(var(--foreground-rgb));
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
transparent,
|
||||||
|
rgb(var(--background-end-rgb))
|
||||||
|
)
|
||||||
|
rgb(var(--background-start-rgb));
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.text-balance {
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
|
}
|
17
src/app/layout.js
Normal file
17
src/app/layout.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Inter } from "next/font/google";
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "Create Next App",
|
||||||
|
description: "Generated by create next app",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({ children }) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className={inter.className}>{children}</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
58
src/app/not-found.js
Normal file
58
src/app/not-found.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
export const runtime = "edge";
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<title>404: This page could not be found.</title>
|
||||||
|
<div style={styles.error}>
|
||||||
|
<div>
|
||||||
|
<style
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<h1 className="next-error-h1" style={styles.h1}>
|
||||||
|
404
|
||||||
|
</h1>
|
||||||
|
<div style={styles.desc}>
|
||||||
|
<h2 style={styles.h2}>This page could not be found.</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
error: {
|
||||||
|
fontFamily:
|
||||||
|
'system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',
|
||||||
|
height: "100vh",
|
||||||
|
textAlign: "center",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
|
||||||
|
desc: {
|
||||||
|
display: "inline-block",
|
||||||
|
},
|
||||||
|
|
||||||
|
h1: {
|
||||||
|
display: "inline-block",
|
||||||
|
margin: "0 20px 0 0",
|
||||||
|
padding: "0 23px 0 0",
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 500,
|
||||||
|
verticalAlign: "top",
|
||||||
|
lineHeight: "49px",
|
||||||
|
},
|
||||||
|
|
||||||
|
h2: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 400,
|
||||||
|
lineHeight: "49px",
|
||||||
|
margin: 0,
|
||||||
|
},
|
||||||
|
};
|
123
src/app/page.js
Normal file
123
src/app/page.js
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useEffect, useState, useRef } from 'react'
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
// 添加状态变量
|
||||||
|
const [alertMsg, setAlertMsg] = useState('');
|
||||||
|
const [alertType, setAlertType] = useState('success'); // 可选值:'success','error'
|
||||||
|
const hasGenerated = useRef(false); // 新增
|
||||||
|
const [qrCodeSrc, setQrCodeSrc] = useState(''); // 新增,初始为空表示未加载
|
||||||
|
|
||||||
|
async function generateQR() {
|
||||||
|
const response = await fetch("/generate_qr", {
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
// 更新二维码图片的 src
|
||||||
|
setQrCodeSrc(data.qr_link);
|
||||||
|
checkStatus(data.sid);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkStatus(sid) {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/check_status/" + sid);
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.status === "LoginSuccess") {
|
||||||
|
document.querySelector("h1").innerText = "获取成功";
|
||||||
|
document.getElementById("accessToken").value = data.access_token;
|
||||||
|
document.getElementById("refreshToken").value = data.refresh_token;
|
||||||
|
document.getElementById("tokens").style.display = "block";
|
||||||
|
document.getElementById("qrCodeContainer").style.display = "none";
|
||||||
|
// 登录成功,不再继续调用 checkStatus
|
||||||
|
} else if (data.status === "ScanSuccess") {
|
||||||
|
document.querySelector("h1").innerText = "扫码成功,等待手机端授权";
|
||||||
|
// 继续轮询
|
||||||
|
setTimeout(() => checkStatus(sid), 2000);
|
||||||
|
} else if (data.status === "LoginFailed") {
|
||||||
|
document.querySelector("h1").innerText = "登录失败,请刷新页面重试";
|
||||||
|
setAlertMsg("登录失败,请刷新页面重试");
|
||||||
|
setAlertType('error');
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
// 其他状态,继续轮询
|
||||||
|
setTimeout(() => checkStatus(sid), 2000);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("检查状态时出错:", error);
|
||||||
|
setAlertMsg("发生错误,请稍后重试");
|
||||||
|
setAlertType('error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyToClipboard(elementId,msg) {
|
||||||
|
const copyText = document.getElementById(elementId).value;
|
||||||
|
navigator.clipboard.writeText(copyText).then(() => {
|
||||||
|
// 复制成功
|
||||||
|
setAlertMsg(msg);
|
||||||
|
setAlertType('success');
|
||||||
|
}).catch(err => {
|
||||||
|
// 复制失败
|
||||||
|
setAlertMsg("复制失败");
|
||||||
|
setAlertType('error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!hasGenerated.current) {
|
||||||
|
generateQR();
|
||||||
|
hasGenerated.current = true;
|
||||||
|
}
|
||||||
|
}, []); // 依赖项为空数组
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="flex min-h-screen flex-col items-center justify-center bg-gray-100">
|
||||||
|
{/* 添加卡片容器 */}
|
||||||
|
<div className="bg-white shadow-lg rounded-lg p-8">
|
||||||
|
{/* 提示组件 */}
|
||||||
|
{alertMsg && (
|
||||||
|
<div className={`mb-4 p-4 text-white rounded ${alertType === 'success' ? 'bg-green-500' : 'bg-red-500'}`}>
|
||||||
|
{alertMsg}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<h1 className="text-2xl font-bold text-gray-800 mb-6 text-center">扫描二维码登录</h1>
|
||||||
|
<div id="qrCodeContainer" className="flex justify-center mb-6">
|
||||||
|
{qrCodeSrc ? (
|
||||||
|
<img
|
||||||
|
id="qrCode"
|
||||||
|
src={qrCodeSrc}
|
||||||
|
alt="二维码"
|
||||||
|
className="w-64 h-64" // 设置固定宽高,与占位符一致
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="w-64 h-64 flex items-center justify-center bg-gray-200">
|
||||||
|
<span className="text-gray-500">二维码加载中...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div id="tokens" className="hidden">
|
||||||
|
<div className="mb-4">
|
||||||
|
<label htmlFor="accessToken" className="block text-gray-700 font-medium mb-2">访问令牌:</label>
|
||||||
|
<div className="flex">
|
||||||
|
<input type="text" id="accessToken" className="flex-1 p-2 border border-gray-300 rounded-l" readOnly />
|
||||||
|
<button onClick={() => copyToClipboard('accessToken','已复制访问令牌')}
|
||||||
|
className="px-4 bg-blue-500 text-white rounded-r hover:bg-blue-600">
|
||||||
|
复制
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<label htmlFor="refreshToken" className="block text-gray-700 font-medium mb-2">刷新令牌:</label>
|
||||||
|
<div className="flex">
|
||||||
|
<input type="text" id="refreshToken" className="flex-1 p-2 border border-gray-300 rounded-l" readOnly />
|
||||||
|
<button onClick={() => copyToClipboard('refreshToken','已复制刷新令牌')}
|
||||||
|
className="px-4 bg-blue-500 text-white rounded-r hover:bg-blue-600">
|
||||||
|
复制
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
43
src/app/refresh/route.js
Normal file
43
src/app/refresh/route.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
import { decrypt, getParams } from '../utils/decode';
|
||||||
|
|
||||||
|
export const runtime = 'edge'
|
||||||
|
|
||||||
|
export async function POST(request) {
|
||||||
|
try {
|
||||||
|
const { refresh_token } = await request.json();
|
||||||
|
const t = Math.floor(Date.now() / 1000);
|
||||||
|
const sendData = {
|
||||||
|
...getParams(t),
|
||||||
|
refresh_token: refresh_token,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
};
|
||||||
|
|
||||||
|
const headers = Object.fromEntries(
|
||||||
|
Object.entries(sendData).map(([k, v]) => [k, String(v)])
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await fetch('http://api.extscreen.com/aliyundrive/v3/token', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: headers,
|
||||||
|
body: JSON.stringify(sendData)
|
||||||
|
});
|
||||||
|
|
||||||
|
const tokenData = await response.json();
|
||||||
|
const jsonp = tokenData.data;
|
||||||
|
const plainData = decrypt(jsonp.ciphertext, jsonp.iv, t);
|
||||||
|
const tokenInfo = JSON.parse(plainData);
|
||||||
|
|
||||||
|
return Response.json({
|
||||||
|
token_type: 'Bearer',
|
||||||
|
access_token: tokenInfo.access_token,
|
||||||
|
refresh_token: tokenInfo.refresh_token,
|
||||||
|
expires_in: tokenInfo.expires_in
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return Response.json(
|
||||||
|
{ error: error.message },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -62,4 +62,4 @@ const generateKey = function (t) {
|
|||||||
console.log(md5(hashedKey));
|
console.log(md5(hashedKey));
|
||||||
return md5(hashedKey);
|
return md5(hashedKey);
|
||||||
};
|
};
|
||||||
module.exports = { decrypt,getParams };
|
export { decrypt, getParams };
|
127
src/html.js
127
src/html.js
@ -1,127 +0,0 @@
|
|||||||
export const html = `
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>二维码登录</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 100vh;
|
|
||||||
margin: 0;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
#qrCodeContainer {
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
#tokens {
|
|
||||||
display: none;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
.token-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.token-container label {
|
|
||||||
margin-right: 10px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.token-container input {
|
|
||||||
width: 300px;
|
|
||||||
padding: 5px;
|
|
||||||
margin-right: 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.token-container button {
|
|
||||||
padding: 5px 10px;
|
|
||||||
border: none;
|
|
||||||
background-color: #007bff;
|
|
||||||
color: white;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.token-container button:hover {
|
|
||||||
background-color: #0056b3;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script>
|
|
||||||
async function generateQR() {
|
|
||||||
const response = await fetch("/generate_qr", {
|
|
||||||
method: "POST",
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
const qrCode = document.getElementById("qrCode");
|
|
||||||
qrCode.src = data.qr_link;
|
|
||||||
qrCode.style.display = "block";
|
|
||||||
checkStatus(data.sid);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkStatus(sid) {
|
|
||||||
const interval = setInterval(async () => {
|
|
||||||
const response = await fetch("/check_status/" + sid);
|
|
||||||
const data = await response.json();
|
|
||||||
if (data.status === "LoginSuccess") {
|
|
||||||
clearInterval(interval);
|
|
||||||
document.querySelector("h1").innerText = "获取成功";
|
|
||||||
document.getElementById("accessToken").value = data.access_token;
|
|
||||||
document.getElementById("refreshToken").value = data.refresh_token;
|
|
||||||
document.getElementById("tokens").style.display = "block";
|
|
||||||
document.getElementById("copyAccessTokenButton").style.display =
|
|
||||||
"inline-block";
|
|
||||||
document.getElementById("copyRefreshTokenButton").style.display =
|
|
||||||
"inline-block";
|
|
||||||
document.getElementById("qrCodeContainer").style.display = "none";
|
|
||||||
} else {
|
|
||||||
if (data.status === "ScanSuccess") {
|
|
||||||
document.querySelector("h1").innerText =
|
|
||||||
"扫码成功,等待手机端授权";
|
|
||||||
}
|
|
||||||
if (data.status === "LoginFailed") {
|
|
||||||
document.querySelector("h1").innerText =
|
|
||||||
"登录失败,请刷新页面重试";
|
|
||||||
clearInterval(interval);
|
|
||||||
alert("登录失败,请刷新页面重试");
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyToClipboard(elementId) {
|
|
||||||
const copyText = document.getElementById(elementId);
|
|
||||||
copyText.select();
|
|
||||||
copyText.setSelectionRange(0, 99999);
|
|
||||||
document.execCommand("copy");
|
|
||||||
alert("已复制: " + copyText.value);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body onload="generateQR()">
|
|
||||||
<h1>扫描二维码登录</h1>
|
|
||||||
<div id="qrCodeContainer">
|
|
||||||
<img id="qrCode" alt="二维码加载中..." />
|
|
||||||
</div>
|
|
||||||
<div id="tokens">
|
|
||||||
<div class="token-container">
|
|
||||||
<label for="accessToken">访问令牌:</label>
|
|
||||||
<input type="text" id="accessToken" readonly />
|
|
||||||
<button id="copyAccessTokenButton" onclick="copyToClipboard('accessToken')">复制</button>
|
|
||||||
</div>
|
|
||||||
<div class="token-container">
|
|
||||||
<label for="refreshToken">刷新令牌:</label>
|
|
||||||
<input type="text" id="refreshToken" readonly />
|
|
||||||
<button id="copyRefreshTokenButton" onclick="copyToClipboard('refreshToken')">复制</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>`
|
|
164
src/index.js
164
src/index.js
@ -1,164 +0,0 @@
|
|||||||
/**
|
|
||||||
* Welcome to Cloudflare Workers! This is your first worker.
|
|
||||||
*
|
|
||||||
* - Run `npm run dev` in your terminal to start a development server
|
|
||||||
* - Open a browser tab at http://localhost:8787/ to see your worker in action
|
|
||||||
* - Run `npm run deploy` to publish your worker
|
|
||||||
*
|
|
||||||
* Learn more at https://developers.cloudflare.com/workers/
|
|
||||||
*/
|
|
||||||
import { decrypt, getParams } from './decode.js';
|
|
||||||
import {html} from './html.js';
|
|
||||||
async function handleRequest(request) {
|
|
||||||
const url = new URL(request.url);
|
|
||||||
if (request.method === 'POST' && url.pathname === '/generate_qr') {
|
|
||||||
return await generateQR(request);
|
|
||||||
} else if (request.method === 'GET' && url.pathname.startsWith('/check_status/')) {
|
|
||||||
const sid = url.pathname.split('/').pop();
|
|
||||||
return await checkStatus(sid);
|
|
||||||
} else if (request.method === 'POST' && url.pathname === '/refresh') {
|
|
||||||
return await refreshToken(request);
|
|
||||||
} else {
|
|
||||||
return new Response(html, {
|
|
||||||
headers: {
|
|
||||||
"content-type": "text/html;charset=UTF-8",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateQR(request) {
|
|
||||||
try {
|
|
||||||
const response = await fetch('http://api.extscreen.com/aliyundrive/qrcode', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
scopes: ["user:base", "file:all:read", "file:all:write"].join(','),
|
|
||||||
width: 500,
|
|
||||||
height: 500,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
const data = await gatherResponse(response);
|
|
||||||
return new Response(JSON.stringify({ qr_link: data.data.qrCodeUrl, sid: data.data.sid }), {
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
return new Response(JSON.stringify({ error: error.message }), {
|
|
||||||
status: 500,
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkStatus(sid) {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`https://openapi.alipan.com/oauth/qrcode/${sid}/status`);
|
|
||||||
const statusData = await response.json();
|
|
||||||
const status = statusData.status;
|
|
||||||
if (status === 'LoginSuccess') {
|
|
||||||
try {
|
|
||||||
console.log(statusData);
|
|
||||||
const authCode = statusData.authCode;
|
|
||||||
const t = Math.floor(Date.now() / 1000);
|
|
||||||
const sendData = { ...getParams(t), code: authCode, "Content-Type": "application/json"};
|
|
||||||
console.log(sendData);
|
|
||||||
const headers = Object.fromEntries(Object.entries(sendData).map(([k, v]) => [k, String(v)]));
|
|
||||||
const tokenResponse = await fetch('http://api.extscreen.com/aliyundrive/v3/token', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: headers,
|
|
||||||
body: JSON.stringify(sendData)
|
|
||||||
});
|
|
||||||
const tokenData = await gatherResponse(tokenResponse);
|
|
||||||
console.log(tokenData);
|
|
||||||
const jsonp = tokenData.data;
|
|
||||||
console.log(jsonp);
|
|
||||||
const plainData = decrypt(jsonp.ciphertext, jsonp.iv, t);
|
|
||||||
console.log(plainData);
|
|
||||||
const tokenInfo = JSON.parse(plainData);
|
|
||||||
const refreshToken = tokenInfo.refresh_token;
|
|
||||||
const accessToken = tokenInfo.access_token;
|
|
||||||
return new Response(JSON.stringify({ status: 'LoginSuccess', refresh_token: refreshToken, access_token: accessToken }), {
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
return new Response(JSON.stringify({ status: 'LoginFailed' }), {
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return new Response(JSON.stringify({ status: statusData.status }), {
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return new Response(JSON.stringify({ error: error.message }), {
|
|
||||||
status: 500,
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function readRequestBody(request) {
|
|
||||||
const contentType = request.headers.get("content-type");
|
|
||||||
if (contentType && contentType.includes("application/json")) {
|
|
||||||
return await request.json();
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* gatherResponse processes the response from the origin
|
|
||||||
* @param {Response} response the response from the origin
|
|
||||||
*/
|
|
||||||
async function gatherResponse(response) {
|
|
||||||
const { headers } = response;
|
|
||||||
const contentType = headers.get("content-type") || "";
|
|
||||||
if (contentType.includes("application/json")) {
|
|
||||||
return await response.json();
|
|
||||||
}
|
|
||||||
return await response.text();
|
|
||||||
}
|
|
||||||
async function refreshToken(request) {
|
|
||||||
try {
|
|
||||||
const { refresh_token } = await readRequestBody(request);
|
|
||||||
console.log(refresh_token);
|
|
||||||
const t = Math.floor(Date.now() / 1000);
|
|
||||||
const sendData = { ...getParams(t), refresh_token: refresh_token,"Content-Type": "application/json" };
|
|
||||||
const headers = Object.fromEntries(Object.entries(sendData).map(([k, v]) => [k, String(v)]));
|
|
||||||
const response = await fetch('http://api.extscreen.com/aliyundrive/v3/token', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: headers,
|
|
||||||
body: JSON.stringify(sendData)
|
|
||||||
});
|
|
||||||
const tokenData = await gatherResponse(response);
|
|
||||||
console.log(tokenData);
|
|
||||||
const jsonp = tokenData.data;
|
|
||||||
console.log(jsonp);
|
|
||||||
const plainData = decrypt(jsonp.ciphertext, jsonp.iv, t);
|
|
||||||
console.log(plainData);
|
|
||||||
const tokenInfo = JSON.parse(plainData);
|
|
||||||
console.log(tokenInfo);
|
|
||||||
|
|
||||||
return new Response(JSON.stringify({
|
|
||||||
token_type: 'Bearer',
|
|
||||||
access_token: tokenInfo.access_token,
|
|
||||||
refresh_token: tokenInfo.refresh_token,
|
|
||||||
expires_in: tokenInfo.expires_in
|
|
||||||
}), {
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
return new Response(JSON.stringify({ error: error.message }), {
|
|
||||||
status: 500,
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default {
|
|
||||||
async fetch(request, env, ctx) {
|
|
||||||
return handleRequest(request);
|
|
||||||
},
|
|
||||||
|
|
||||||
};
|
|
18
tailwind.config.js
Normal file
18
tailwind.config.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
backgroundImage: {
|
||||||
|
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
||||||
|
"gradient-conic":
|
||||||
|
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
@ -1,20 +0,0 @@
|
|||||||
import { env, createExecutionContext, waitOnExecutionContext, SELF } from 'cloudflare:test';
|
|
||||||
import { describe, it, expect } from 'vitest';
|
|
||||||
import worker from '../src';
|
|
||||||
|
|
||||||
describe('Hello World worker', () => {
|
|
||||||
it('responds with Hello World! (unit style)', async () => {
|
|
||||||
const request = new Request('http://example.com');
|
|
||||||
// Create an empty context to pass to `worker.fetch()`.
|
|
||||||
const ctx = createExecutionContext();
|
|
||||||
const response = await worker.fetch(request, env, ctx);
|
|
||||||
// Wait for all `Promise`s passed to `ctx.waitUntil()` to settle before running test assertions
|
|
||||||
await waitOnExecutionContext(ctx);
|
|
||||||
expect(await response.text()).toMatchInlineSnapshot(`"Hello World!"`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('responds with Hello World! (integration style)', async () => {
|
|
||||||
const response = await SELF.fetch(request, env, ctx);
|
|
||||||
expect(await response.text()).toMatchInlineSnapshot(`"Hello World!"`);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,11 +0,0 @@
|
|||||||
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';
|
|
||||||
|
|
||||||
export default defineWorkersConfig({
|
|
||||||
test: {
|
|
||||||
poolOptions: {
|
|
||||||
workers: {
|
|
||||||
wrangler: { configPath: './wrangler.toml' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
@ -1,108 +1,87 @@
|
|||||||
#:schema node_modules/wrangler/config-schema.json
|
#:schema node_modules/wrangler/config-schema.json
|
||||||
name = "alipan-tv-token"
|
name = "alipan-tv-token-next"
|
||||||
main = "src/index.js"
|
|
||||||
compatibility_date = "2023-05-18"
|
compatibility_date = "2023-05-18"
|
||||||
compatibility_flags = ["nodejs_compat"]
|
compatibility_flags = ["nodejs_compat"]
|
||||||
|
pages_build_output_dir = ".vercel/output/static"
|
||||||
|
|
||||||
# Automatically place your workloads in an optimal location to minimize latency.
|
# Automatically place your workloads in an optimal location to minimize latency.
|
||||||
# If you are running back-end logic in a Worker, running it closer to your back-end infrastructure
|
# If you are running back-end logic in a Pages Function, running it closer to your back-end infrastructure
|
||||||
# rather than the end user may result in better performance.
|
# rather than the end user may result in better performance.
|
||||||
# Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
|
# Docs: https://developers.cloudflare.com/pages/functions/smart-placement/#smart-placement
|
||||||
# [placement]
|
# [placement]
|
||||||
# mode = "smart"
|
# mode = "smart"
|
||||||
|
|
||||||
# Variable bindings. These are arbitrary, plaintext strings (similar to environment variables)
|
# Variable bindings. These are arbitrary, plaintext strings (similar to environment variables)
|
||||||
# Docs:
|
# Docs:
|
||||||
# - https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
|
# - https://developers.cloudflare.com/pages/functions/bindings/#environment-variables
|
||||||
# Note: Use secrets to store sensitive data.
|
# Note: Use secrets to store sensitive data.
|
||||||
# - https://developers.cloudflare.com/workers/configuration/secrets/
|
# - https://developers.cloudflare.com/pages/functions/bindings/#secrets
|
||||||
# [vars]
|
# [vars]
|
||||||
# MY_VARIABLE = "production_value"
|
# MY_VARIABLE = "production_value"
|
||||||
|
|
||||||
# Bind the Workers AI model catalog. Run machine learning models, powered by serverless GPUs, on Cloudflare’s global network
|
# Bind the Workers AI model catalog. Run machine learning models, powered by serverless GPUs, on Cloudflare’s global network
|
||||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#workers-ai
|
# Docs: https://developers.cloudflare.com/pages/functions/bindings/#workers-ai
|
||||||
# [ai]
|
# [ai]
|
||||||
# binding = "AI"
|
# binding = "AI"
|
||||||
|
|
||||||
# Bind an Analytics Engine dataset. Use Analytics Engine to write analytics within your Pages Function.
|
|
||||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#analytics-engine-datasets
|
|
||||||
# [[analytics_engine_datasets]]
|
|
||||||
# binding = "MY_DATASET"
|
|
||||||
|
|
||||||
# Bind a headless browser instance running on Cloudflare's global network.
|
|
||||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#browser-rendering
|
|
||||||
# [browser]
|
|
||||||
# binding = "MY_BROWSER"
|
|
||||||
|
|
||||||
# Bind a D1 database. D1 is Cloudflare’s native serverless SQL database.
|
# Bind a D1 database. D1 is Cloudflare’s native serverless SQL database.
|
||||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#d1-databases
|
# Docs: https://developers.cloudflare.com/pages/functions/bindings/#d1-databases
|
||||||
# [[d1_databases]]
|
# [[d1_databases]]
|
||||||
# binding = "MY_DB"
|
# binding = "MY_DB"
|
||||||
# database_name = "my-database"
|
# database_name = "my-database"
|
||||||
# database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
# database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||||
|
|
||||||
# Bind a dispatch namespace. Use Workers for Platforms to deploy serverless functions programmatically on behalf of your customers.
|
|
||||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#dispatch-namespace-bindings-workers-for-platforms
|
|
||||||
# [[dispatch_namespaces]]
|
|
||||||
# binding = "MY_DISPATCHER"
|
|
||||||
# namespace = "my-namespace"
|
|
||||||
|
|
||||||
# Bind a Durable Object. Durable objects are a scale-to-zero compute primitive based on the actor model.
|
# Bind a Durable Object. Durable objects are a scale-to-zero compute primitive based on the actor model.
|
||||||
# Durable Objects can live for as long as needed. Use these when you need a long-running "server", such as in realtime apps.
|
# Durable Objects can live for as long as needed. Use these when you need a long-running "server", such as in realtime apps.
|
||||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#durable-objects
|
# Docs: https://developers.cloudflare.com/workers/runtime-apis/durable-objects
|
||||||
# [[durable_objects.bindings]]
|
# [[durable_objects.bindings]]
|
||||||
# name = "MY_DURABLE_OBJECT"
|
# name = "MY_DURABLE_OBJECT"
|
||||||
# class_name = "MyDurableObject"
|
# class_name = "MyDurableObject"
|
||||||
|
# script_name = 'my-durable-object'
|
||||||
# Durable Object migrations.
|
|
||||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#migrations
|
|
||||||
# [[migrations]]
|
|
||||||
# tag = "v1"
|
|
||||||
# new_classes = ["MyDurableObject"]
|
|
||||||
|
|
||||||
# Bind a Hyperdrive configuration. Use to accelerate access to your existing databases from Cloudflare Workers.
|
|
||||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#hyperdrive
|
|
||||||
# [[hyperdrive]]
|
|
||||||
# binding = "MY_HYPERDRIVE"
|
|
||||||
# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
||||||
|
|
||||||
# Bind a KV Namespace. Use KV as persistent storage for small key-value pairs.
|
# Bind a KV Namespace. Use KV as persistent storage for small key-value pairs.
|
||||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#kv-namespaces
|
# Docs: https://developers.cloudflare.com/pages/functions/bindings/#kv-namespaces
|
||||||
|
# KV Example:
|
||||||
# [[kv_namespaces]]
|
# [[kv_namespaces]]
|
||||||
# binding = "MY_KV_NAMESPACE"
|
# binding = "MY_KV_NAMESPACE"
|
||||||
# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
|
||||||
# Bind an mTLS certificate. Use to present a client certificate when communicating with another service.
|
|
||||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#mtls-certificates
|
|
||||||
# [[mtls_certificates]]
|
|
||||||
# binding = "MY_CERTIFICATE"
|
|
||||||
# certificate_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
|
||||||
|
|
||||||
# Bind a Queue producer. Use this binding to schedule an arbitrary task that may be processed later by a Queue consumer.
|
# Bind a Queue producer. Use this binding to schedule an arbitrary task that may be processed later by a Queue consumer.
|
||||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#queues
|
# Docs: https://developers.cloudflare.com/pages/functions/bindings/#queue-producers
|
||||||
# [[queues.producers]]
|
# [[queues.producers]]
|
||||||
# binding = "MY_QUEUE"
|
# binding = "MY_QUEUE"
|
||||||
# queue = "my-queue"
|
# queue = "my-queue"
|
||||||
|
|
||||||
# Bind a Queue consumer. Queue Consumers can retrieve tasks scheduled by Producers to act on them.
|
|
||||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#queues
|
|
||||||
# [[queues.consumers]]
|
|
||||||
# queue = "my-queue"
|
|
||||||
|
|
||||||
# Bind an R2 Bucket. Use R2 to store arbitrarily large blobs of data, such as files.
|
# Bind an R2 Bucket. Use R2 to store arbitrarily large blobs of data, such as files.
|
||||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#r2-buckets
|
# Docs: https://developers.cloudflare.com/pages/functions/bindings/#r2-buckets
|
||||||
# [[r2_buckets]]
|
# [[r2_buckets]]
|
||||||
# binding = "MY_BUCKET"
|
# binding = "MY_BUCKET"
|
||||||
# bucket_name = "my-bucket"
|
# bucket_name = "my-bucket"
|
||||||
|
|
||||||
# Bind another Worker service. Use this binding to call another Worker without network overhead.
|
# Bind another Worker service. Use this binding to call another Worker without network overhead.
|
||||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
|
# Docs: https://developers.cloudflare.com/pages/functions/bindings/#service-bindings
|
||||||
# [[services]]
|
# [[services]]
|
||||||
# binding = "MY_SERVICE"
|
# binding = "MY_SERVICE"
|
||||||
# service = "my-service"
|
# service = "my-service"
|
||||||
|
|
||||||
# Bind a Vectorize index. Use to store and query vector embeddings for semantic search, classification and other vector search use-cases.
|
# To use different bindings for preview and production environments, follow the examples below.
|
||||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#vectorize-indexes
|
# When using environment-specific overrides for bindings, ALL bindings must be specified on a per-environment basis.
|
||||||
# [[vectorize]]
|
# Docs: https://developers.cloudflare.com/pages/functions/wrangler-configuration#environment-specific-overrides
|
||||||
# binding = "MY_INDEX"
|
|
||||||
# index_name = "my-index"
|
######## PREVIEW environment config ########
|
||||||
|
|
||||||
|
# [env.preview.vars]
|
||||||
|
# API_KEY = "xyz789"
|
||||||
|
|
||||||
|
# [[env.preview.kv_namespaces]]
|
||||||
|
# binding = "MY_KV_NAMESPACE"
|
||||||
|
# id = "<PREVIEW_NAMESPACE_ID>"
|
||||||
|
|
||||||
|
######## PRODUCTION environment config ########
|
||||||
|
|
||||||
|
# [env.production.vars]
|
||||||
|
# API_KEY = "abc123"
|
||||||
|
|
||||||
|
# [[env.production.kv_namespaces]]
|
||||||
|
# binding = "MY_KV_NAMESPACE"
|
||||||
|
# id = "<PRODUCTION_NAMESPACE_ID>"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user