@0xchain/token 1.1.0-beta.62 → 1.1.0-beta.64

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.
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Cookie 唯一真相源。
3
+ *
4
+ * 设计原则:domain / path / sameSite / secure 这套属性只在这里定义一次,
5
+ * set / get / remove 全部基于同一套属性。这样可以从机制上杜绝
6
+ * “写入带 secure、删除不带 secure 导致删不掉”这类属性漂移引发的
7
+ * cookie 时有时无问题。
8
+ */
9
+ export declare const COOKIE_DOMAIN = ".ichatgo.ai";
10
+ export declare const COOKIE_PATH = "/";
11
+ export declare const COOKIE_SAME_SITE: "lax";
12
+ export declare const REFRESH_TOKEN_COOKIE_DAYS = 7;
13
+ export declare const DEFAULT_ACCESS_TOKEN_TTL_SECONDS = 3600;
14
+ export declare const RUNTIME_ENV: string;
15
+ /** 生产环境保持无后缀(兼容存量),其余环境按环境隔离 */
16
+ export declare const COOKIE_ENV_SUFFIX: string;
17
+ /**
18
+ * 生成环境作用域内的 cookie 名。
19
+ * 例:test 环境 `access_token` → `access_token__test`;生产环境保持 `access_token`。
20
+ * 幂等:已带后缀的名字不会重复追加。
21
+ */
22
+ export declare function envCookieName(base: string): string;
23
+ /** js-cookie:number 表示「天数」,须用 Date 表示绝对过期时间 */
24
+ export declare function expiresFromSeconds(seconds: number): Date;
25
+ export declare const cookieStore: {
26
+ get(key: string): string;
27
+ set(key: string, value: string, expires: Date | number): void;
28
+ /**
29
+ * 删除必须与写入属性完全一致(domain/path/sameSite/secure),
30
+ * 否则浏览器无法命中目标 cookie,删除会“静默失败”。
31
+ */
32
+ remove(key: string): void;
33
+ };
34
+ //# sourceMappingURL=cookieStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookieStore.d.ts","sourceRoot":"","sources":["../src/cookieStore.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,gBAAgB,CAAA;AAC1C,eAAO,MAAM,WAAW,MAAM,CAAA;AAC9B,eAAO,MAAM,gBAAgB,EAAG,KAAc,CAAA;AAE9C,eAAO,MAAM,yBAAyB,IAAI,CAAA;AAC1C,eAAO,MAAM,gCAAgC,OAAO,CAAA;AAqBpD,eAAO,MAAM,WAAW,QAA2D,CAAA;AAEnF,gCAAgC;AAChC,eAAO,MAAM,iBAAiB,QAAyD,CAAA;AAEvF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIlD;AAYD,+CAA+C;AAC/C,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAExD;AAED,eAAO,MAAM,WAAW;aACb,MAAM,GAAG,MAAM;aAGf,MAAM,SAAS,MAAM,WAAW,IAAI,GAAG,MAAM,GAAG,IAAI;IAG7D;;;OAGG;gBACS,MAAM,GAAG,IAAI;CAG1B,CAAA"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { NextResponse } from 'next/server';
2
2
  import { ITokenManager, ITokenManagerResponse } from './type';
3
+ export { decodeJwtPayload, isJwtExpired } from './jwt';
4
+ export { envCookieName, COOKIE_ENV_SUFFIX, RUNTIME_ENV, COOKIE_DOMAIN, } from './cookieStore';
3
5
  /**
4
6
  * 设置|更新token到cookie
5
7
  * @param response NextResponse
@@ -11,6 +13,7 @@ export declare const getToken: (key?: string) => string;
11
13
  /** 兼容 OAuth 直出与 { data: token } 包装 */
12
14
  export declare function normalizeTokenPayload(res: unknown): ITokenManagerResponse | null;
13
15
  export declare const removeAllToken: () => void;
16
+ export declare const removeTokenByKey: (key?: string) => void;
14
17
  export declare const isTokenExpired: () => Promise<boolean>;
15
18
  declare const TokenManager: ITokenManager;
16
19
  export default TokenManager;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AA2E9D;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,qBAAqB,EAAE,WAAW,YAAY,GAAG,IAAI,KAAG,YAAY,GAAG,IA0CpG,CAAA;AAED,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,WAEpC,CAAA;AAED,sCAAsC;AACtC,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,GACX,qBAAqB,GAAG,IAAI,CAc9B;AAED,eAAO,MAAM,cAAc,YAM1B,CAAA;AAID,eAAO,MAAM,cAAc,wBAgD1B,CAAA;AACD,QAAA,MAAM,YAAY,EAAE,aAMnB,CAAA;AAED,eAAe,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AAc9D,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AACvD,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,WAAW,EACX,aAAa,GACd,MAAM,eAAe,CAAC;AA+BvB;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GACnB,KAAK,qBAAqB,EAC1B,WAAW,YAAY,GAAG,IAAI,KAC7B,YAAY,GAAG,IA0DjB,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,WAEpC,CAAC;AAEF,sCAAsC;AACtC,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,GACX,qBAAqB,GAAG,IAAI,CAc9B;AAED,eAAO,MAAM,cAAc,YAM1B,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,SAE5C,CAAC;AAEF,eAAO,MAAM,cAAc,wBA6C1B,CAAC;AAEF,QAAA,MAAM,YAAY,EAAE,aAMnB,CAAC;AAEF,eAAe,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -14,104 +14,163 @@ const COOKIE_DOMAIN = ".ichatgo.ai";
14
14
  const COOKIE_PATH = "/";
15
15
  const COOKIE_SAME_SITE = "lax";
16
16
  const REFRESH_TOKEN_COOKIE_DAYS = 7;
17
+ const DEFAULT_ACCESS_TOKEN_TTL_SECONDS = 3600;
18
+ function normalizeRuntimeEnv(raw) {
19
+ const env = (raw || "").trim().toLowerCase();
20
+ if (!env || env === "prod" || env === "production") return "production";
21
+ return env;
22
+ }
23
+ const RUNTIME_ENV = normalizeRuntimeEnv(process.env.NEXT_PUBLIC_RUNTIME_ENV);
24
+ const COOKIE_ENV_SUFFIX = RUNTIME_ENV === "production" ? "" : `__${RUNTIME_ENV}`;
25
+ function envCookieName(base) {
26
+ if (!COOKIE_ENV_SUFFIX) return base;
27
+ if (base.endsWith(COOKIE_ENV_SUFFIX)) return base;
28
+ return `${base}${COOKIE_ENV_SUFFIX}`;
29
+ }
30
+ function baseAttributes() {
31
+ return {
32
+ domain: COOKIE_DOMAIN,
33
+ path: COOKIE_PATH,
34
+ sameSite: COOKIE_SAME_SITE,
35
+ secure: resolveCookieSecure()
36
+ };
37
+ }
17
38
  function expiresFromSeconds(seconds) {
18
39
  return new Date(Date.now() + Math.max(seconds, 0) * 1e3);
19
40
  }
20
- function decodeJwtExpiryMs(token) {
21
- if (!token) return void 0;
41
+ const cookieStore = {
42
+ get(key) {
43
+ return Cookies.get(key) || "";
44
+ },
45
+ set(key, value, expires) {
46
+ Cookies.set(key, value, { ...baseAttributes(), expires });
47
+ },
48
+ /**
49
+ * 删除必须与写入属性完全一致(domain/path/sameSite/secure),
50
+ * 否则浏览器无法命中目标 cookie,删除会“静默失败”。
51
+ */
52
+ remove(key) {
53
+ Cookies.remove(key, baseAttributes());
54
+ }
55
+ };
56
+ function base64UrlDecode(input) {
57
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
58
+ const padLength = (4 - normalized.length % 4) % 4;
59
+ const padded = normalized + "=".repeat(padLength);
60
+ if (typeof atob === "function") {
61
+ const binary = atob(padded);
62
+ const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
63
+ return new TextDecoder().decode(bytes);
64
+ }
65
+ return Buffer.from(padded, "base64").toString("utf8");
66
+ }
67
+ function decodeJwtPayload(token) {
68
+ if (!token) return null;
22
69
  const parts = token.split(".");
23
- if (parts.length < 2) return void 0;
24
- const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
70
+ if (parts.length < 2) return null;
25
71
  try {
26
- const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
27
- const raw = typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("binary");
28
- const utf8 = decodeURIComponent(
29
- raw.split("").map((c) => `%${c.charCodeAt(0).toString(16).padStart(2, "0")}`).join("")
30
- );
31
- const payload = JSON.parse(utf8);
32
- if (typeof payload.exp === "number") {
33
- return payload.exp * 1e3;
34
- }
72
+ const payloadJson = base64UrlDecode(parts[1]);
73
+ return JSON.parse(payloadJson);
35
74
  } catch {
75
+ return null;
76
+ }
77
+ }
78
+ function getJwtExpiryMs(token) {
79
+ const payload = decodeJwtPayload(token);
80
+ if (typeof payload?.exp === "number") {
81
+ return payload.exp * 1e3;
36
82
  }
37
83
  return void 0;
38
84
  }
85
+ function isJwtExpired(payload, options) {
86
+ if (!payload?.exp) return false;
87
+ const skew = options?.skewSeconds ?? 15;
88
+ const now = Math.floor(Date.now() / 1e3);
89
+ return payload.exp <= now + skew;
90
+ }
39
91
  const cookieSetToken = (data) => {
40
- const expiresIn = data?.expires_in ?? 3600;
92
+ const nextAccessToken = data?.access_token || getToken("access_token");
93
+ const nextRefreshToken = data?.refresh_token || getToken("refresh_token");
94
+ const nextIdToken = data?.id_token || getToken("id_token");
95
+ if (!nextAccessToken) {
96
+ return;
97
+ }
98
+ const expiresIn = data?.expires_in ?? DEFAULT_ACCESS_TOKEN_TTL_SECONDS;
41
99
  const accessExpires = expiresFromSeconds(expiresIn);
100
+ cookieStore.set(envCookieName("access_token"), nextAccessToken, accessExpires);
101
+ if (nextRefreshToken) {
102
+ cookieStore.set(
103
+ envCookieName("refresh_token"),
104
+ nextRefreshToken,
105
+ REFRESH_TOKEN_COOKIE_DAYS
106
+ );
107
+ }
108
+ if (nextIdToken) {
109
+ cookieStore.set(envCookieName("id_token"), nextIdToken, accessExpires);
110
+ }
111
+ cookieStore.set(
112
+ envCookieName("token_expire"),
113
+ (Date.now() + expiresIn * 1e3).toString(),
114
+ accessExpires
115
+ );
116
+ };
117
+ const setToken = (res, response) => {
118
+ if (!response) {
119
+ cookieSetToken(res);
120
+ return null;
121
+ }
122
+ const nextAccessToken = res?.access_token;
123
+ const nextRefreshToken = res?.refresh_token;
124
+ const nextIdToken = res?.id_token;
125
+ const expiresIn = typeof res?.expires_in === "number" && res.expires_in > 0 ? res.expires_in : DEFAULT_ACCESS_TOKEN_TTL_SECONDS;
126
+ if (!nextAccessToken) {
127
+ return response;
128
+ }
42
129
  const secure = resolveCookieSecure();
43
- Cookies.set("access_token", data?.access_token, {
44
- expires: accessExpires,
130
+ response.cookies.set(envCookieName("access_token"), nextAccessToken, {
131
+ httpOnly: false,
132
+ maxAge: expiresIn,
45
133
  domain: COOKIE_DOMAIN,
46
134
  sameSite: COOKIE_SAME_SITE,
47
135
  secure,
48
136
  path: COOKIE_PATH
49
137
  });
50
- Cookies.set("refresh_token", data?.refresh_token, {
51
- expires: REFRESH_TOKEN_COOKIE_DAYS,
52
- domain: COOKIE_DOMAIN,
53
- sameSite: COOKIE_SAME_SITE,
54
- secure,
55
- path: COOKIE_PATH
56
- });
57
- Cookies.set("id_token", data?.id_token, {
58
- expires: accessExpires,
59
- domain: COOKIE_DOMAIN,
60
- sameSite: COOKIE_SAME_SITE,
61
- secure,
62
- path: COOKIE_PATH
63
- });
64
- Cookies.set("token_expire", (Date.now() + expiresIn * 1e3).toString(), {
65
- expires: accessExpires,
66
- domain: COOKIE_DOMAIN,
67
- sameSite: COOKIE_SAME_SITE,
68
- secure,
69
- path: COOKIE_PATH
70
- });
71
- };
72
- const setToken = (res, response) => {
73
- if (!response) {
74
- cookieSetToken(res);
75
- return null;
76
- } else {
77
- const secure = resolveCookieSecure();
78
- response.cookies.set("access_token", res?.access_token, {
79
- httpOnly: false,
80
- maxAge: res?.expires_in,
81
- domain: COOKIE_DOMAIN,
82
- sameSite: COOKIE_SAME_SITE,
83
- secure,
84
- path: COOKIE_PATH
85
- });
86
- response.cookies.set("refresh_token", res?.refresh_token, {
138
+ if (nextRefreshToken) {
139
+ response.cookies.set(envCookieName("refresh_token"), nextRefreshToken, {
87
140
  httpOnly: false,
88
- maxAge: 60 * 60 * 24 * 7,
141
+ maxAge: 60 * 60 * 24 * REFRESH_TOKEN_COOKIE_DAYS,
89
142
  domain: COOKIE_DOMAIN,
90
143
  sameSite: COOKIE_SAME_SITE,
91
144
  secure,
92
145
  path: COOKIE_PATH
93
146
  });
94
- response.cookies.set("id_token", res?.id_token, {
147
+ }
148
+ if (nextIdToken) {
149
+ response.cookies.set(envCookieName("id_token"), nextIdToken, {
95
150
  httpOnly: false,
96
- maxAge: res?.expires_in,
151
+ maxAge: expiresIn,
97
152
  domain: COOKIE_DOMAIN,
98
153
  sameSite: COOKIE_SAME_SITE,
99
154
  secure,
100
155
  path: COOKIE_PATH
101
156
  });
102
- response.cookies.set("token_expire", (Date.now() + res?.expires_in * 1e3).toString(), {
157
+ }
158
+ response.cookies.set(
159
+ envCookieName("token_expire"),
160
+ (Date.now() + expiresIn * 1e3).toString(),
161
+ {
103
162
  httpOnly: false,
104
- maxAge: res?.expires_in,
163
+ maxAge: expiresIn,
105
164
  domain: COOKIE_DOMAIN,
106
165
  sameSite: COOKIE_SAME_SITE,
107
166
  secure,
108
167
  path: COOKIE_PATH
109
- });
110
- }
168
+ }
169
+ );
111
170
  return response;
112
171
  };
113
172
  const getToken = (key) => {
114
- return Cookies.get(key || "id_token") || "";
173
+ return cookieStore.get(envCookieName(key || "id_token"));
115
174
  };
116
175
  function normalizeTokenPayload(res) {
117
176
  if (!res || typeof res !== "object") return null;
@@ -129,14 +188,14 @@ function normalizeTokenPayload(res) {
129
188
  return null;
130
189
  }
131
190
  const removeAllToken = () => {
132
- Cookies.remove("id_token", { path: COOKIE_PATH, domain: COOKIE_DOMAIN });
133
- Cookies.remove("access_token", { path: COOKIE_PATH, domain: COOKIE_DOMAIN });
134
- Cookies.remove("refresh_token", { path: COOKIE_PATH, domain: COOKIE_DOMAIN });
135
- Cookies.remove(`sign_session`, { domain: COOKIE_DOMAIN });
136
- Cookies.remove("token_expire", { path: COOKIE_PATH, domain: COOKIE_DOMAIN });
191
+ cookieStore.remove(envCookieName("id_token"));
192
+ cookieStore.remove(envCookieName("access_token"));
193
+ cookieStore.remove(envCookieName("refresh_token"));
194
+ cookieStore.remove(envCookieName("sign_session"));
195
+ cookieStore.remove(envCookieName("token_expire"));
137
196
  };
138
197
  const removeTokenByKey = (key) => {
139
- Cookies.remove(key || "id_token", { path: COOKIE_PATH, domain: COOKIE_DOMAIN });
198
+ cookieStore.remove(envCookieName(key || "id_token"));
140
199
  };
141
200
  const isTokenExpired = async () => {
142
201
  let t;
@@ -145,23 +204,23 @@ const isTokenExpired = async () => {
145
204
  if (typeof window === "undefined") {
146
205
  try {
147
206
  const { cookies } = await import("next/headers");
148
- const cookieStore = await cookies();
149
- const tokenExpire = cookieStore.get("token_expire")?.value;
150
- refreshToken = cookieStore.get("refresh_token")?.value;
151
- accessToken = cookieStore.get("access_token")?.value;
207
+ const nextCookies = await cookies();
208
+ const tokenExpire = nextCookies.get(envCookieName("token_expire"))?.value;
209
+ refreshToken = nextCookies.get(envCookieName("refresh_token"))?.value;
210
+ accessToken = nextCookies.get(envCookieName("access_token"))?.value;
152
211
  t = tokenExpire ? Number(tokenExpire) : void 0;
153
212
  } catch {
154
213
  return false;
155
214
  }
156
215
  } else {
157
- const rawExpire = Cookies.get("token_expire");
216
+ const rawExpire = cookieStore.get(envCookieName("token_expire"));
158
217
  t = rawExpire ? Number(rawExpire) : void 0;
159
- refreshToken = Cookies.get("refresh_token");
160
- accessToken = Cookies.get("access_token");
218
+ refreshToken = cookieStore.get(envCookieName("refresh_token"));
219
+ accessToken = cookieStore.get(envCookieName("access_token"));
161
220
  }
162
221
  const nearExpiry = (expireAt) => expireAt - Date.now() < 5 * 60 * 1e3;
163
222
  if (accessToken) {
164
- const jwtExpiry = decodeJwtExpiryMs(accessToken);
223
+ const jwtExpiry = getJwtExpiryMs(accessToken);
165
224
  if (jwtExpiry) {
166
225
  return nearExpiry(jwtExpiry);
167
226
  }
@@ -186,10 +245,17 @@ const TokenManager = {
186
245
  isTokenExpired
187
246
  };
188
247
  export {
248
+ COOKIE_DOMAIN,
249
+ COOKIE_ENV_SUFFIX,
250
+ RUNTIME_ENV,
251
+ decodeJwtPayload,
189
252
  TokenManager as default,
253
+ envCookieName,
190
254
  getToken,
255
+ isJwtExpired,
191
256
  isTokenExpired,
192
257
  normalizeTokenPayload,
193
258
  removeAllToken,
259
+ removeTokenByKey,
194
260
  setToken
195
261
  };
package/dist/jwt.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ export type JwtPayload = Record<string, unknown> & {
2
+ exp?: number;
3
+ };
4
+ export declare function decodeJwtPayload(token?: string | null): JwtPayload | null;
5
+ /** JWT exp 字段转毫秒时间戳;无法解析时返回 undefined */
6
+ export declare function getJwtExpiryMs(token?: string | null): number | undefined;
7
+ export declare function isJwtExpired(payload: JwtPayload | null, options?: {
8
+ skewSeconds?: number;
9
+ }): boolean;
10
+ //# sourceMappingURL=jwt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../src/jwt.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IACjD,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAeF,wBAAgB,gBAAgB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,UAAU,GAAG,IAAI,CAUzE;AAED,yCAAyC;AACzC,wBAAgB,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAMxE;AAED,wBAAgB,YAAY,CAC1B,OAAO,EAAE,UAAU,GAAG,IAAI,EAC1B,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GACjC,OAAO,CAKT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xchain/token",
3
- "version": "1.1.0-beta.62",
3
+ "version": "1.1.0-beta.64",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",