gitalk-react 1.0.0 → 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.
Files changed (63) hide show
  1. package/README-zh-CN.md +152 -53
  2. package/README.md +132 -34
  3. package/dist/gitalk-dark.css +1 -1
  4. package/dist/gitalk-light.css +1 -1
  5. package/dist/gitalk.browser.full.js +102 -0
  6. package/dist/gitalk.browser.js +94 -0
  7. package/dist/gitalk.cjs +94 -0
  8. package/dist/gitalk.d.cts +14 -0
  9. package/dist/gitalk.d.mts +174 -0
  10. package/dist/gitalk.js +2737 -10382
  11. package/package.json +61 -41
  12. package/proxy-server/.env.example +6 -0
  13. package/proxy-server/README.md +38 -0
  14. package/proxy-server/server.mjs +110 -0
  15. package/dist/gitalk.d.ts +0 -623
  16. package/dist/gitalk.umd.cjs +0 -126
  17. package/lib/assets/arrow-down.svg +0 -1
  18. package/lib/assets/edit.svg +0 -3
  19. package/lib/assets/github.svg +0 -3
  20. package/lib/assets/heart-filled.svg +0 -3
  21. package/lib/assets/heart.svg +0 -3
  22. package/lib/assets/reply.svg +0 -3
  23. package/lib/assets/tip.svg +0 -8
  24. package/lib/components/action.tsx +0 -21
  25. package/lib/components/avatar.tsx +0 -42
  26. package/lib/components/button.tsx +0 -35
  27. package/lib/components/comment-textarea.tsx +0 -166
  28. package/lib/components/comment.tsx +0 -204
  29. package/lib/components/comments-list.tsx +0 -112
  30. package/lib/components/meta.tsx +0 -133
  31. package/lib/components/svg.tsx +0 -29
  32. package/lib/constants/index.ts +0 -71
  33. package/lib/contexts/I18nContext.ts +0 -20
  34. package/lib/gitalk.tsx +0 -891
  35. package/lib/i18n/ar.json +0 -20
  36. package/lib/i18n/bn.json +0 -20
  37. package/lib/i18n/de.json +0 -20
  38. package/lib/i18n/en.json +0 -20
  39. package/lib/i18n/es.json +0 -20
  40. package/lib/i18n/fa.json +0 -20
  41. package/lib/i18n/fr.json +0 -20
  42. package/lib/i18n/hi.json +0 -20
  43. package/lib/i18n/index.ts +0 -53
  44. package/lib/i18n/ja.json +0 -20
  45. package/lib/i18n/ko.json +0 -20
  46. package/lib/i18n/pl.json +0 -21
  47. package/lib/i18n/pt.json +0 -20
  48. package/lib/i18n/ru.json +0 -20
  49. package/lib/i18n/ur.json +0 -20
  50. package/lib/i18n/zh-CN.json +0 -20
  51. package/lib/i18n/zh-TW.json +0 -20
  52. package/lib/interfaces/index.ts +0 -198
  53. package/lib/services/graphql/comment.ts +0 -84
  54. package/lib/services/request.ts +0 -24
  55. package/lib/services/user.ts +0 -40
  56. package/lib/themes/base.scss +0 -630
  57. package/lib/themes/gitalk-dark.scss +0 -28
  58. package/lib/themes/gitalk-light.scss +0 -28
  59. package/lib/utils/compatibility.ts +0 -35
  60. package/lib/utils/dom.ts +0 -15
  61. package/lib/utils/logger.ts +0 -66
  62. package/lib/utils/query.ts +0 -19
  63. 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.0.0",
3
+ "version": "1.1.0",
4
4
  "private": false,
5
5
  "author": {
6
6
  "name": "LolipopJ",
7
- "email": "mail@towind.fun",
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
- "lib",
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
- "main": "./dist/gitalk.umd.cjs",
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
- "import": "./dist/gitalk.js",
30
- "require": "./dist/gitalk.umd.cjs"
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,60 +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.ts",
51
+ "types": "./dist/gitalk.d.mts",
39
52
  "scripts": {
40
53
  "dev": "vite --config vite.config.preview.ts",
41
- "build": "npm run build:lib && npm run build:preview",
42
- "build:lib": "npm run build:ts && npm run build:scss",
43
- "build:ts": "tsc -b && vite 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
- "lint": "npm run lint:ts && npm run lint:scss",
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 **/*.scss --fix"
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
- "ahooks": "^3.0.0",
52
- "classnames": "^2.0.0",
53
- "date-fns": "^4.0.0",
54
- "node-polyglot": "^2.0.0",
55
- "octokit": "^5.0.0",
56
- "react-flip-move": "^3.0.0"
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"
57
73
  },
58
74
  "peerDependencies": {
59
75
  "react": ">=16.8.0",
60
76
  "react-dom": ">=16.8.0"
61
77
  },
62
78
  "devDependencies": {
63
- "@eslint/js": "^9.39.4",
64
- "@octokit/types": "^16.0.0",
65
- "@types/node": "^24.0.0",
79
+ "@eslint/js": "^10.0.1",
80
+ "@microsoft/api-extractor": "^7.59.4",
81
+ "@types/node": "^24.19.2",
66
82
  "@types/node-polyglot": "^2.5.0",
67
- "@types/react": "^16.14.69",
83
+ "@types/react": "^16.14.70",
68
84
  "@types/react-dom": "^16.9.25",
69
- "@vitejs/plugin-react": "^6.0.1",
70
- "eslint": "^9.39.4",
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",
71
89
  "eslint-config-prettier": "^10.1.8",
72
- "eslint-plugin-prettier": "^5.5.5",
90
+ "eslint-plugin-prettier": "^5.5.7",
73
91
  "eslint-plugin-react": "^7.37.5",
74
- "eslint-plugin-react-hooks": "^7.0.1",
75
- "eslint-plugin-react-refresh": "^0.5.2",
76
- "eslint-plugin-simple-import-sort": "^12.1.1",
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",
77
95
  "github-markdown-css": "^5.9.0",
78
- "globals": "^17.4.0",
79
- "postcss": "^8.5.8",
80
- "prettier": "^3.8.1",
96
+ "globals": "^17.13.0",
97
+ "postcss": "^8.5.30",
98
+ "prettier": "^3.9.10",
81
99
  "react": "^16.14.0",
82
100
  "react-dom": "^16.14.0",
83
- "rollup-plugin-visualizer": "^7.0.1",
84
- "sass-embedded": "^1.98.0",
85
- "stylelint": "^17.5.0",
86
- "stylelint-config-recess-order": "^7.7.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",
87
107
  "stylelint-config-standard-scss": "^17.0.0",
88
- "stylelint-order": "^8.1.1",
89
- "typescript": "~5.9.3",
90
- "typescript-eslint": "^8.57.1",
91
- "vite": "^8.0.1",
92
- "vite-plugin-dts": "^4.5.4"
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"
93
113
  }
94
114
  }
@@ -0,0 +1,6 @@
1
+ # Server-only settings. Never use VITE_* for the client secret.
2
+ GITHUB_CLIENT_ID=YOUR_GITHUB_OAUTH_APP_CLIENT_ID
3
+ GITHUB_CLIENT_SECRET=YOUR_GITHUB_OAUTH_APP_CLIENT_SECRET
4
+ ALLOWED_ORIGIN=http://localhost:5173
5
+ HOST=127.0.0.1
6
+ PORT=3000
@@ -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
+ }