gitalk-react 1.0.0-beta.8 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-zh-CN.md +159 -57
- package/README.md +142 -40
- package/dist/gitalk-dark.css +1 -1
- package/dist/gitalk-light.css +1 -1
- package/dist/gitalk.browser.full.js +102 -0
- package/dist/gitalk.browser.js +94 -0
- package/dist/gitalk.cjs +94 -0
- package/dist/gitalk.d.cts +14 -0
- package/dist/gitalk.d.mts +174 -0
- package/dist/gitalk.js +7427 -12628
- package/package.json +67 -44
- package/proxy-server/.env.example +6 -0
- package/proxy-server/README.md +38 -0
- package/proxy-server/server.mjs +110 -0
- package/dist/gitalk.d.ts +0 -455
- package/dist/gitalk.umd.cjs +0 -123
- package/lib/assets/arrow-down.svg +0 -1
- package/lib/assets/edit.svg +0 -3
- package/lib/assets/github.svg +0 -3
- package/lib/assets/heart-filled.svg +0 -3
- package/lib/assets/heart.svg +0 -3
- package/lib/assets/reply.svg +0 -3
- package/lib/assets/tip.svg +0 -8
- package/lib/components/action.tsx +0 -21
- package/lib/components/avatar.tsx +0 -42
- package/lib/components/button.tsx +0 -35
- package/lib/components/comment-textarea.tsx +0 -164
- package/lib/components/comment.tsx +0 -185
- package/lib/components/comments-list.tsx +0 -106
- package/lib/components/meta.tsx +0 -129
- package/lib/components/svg.tsx +0 -29
- package/lib/constants/index.ts +0 -57
- package/lib/contexts/I18nContext.ts +0 -20
- package/lib/gitalk.tsx +0 -882
- package/lib/i18n/de.json +0 -20
- package/lib/i18n/en.json +0 -20
- package/lib/i18n/es-ES.json +0 -20
- package/lib/i18n/fa.json +0 -20
- package/lib/i18n/fr.json +0 -20
- package/lib/i18n/index.ts +0 -40
- package/lib/i18n/ja.json +0 -20
- package/lib/i18n/ko.json +0 -20
- package/lib/i18n/pl.json +0 -21
- package/lib/i18n/ru.json +0 -20
- package/lib/i18n/zh-CN.json +0 -20
- package/lib/i18n/zh-TW.json +0 -20
- package/lib/interfaces/index.ts +0 -196
- package/lib/services/graphql/comment.ts +0 -84
- package/lib/services/request.ts +0 -24
- package/lib/services/user.ts +0 -40
- package/lib/themes/base.scss +0 -628
- package/lib/themes/gitalk-dark.scss +0 -28
- package/lib/themes/gitalk-light.scss +0 -28
- package/lib/utils/compatibility.ts +0 -35
- package/lib/utils/dom.ts +0 -15
- package/lib/utils/logger.ts +0 -66
- package/lib/utils/query.ts +0 -19
- package/lib/vite-env.d.ts +0 -1
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitalk-react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "LolipopJ",
|
|
7
|
-
"email": "mail@towind.
|
|
7
|
+
"email": "mail@towind.cn",
|
|
8
8
|
"url": "https://github.com/LolipopJ"
|
|
9
9
|
},
|
|
10
10
|
"repository": {
|
|
@@ -13,21 +13,34 @@
|
|
|
13
13
|
},
|
|
14
14
|
"homepage": "https://lolipopj.github.io/gitalk-react",
|
|
15
15
|
"license": "MIT",
|
|
16
|
+
"packageManager": "pnpm@12.11.2",
|
|
16
17
|
"type": "module",
|
|
17
18
|
"files": [
|
|
18
|
-
"dist",
|
|
19
|
-
"
|
|
19
|
+
"dist/",
|
|
20
|
+
"proxy-server/",
|
|
20
21
|
"LICENSE",
|
|
21
22
|
"package.json",
|
|
22
23
|
"README-zh-CN.md",
|
|
23
24
|
"README.md"
|
|
24
25
|
],
|
|
25
|
-
"
|
|
26
|
+
"sideEffects": [
|
|
27
|
+
"**/*.css"
|
|
28
|
+
],
|
|
29
|
+
"main": "./dist/gitalk.cjs",
|
|
26
30
|
"module": "./dist/gitalk.js",
|
|
31
|
+
"unpkg": "./dist/gitalk.browser.full.js",
|
|
32
|
+
"jsdelivr": "./dist/gitalk.browser.full.js",
|
|
27
33
|
"exports": {
|
|
28
34
|
".": {
|
|
29
|
-
"
|
|
30
|
-
"
|
|
35
|
+
"unpkg": "./dist/gitalk.browser.full.js",
|
|
36
|
+
"import": {
|
|
37
|
+
"types": "./dist/gitalk.d.mts",
|
|
38
|
+
"default": "./dist/gitalk.js"
|
|
39
|
+
},
|
|
40
|
+
"require": {
|
|
41
|
+
"types": "./dist/gitalk.d.cts",
|
|
42
|
+
"default": "./dist/gitalk.cjs"
|
|
43
|
+
}
|
|
31
44
|
},
|
|
32
45
|
"./gitalk.css": "./dist/gitalk-light.css",
|
|
33
46
|
"./light.css": "./dist/gitalk-light.css",
|
|
@@ -35,57 +48,67 @@
|
|
|
35
48
|
"./dark.css": "./dist/gitalk-dark.css",
|
|
36
49
|
"./gitalk-dark.css": "./dist/gitalk-dark.css"
|
|
37
50
|
},
|
|
38
|
-
"types": "./dist/gitalk.d.
|
|
51
|
+
"types": "./dist/gitalk.d.mts",
|
|
39
52
|
"scripts": {
|
|
40
53
|
"dev": "vite --config vite.config.preview.ts",
|
|
41
|
-
"build": "
|
|
42
|
-
"build:lib": "
|
|
43
|
-
"build:
|
|
54
|
+
"build": "tsc -b && pnpm run build:lib && pnpm run build:browser && pnpm run build:scss && pnpm run build:preview",
|
|
55
|
+
"build:lib": "vite build",
|
|
56
|
+
"build:browser": "vite build --config vite.config.browser.ts && vite build --config vite.config.browser.ts --mode full",
|
|
44
57
|
"build:scss": "vite build --config vite.config.scss.ts",
|
|
45
58
|
"build:preview": "vite build --config vite.config.preview.ts",
|
|
46
|
-
"
|
|
59
|
+
"analyze": "vite build --mode analyze",
|
|
60
|
+
"prepack": "pnpm run test",
|
|
61
|
+
"lint": "pnpm run lint:ts && pnpm run lint:scss",
|
|
47
62
|
"lint:ts": "eslint . --fix",
|
|
48
|
-
"lint:scss": "stylelint
|
|
63
|
+
"lint:scss": "stylelint {dev,lib}/**/*.scss --fix",
|
|
64
|
+
"start:proxy": "node --env-file=proxy-server/.env.local proxy-server/server.mjs",
|
|
65
|
+
"test": "pnpm run build && node --test test/*.test.mjs"
|
|
49
66
|
},
|
|
50
67
|
"dependencies": {
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
68
|
+
"@octokit/types": "^18.0.0",
|
|
69
|
+
"ahooks": "^3.10.0",
|
|
70
|
+
"classnames": "^2.5.1",
|
|
71
|
+
"node-polyglot": "^2.6.0",
|
|
72
|
+
"octokit": "^5.0.5"
|
|
56
73
|
},
|
|
57
74
|
"peerDependencies": {
|
|
58
75
|
"react": ">=16.8.0",
|
|
59
76
|
"react-dom": ">=16.8.0"
|
|
60
77
|
},
|
|
61
78
|
"devDependencies": {
|
|
62
|
-
"@eslint/js": "^
|
|
63
|
-
"@
|
|
64
|
-
"@types/node": "^
|
|
79
|
+
"@eslint/js": "^10.0.1",
|
|
80
|
+
"@microsoft/api-extractor": "^7.59.4",
|
|
81
|
+
"@types/node": "^24.19.2",
|
|
65
82
|
"@types/node-polyglot": "^2.5.0",
|
|
66
|
-
"@types/react": "^16.
|
|
67
|
-
"@types/react-dom": "^16.
|
|
68
|
-
"@
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"eslint
|
|
83
|
+
"@types/react": "^16.14.70",
|
|
84
|
+
"@types/react-dom": "^16.9.25",
|
|
85
|
+
"@types/react-latest": "npm:@types/react@latest",
|
|
86
|
+
"@types/react-dom-latest": "npm:@types/react-dom@latest",
|
|
87
|
+
"@vitejs/plugin-react": "^6.1.2",
|
|
88
|
+
"eslint": "^10.12.0",
|
|
89
|
+
"eslint-config-prettier": "^10.1.8",
|
|
90
|
+
"eslint-plugin-prettier": "^5.5.7",
|
|
72
91
|
"eslint-plugin-react": "^7.37.5",
|
|
73
|
-
"eslint-plugin-react-hooks": "^
|
|
74
|
-
"eslint-plugin-react-refresh": "^0.
|
|
75
|
-
"eslint-plugin-simple-import-sort": "^
|
|
76
|
-
"github-markdown-css": "^5.
|
|
77
|
-
"globals": "^
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"react
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
92
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
93
|
+
"eslint-plugin-react-refresh": "^0.5.7",
|
|
94
|
+
"eslint-plugin-simple-import-sort": "^14.0.0",
|
|
95
|
+
"github-markdown-css": "^5.9.0",
|
|
96
|
+
"globals": "^17.13.0",
|
|
97
|
+
"postcss": "^8.5.30",
|
|
98
|
+
"prettier": "^3.9.10",
|
|
99
|
+
"react": "^16.14.0",
|
|
100
|
+
"react-dom": "^16.14.0",
|
|
101
|
+
"react-latest": "npm:react@latest",
|
|
102
|
+
"react-dom-latest": "npm:react-dom@latest",
|
|
103
|
+
"rollup-plugin-visualizer": "^7.1.1",
|
|
104
|
+
"sass-embedded": "^1.105.1",
|
|
105
|
+
"stylelint": "^17.16.0",
|
|
106
|
+
"stylelint-config-recess-order": "^7.8.0",
|
|
107
|
+
"stylelint-config-standard-scss": "^17.0.0",
|
|
108
|
+
"stylelint-order": "^8.1.2",
|
|
109
|
+
"typescript": "~6.0.3",
|
|
110
|
+
"typescript-eslint": "^8.71.2",
|
|
111
|
+
"vite": "^8.3.4",
|
|
112
|
+
"vite-plugin-dts": "^5.1.2"
|
|
90
113
|
}
|
|
91
114
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# OAuth proxy
|
|
2
|
+
|
|
3
|
+
This Node.js 22+ server exchanges GitHub OAuth authorization codes using credentials kept on the server. It has no additional dependencies.
|
|
4
|
+
|
|
5
|
+
From the repository root, copy `proxy-server/.env.example` to `proxy-server/.env.local`. Set:
|
|
6
|
+
|
|
7
|
+
- `GITHUB_CLIENT_ID`: the same OAuth App ID as the browser's `clientID`.
|
|
8
|
+
- `GITHUB_CLIENT_SECRET`: the OAuth App secret, stored only on the server.
|
|
9
|
+
- `ALLOWED_ORIGIN`: your exact site origin, such as `http://localhost:5173` or `https://example.com`, without a path or trailing slash. For the deployed preview, use `https://lolipopj.github.io`.
|
|
10
|
+
- `HOST` and `PORT`: default to `127.0.0.1` and `3000`. Set `HOST=0.0.0.0` when a container needs to accept connections from outside it.
|
|
11
|
+
|
|
12
|
+
The `.env.local` file is ignored by Git. Never copy its secret into `VITE_*`, component props, or the static preview build.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm run start:proxy
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
In another terminal, run `npm run dev`. Vite forwards `/oauth/access_token` to the local proxy. If you override `VITE_PROXY`, point it to `http://localhost:3000/oauth/access_token` locally and use the exact Vite origin for `ALLOWED_ORIGIN`.
|
|
19
|
+
|
|
20
|
+
For deployment, copy `server.mjs` to a Node host, inject the environment variables through the host's secret settings, and run:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
node server.mjs
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Use a process manager and an HTTPS reverse proxy. Disable caching and query-string access logs for `/oauth/access_token`; URLs contain one-time authorization codes. For the GitHub Pages preview, configure the repository Actions variable `OAUTH_PROXY_URL` with this HTTPS endpoint. Set `VITE_CLIENT_ID` too if you use a different OAuth App. Register the frontend callback URL with that App.
|
|
27
|
+
|
|
28
|
+
Requests with an `Origin` must match `ALLOWED_ORIGIN`. Requests without `Origin` are permitted for same-origin browsers and command-line clients; CORS is a browser restriction, not authentication. Only the server-configured App credentials are used, regardless of query parameters supplied by callers.
|
|
29
|
+
|
|
30
|
+
The endpoint accepts `GET /oauth/access_token?code=...` and preserves the status and JSON body returned by `https://github.com/login/oauth/access_token`, including `scope`, `token_type`, and OAuth error fields. The browser validates this response using the same code as client-side exchange. Missing or duplicate codes return 400; disallowed origins return 403; unsupported paths and methods return 404/405. Invalid upstream JSON or network failures return 502 with a generic error. All responses use `Cache-Control: no-store`. Upstream requests time out after 10 seconds.
|
|
31
|
+
|
|
32
|
+
For both client-side and server-side exchange, Gitalk generates a cryptographically random, one-time `state` stored in the initiating tab's `sessionStorage`, scoped to the OAuth App and valid for 10 minutes. The frontend validates and consumes it before contacting this endpoint, handles denied authorization, and removes OAuth callback parameters while preserving the page's other query parameters and hash. The proxy only exchanges codes; it does not establish a separate login session. A 401 or logout clears the browser's token and user state and cancels pending requests. Rotate secrets previously published in preview source before deploying.
|
|
33
|
+
|
|
34
|
+
Run the focused tests from the repository root:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm run test:oauth
|
|
38
|
+
```
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
|
|
4
|
+
export const createOAuthServer = ({
|
|
5
|
+
clientID,
|
|
6
|
+
clientSecret,
|
|
7
|
+
allowedOrigin,
|
|
8
|
+
}) => {
|
|
9
|
+
if (!clientID || !clientSecret || !allowedOrigin) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
"GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET and ALLOWED_ORIGIN are required",
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
const origin = new URL(allowedOrigin);
|
|
15
|
+
if (
|
|
16
|
+
!["http:", "https:"].includes(origin.protocol) ||
|
|
17
|
+
origin.origin !== allowedOrigin
|
|
18
|
+
) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
"ALLOWED_ORIGIN must be an exact HTTP(S) origin without a path",
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return createServer(async (req, res) => {
|
|
25
|
+
res.setHeader("Cache-Control", "no-store");
|
|
26
|
+
res.setHeader("Vary", "Origin");
|
|
27
|
+
const send = (status, body) => {
|
|
28
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
29
|
+
res.end(JSON.stringify(body));
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
if (req.headers.origin && req.headers.origin !== allowedOrigin) {
|
|
33
|
+
send(403, { error: "origin_not_allowed" });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (req.headers.origin) {
|
|
37
|
+
res.setHeader("Access-Control-Allow-Origin", allowedOrigin);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let url;
|
|
41
|
+
try {
|
|
42
|
+
url = new URL(req.url, "http://localhost");
|
|
43
|
+
} catch {
|
|
44
|
+
send(400, { error: "invalid_url" });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (url.pathname !== "/oauth/access_token") {
|
|
48
|
+
send(404, { error: "not_found" });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (req.method === "OPTIONS") {
|
|
52
|
+
res.writeHead(204, { "Access-Control-Allow-Methods": "GET" });
|
|
53
|
+
res.end();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (req.method !== "GET") {
|
|
57
|
+
res.setHeader("Allow", "GET, OPTIONS");
|
|
58
|
+
send(405, { error: "method_not_allowed" });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const codes = url.searchParams.getAll("code");
|
|
63
|
+
const code = codes[0];
|
|
64
|
+
if (codes.length !== 1 || !code || code.length > 2048) {
|
|
65
|
+
send(400, { error: "invalid_code" });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const upstream = await fetch(
|
|
71
|
+
"https://github.com/login/oauth/access_token",
|
|
72
|
+
{
|
|
73
|
+
method: "POST",
|
|
74
|
+
headers: {
|
|
75
|
+
Accept: "application/json",
|
|
76
|
+
"Content-Type": "application/json",
|
|
77
|
+
},
|
|
78
|
+
body: JSON.stringify({
|
|
79
|
+
code,
|
|
80
|
+
client_id: clientID,
|
|
81
|
+
client_secret: clientSecret,
|
|
82
|
+
}),
|
|
83
|
+
signal: AbortSignal.timeout(10000),
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
const data = await upstream.json();
|
|
87
|
+
send(upstream.status, data);
|
|
88
|
+
} catch {
|
|
89
|
+
send(502, { error: "oauth_exchange_failed" });
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
if (
|
|
95
|
+
process.argv[1] &&
|
|
96
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
97
|
+
) {
|
|
98
|
+
const port = Number(process.env["PORT"] ?? 3000);
|
|
99
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
100
|
+
throw new Error("PORT must be an integer from 1 to 65535");
|
|
101
|
+
}
|
|
102
|
+
const server = createOAuthServer({
|
|
103
|
+
clientID: process.env["GITHUB_CLIENT_ID"],
|
|
104
|
+
clientSecret: process.env["GITHUB_CLIENT_SECRET"],
|
|
105
|
+
allowedOrigin: process.env["ALLOWED_ORIGIN"],
|
|
106
|
+
});
|
|
107
|
+
server.listen(port, process.env["HOST"] ?? "127.0.0.1", () => {
|
|
108
|
+
console.log(`OAuth proxy listening on port ${port}`);
|
|
109
|
+
});
|
|
110
|
+
}
|