@yuntower/yuntower-account-electron-sdk 0.0.2 → 0.0.3

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.md CHANGED
@@ -1,113 +1,113 @@
1
- # YunTowerAccount-ElectronSDK
2
-
3
- 云塔账号通行证 Electron 桌面端 SDK,支持在桌面应用中拉起浏览器完成授权,并通过自定义 URL 方案回到应用、获取 token 与用户信息。
4
-
5
- - 官网:[https://account.yuntower.com](https://account.yuntower.com)
6
- - 文档:[https://docs.yuntower.com/account/open.html](https://docs.yuntower.com/account/open.html)
7
- - NPM:[https://www.npmjs.com/package/@yuntower/yuntower-account-electron-sdk](https://www.npmjs.com/package/@yuntower/yuntower-account-electron-sdk)
8
-
9
- ```bash
10
- npm install @yuntower/yuntower-account-electron-sdk
11
- ```
12
-
13
- 依赖:`electron >= 20`(peerDependency)
14
-
15
- ---
16
-
17
- ## 快速开始
18
-
19
- 接入前请在云塔账号通行证**应用详情页**为该应用配置并加入白名单的**重定向地址**(如 `myapp://callback`)。授权完成后浏览器会重定向到该地址;Electron 侧使用的**方案名**(`protocol`)与**重定向 URL**(`redirectUrl`)须与后台一致。下例中的 `myapp` 请替换为本应用已注册的方案名与地址。
20
-
21
- 在主进程中:① 在 **app.ready 之前** 创建 SDK 并调用 `install(app)`,将当前应用注册为该 URL 方案的默认处理程序;② 在需要登录时调用 `launchAuthorization()`,会打开系统浏览器,用户完成授权后通过重定向回到应用,Promise 返回 `{ token, user }`。
22
- 开发环境(如 `electron .`)须对 `install(app)` 传入第二、第三参数(见下方示例);生产环境直接 `sdk.install(app)`。使用 electron-builder 打包时,若需在安装时注册 URL 方案,可在 `package.json` 的 `build.protocols` 中配置 `schemes`(与 SDK 的 `protocol` 一致)。
23
-
24
- ---
25
-
26
- ### 完整示例:主进程入口 `main.js`
27
-
28
- 单实例锁保证通过 URL 方案唤起时由已运行实例处理回调;渲染进程通过 IPC 发送 `start-oauth` 触发登录,主进程将结果通过 `oauth-success` / `oauth-error` 回传。
29
-
30
- ```js
31
- const { app, BrowserWindow, ipcMain } = require('electron');
32
- const path = require('path');
33
- const YunTowerAccountElectronSDK = require('@yuntower/yuntower-account-electron-sdk').default;
34
-
35
- // 替换为你在后台配置的 appid、appsecret 及已加入白名单的 URL 方案与重定向地址
36
- const APP_ID = 'YOUR_APP_ID';
37
- const APP_SECRET = 'YOUR_APP_SECRET';
38
- const REDIRECT_URL = 'myapp://callback';
39
- const PROTOCOL = 'myapp';
40
-
41
- const sdk = new YunTowerAccountElectronSDK(APP_ID, APP_SECRET, {
42
- redirectUrl: REDIRECT_URL,
43
- protocol: PROTOCOL,
44
- scope: 'user:profile,user:email',
45
- });
46
-
47
- // 必须在 app.ready 之前注册 URL 方案
48
- if (process.defaultApp && process.argv.length >= 2) {
49
- sdk.install(app, process.execPath, [path.resolve(process.argv[1])]);
50
- } else {
51
- sdk.install(app);
52
- }
53
-
54
- const gotTheLock = app.requestSingleInstanceLock();
55
- if (!gotTheLock) {
56
- app.quit();
57
- process.exit(0);
58
- }
59
-
60
- let mainWindow = null;
61
-
62
- app.whenReady().then(() => {
63
- mainWindow = new BrowserWindow({
64
- width: 800,
65
- height: 600,
66
- webPreferences: { nodeIntegration: true, contextIsolation: false },
67
- });
68
- mainWindow.loadFile('index.html');
69
- mainWindow.on('closed', () => { mainWindow = null; });
70
- });
71
-
72
- app.on('window-all-closed', () => {
73
- if (process.platform !== 'darwin') app.quit();
74
- });
75
-
76
- // 渲染进程点击「登录」时发送 'start-oauth',此处发起授权并将结果回传
77
- ipcMain.on('start-oauth', async () => {
78
- if (mainWindow) mainWindow.webContents.send('oauth-started');
79
- try {
80
- const { token, user } = await sdk.launchAuthorization({
81
- scope: 'user:profile,user:email',
82
- fetchUser: true,
83
- });
84
- if (mainWindow) mainWindow.webContents.send('oauth-success', { token, user });
85
- } catch (err) {
86
- if (mainWindow) mainWindow.webContents.send('oauth-error', { message: err.message });
87
- }
88
- });
89
- ```
90
-
91
- ### 渲染进程示例:`index.html` 中触发登录并接收结果
92
-
93
- ```html
94
- <button id="loginBtn">使用云塔账号登录</button>
95
- <pre id="result"></pre>
96
- <script>
97
- const { ipcRenderer } = require('electron');
98
- const btn = document.getElementById('loginBtn');
99
- const result = document.getElementById('result');
100
-
101
- btn.onclick = () => ipcRenderer.send('start-oauth');
102
-
103
- ipcRenderer.on('oauth-started', () => {
104
- result.textContent = '已打开浏览器,请在浏览器中完成授权…';
105
- });
106
- ipcRenderer.on('oauth-success', (event, { token, user }) => {
107
- result.textContent = JSON.stringify({ token: { ...token, access_token: token.access_token?.slice(0, 20) + '…' }, user }, null, 2);
108
- });
109
- ipcRenderer.on('oauth-error', (event, err) => {
110
- result.textContent = '授权失败: ' + (err.message || err);
111
- });
112
- </script>
1
+ # YunTowerAccount-ElectronSDK
2
+
3
+ 云塔账号通行证 Electron 桌面端 SDK,支持在桌面应用中拉起浏览器完成授权,并通过自定义 URL 方案回到应用、获取 token 与用户信息。
4
+
5
+ - 官网:[https://account.yuntower.com](https://account.yuntower.com)
6
+ - 文档:[https://docs.yuntower.com/account/open.html](https://docs.yuntower.com/account/open.html)
7
+ - NPM:[https://www.npmjs.com/package/@yuntower/yuntower-account-electron-sdk](https://www.npmjs.com/package/@yuntower/yuntower-account-electron-sdk)
8
+
9
+ ```bash
10
+ npm install @yuntower/yuntower-account-electron-sdk
11
+ ```
12
+
13
+ 依赖:`electron >= 20`(peerDependency)
14
+
15
+ ---
16
+
17
+ ## 快速开始
18
+
19
+ 接入前请在云塔账号通行证**应用详情页**为该应用配置并加入白名单的**重定向地址**(如 `myapp://callback`)。授权完成后浏览器会重定向到该地址;Electron 侧使用的**方案名**(`protocol`)与**重定向 URL**(`redirectUrl`)须与后台一致。下例中的 `myapp` 请替换为本应用已注册的方案名与地址。
20
+
21
+ 在主进程中:① 在 **app.ready 之前** 创建 SDK 并调用 `install(app)`,将当前应用注册为该 URL 方案的默认处理程序;② 在需要登录时调用 `launchAuthorization()`,会打开系统浏览器,用户完成授权后通过重定向回到应用,Promise 返回 `{ token, user }`。
22
+ 开发环境(如 `electron .`)须对 `install(app)` 传入第二、第三参数(见下方示例);生产环境直接 `sdk.install(app)`。使用 electron-builder 打包时,若需在安装时注册 URL 方案,可在 `package.json` 的 `build.protocols` 中配置 `schemes`(与 SDK 的 `protocol` 一致)。
23
+
24
+ ---
25
+
26
+ ### 完整示例:主进程入口 `main.js`
27
+
28
+ 单实例锁保证通过 URL 方案唤起时由已运行实例处理回调;渲染进程通过 IPC 发送 `start-oauth` 触发登录,主进程将结果通过 `oauth-success` / `oauth-error` 回传。
29
+
30
+ ```js
31
+ const { app, BrowserWindow, ipcMain } = require('electron');
32
+ const path = require('path');
33
+ const YunTowerAccountElectronSDK = require('@yuntower/yuntower-account-electron-sdk').default;
34
+
35
+ // 替换为你在后台配置的 appid、appsecret 及已加入白名单的 URL 方案与重定向地址
36
+ const APP_ID = 'YOUR_APP_ID';
37
+ const APP_SECRET = 'YOUR_APP_SECRET';
38
+ const REDIRECT_URL = 'myapp://callback';
39
+ const PROTOCOL = 'myapp';
40
+
41
+ const sdk = new YunTowerAccountElectronSDK(APP_ID, APP_SECRET, {
42
+ redirectUrl: REDIRECT_URL,
43
+ protocol: PROTOCOL,
44
+ scope: 'user:profile,user:email',
45
+ });
46
+
47
+ // 必须在 app.ready 之前注册 URL 方案
48
+ if (process.defaultApp && process.argv.length >= 2) {
49
+ sdk.install(app, process.execPath, [path.resolve(process.argv[1])]);
50
+ } else {
51
+ sdk.install(app);
52
+ }
53
+
54
+ const gotTheLock = app.requestSingleInstanceLock();
55
+ if (!gotTheLock) {
56
+ app.quit();
57
+ process.exit(0);
58
+ }
59
+
60
+ let mainWindow = null;
61
+
62
+ app.whenReady().then(() => {
63
+ mainWindow = new BrowserWindow({
64
+ width: 800,
65
+ height: 600,
66
+ webPreferences: { nodeIntegration: true, contextIsolation: false },
67
+ });
68
+ mainWindow.loadFile('index.html');
69
+ mainWindow.on('closed', () => { mainWindow = null; });
70
+ });
71
+
72
+ app.on('window-all-closed', () => {
73
+ if (process.platform !== 'darwin') app.quit();
74
+ });
75
+
76
+ // 渲染进程点击「登录」时发送 'start-oauth',此处发起授权并将结果回传
77
+ ipcMain.on('start-oauth', async () => {
78
+ if (mainWindow) mainWindow.webContents.send('oauth-started');
79
+ try {
80
+ const { token, user } = await sdk.launchAuthorization({
81
+ scope: 'user:profile,user:email',
82
+ fetchUser: true,
83
+ });
84
+ if (mainWindow) mainWindow.webContents.send('oauth-success', { token, user });
85
+ } catch (err) {
86
+ if (mainWindow) mainWindow.webContents.send('oauth-error', { message: err.message });
87
+ }
88
+ });
89
+ ```
90
+
91
+ ### 渲染进程示例:`index.html` 中触发登录并接收结果
92
+
93
+ ```html
94
+ <button id="loginBtn">使用云塔账号登录</button>
95
+ <pre id="result"></pre>
96
+ <script>
97
+ const { ipcRenderer } = require('electron');
98
+ const btn = document.getElementById('loginBtn');
99
+ const result = document.getElementById('result');
100
+
101
+ btn.onclick = () => ipcRenderer.send('start-oauth');
102
+
103
+ ipcRenderer.on('oauth-started', () => {
104
+ result.textContent = '已打开浏览器,请在浏览器中完成授权…';
105
+ });
106
+ ipcRenderer.on('oauth-success', (event, { token, user }) => {
107
+ result.textContent = JSON.stringify({ token: { ...token, access_token: token.access_token?.slice(0, 20) + '…' }, user }, null, 2);
108
+ });
109
+ ipcRenderer.on('oauth-error', (event, err) => {
110
+ result.textContent = '授权失败: ' + (err.message || err);
111
+ });
112
+ </script>
113
113
  ```
package/dist/index.d.ts CHANGED
@@ -1,6 +1,19 @@
1
1
  /**
2
2
  * YunTower Account Electron SDK
3
3
  */
4
+ export interface ApiResponseBody {
5
+ code?: number;
6
+ msg?: string;
7
+ data?: unknown;
8
+ }
9
+ export declare class YunTowerAccountSDKError extends Error {
10
+ readonly status: number;
11
+ readonly responseBody: ApiResponseBody | string | null;
12
+ readonly apiCode?: number;
13
+ readonly apiMsg?: string;
14
+ constructor(message: string, status: number, responseBody: ApiResponseBody | string | null);
15
+ static format(err: unknown): string;
16
+ }
4
17
  declare const ALLOWED_SCOPES: readonly ["user:profile", "user:email", "connect:codemao_uid", "connect:pgaot_uid", "connect:dao3_uid"];
5
18
  export type AllowedScope = (typeof ALLOWED_SCOPES)[number];
6
19
  export interface ElectronSDKOptions {
@@ -90,7 +103,7 @@ export declare class YunTowerAccountElectronSDK {
90
103
  private _exchangeCodeForToken;
91
104
  /** 校验自定义 token 有效期不超过 12 天 / 24 天,否则抛错 */
92
105
  private _validateTokenExpiry;
93
- /** 发起 JSON POST/GET 请求并返回解析后的 JSON */
106
+ /** 统一请求 */
94
107
  private _fetch;
95
108
  /**
96
109
  * 用授权码换取 access_token 与 refresh_token
package/dist/index.js CHANGED
@@ -36,7 +36,68 @@ var __importStar = (this && this.__importStar) || (function () {
36
36
  };
37
37
  })();
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.YunTowerAccountElectronSDK = void 0;
39
+ exports.YunTowerAccountElectronSDK = exports.YunTowerAccountSDKError = void 0;
40
+ class YunTowerAccountSDKError extends Error {
41
+ constructor(message, status, responseBody) {
42
+ super(message);
43
+ this.name = "YunTowerAccountSDKError";
44
+ this.status = status;
45
+ this.responseBody = responseBody;
46
+ if (responseBody &&
47
+ typeof responseBody === "object" &&
48
+ "code" in responseBody) {
49
+ this.apiCode = responseBody.code;
50
+ }
51
+ if (responseBody &&
52
+ typeof responseBody === "object" &&
53
+ "msg" in responseBody) {
54
+ this.apiMsg = responseBody.msg;
55
+ }
56
+ Object.setPrototypeOf(this, YunTowerAccountSDKError.prototype);
57
+ }
58
+ static format(err) {
59
+ if (err instanceof YunTowerAccountSDKError) {
60
+ const parts = [
61
+ `[YunTowerAccountSDKError] ${err.message}`,
62
+ `HTTP Status: ${err.status}`,
63
+ ];
64
+ if (err.apiMsg != null)
65
+ parts.push(`API Msg: ${err.apiMsg}`);
66
+ if (err.apiCode != null)
67
+ parts.push(`API Code: ${err.apiCode}`);
68
+ if (err.responseBody != null) {
69
+ parts.push("Response: " +
70
+ (typeof err.responseBody === "string"
71
+ ? err.responseBody
72
+ : JSON.stringify(err.responseBody, null, 2)));
73
+ }
74
+ return parts.join("\n");
75
+ }
76
+ if (err instanceof Error)
77
+ return err.message;
78
+ return String(err);
79
+ }
80
+ }
81
+ exports.YunTowerAccountSDKError = YunTowerAccountSDKError;
82
+ function parseResponseBody(raw) {
83
+ if (!raw.trim())
84
+ return null;
85
+ try {
86
+ return JSON.parse(raw);
87
+ }
88
+ catch {
89
+ return raw;
90
+ }
91
+ }
92
+ function createApiError(status, rawBody) {
93
+ const responseBody = parseResponseBody(rawBody);
94
+ const msg = responseBody &&
95
+ typeof responseBody === "object" &&
96
+ responseBody.msg
97
+ ? responseBody.msg
98
+ : `HTTP error! Status: ${status}`;
99
+ return new YunTowerAccountSDKError(msg, status, responseBody);
100
+ }
40
101
  const ACCESS_TOKEN_MAX_EXPIRE = 12 * 24 * 3600;
41
102
  const REFRESH_TOKEN_MAX_EXPIRE = 24 * 24 * 3600;
42
103
  const AVATAR_MAX_SIZE = 15 * 1024 * 1024;
@@ -220,10 +281,11 @@ class YunTowerAccountElectronSDK {
220
281
  const res = await this._fetch(`${this.config.api}/user/token/get`, "POST", body);
221
282
  const result = res;
222
283
  if (result.code !== 0) {
223
- throw new Error(result.msg || "换取 token 失败");
284
+ throw new YunTowerAccountSDKError(result.msg ?? "换取 token 失败", 200, result);
285
+ }
286
+ if (!result.data) {
287
+ throw new YunTowerAccountSDKError("换取 token 失败", 200, result);
224
288
  }
225
- if (!result.data)
226
- throw new Error("换取 token 失败");
227
289
  return result.data;
228
290
  }
229
291
  /** 校验自定义 token 有效期不超过 12 天 / 24 天,否则抛错 */
@@ -235,7 +297,7 @@ class YunTowerAccountElectronSDK {
235
297
  throw new Error(`refresh_token 有效期不能超过 ${REFRESH_TOKEN_MAX_EXPIRE} 秒(24 天)`);
236
298
  }
237
299
  }
238
- /** 发起 JSON POST/GET 请求并返回解析后的 JSON */
300
+ /** 统一请求 */
239
301
  async _fetch(url, method, data = {}, headers = {}) {
240
302
  const opt = {
241
303
  method,
@@ -244,9 +306,11 @@ class YunTowerAccountElectronSDK {
244
306
  if (method === "POST")
245
307
  opt.body = JSON.stringify(data);
246
308
  const response = await fetch(url, opt);
247
- if (!response.ok)
248
- throw new Error(`HTTP error! Status: ${response.status}`);
249
- return response.json();
309
+ const rawBody = await response.text();
310
+ if (!response.ok) {
311
+ throw createApiError(response.status, rawBody);
312
+ }
313
+ return rawBody ? parseResponseBody(rawBody) : null;
250
314
  }
251
315
  /**
252
316
  * 用授权码换取 access_token 与 refresh_token
@@ -380,9 +444,11 @@ class YunTowerAccountElectronSDK {
380
444
  method: "POST",
381
445
  body: form,
382
446
  });
383
- if (!response.ok)
384
- throw new Error(`HTTP error! Status: ${response.status}`);
385
- return response.json();
447
+ const rawBody = await response.text();
448
+ if (!response.ok) {
449
+ throw createApiError(response.status, rawBody);
450
+ }
451
+ return parseResponseBody(rawBody) ?? null;
386
452
  }
387
453
  }
388
454
  exports.YunTowerAccountElectronSDK = YunTowerAccountElectronSDK;
package/package.json CHANGED
@@ -1,36 +1,36 @@
1
- {
2
- "name": "@yuntower/yuntower-account-electron-sdk",
3
- "description": "YunTower Account Electron SDK",
4
- "version": "0.0.2",
5
- "private": false,
6
- "author": "yuntower",
7
- "license": "Apache-2.0",
8
- "main": "dist/index.js",
9
- "module": "dist/index.js",
10
- "types": "dist/index.d.ts",
11
- "exports": {
12
- ".": {
13
- "require": "./dist/index.js",
14
- "import": "./dist/index.js",
15
- "types": "./dist/index.d.ts"
16
- }
17
- },
18
- "files": [
19
- "dist"
20
- ],
21
- "peerDependencies": {
22
- "electron": ">=20.0.0"
23
- },
24
- "scripts": {
25
- "build": "tsc",
26
- "test": "echo \"Error: no test specified\" && exit 1"
27
- },
28
- "publishConfig": {
29
- "access": "public"
30
- },
31
- "devDependencies": {
32
- "@types/node": "^20.0.0",
33
- "electron": "^28.0.0",
34
- "typescript": "^5.9.3"
35
- }
36
- }
1
+ {
2
+ "name": "@yuntower/yuntower-account-electron-sdk",
3
+ "description": "YunTower Account Electron SDK",
4
+ "version": "0.0.3",
5
+ "private": false,
6
+ "author": "yuntower",
7
+ "license": "Apache-2.0",
8
+ "main": "dist/index.js",
9
+ "module": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "require": "./dist/index.js",
14
+ "import": "./dist/index.js",
15
+ "types": "./dist/index.d.ts"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "peerDependencies": {
22
+ "electron": ">=20.0.0"
23
+ },
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "test": "echo \"Error: no test specified\" && exit 1"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^20.0.0",
33
+ "electron": "^28.0.0",
34
+ "typescript": "^5.9.3"
35
+ }
36
+ }