@zhimakechuang/game-sdk 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.
- package/README.md +176 -176
- package/dist/index.d.mts +25 -5
- package/dist/index.d.ts +25 -5
- package/dist/index.global.js +109 -3
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +103 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +110 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +43 -55
package/README.md
CHANGED
|
@@ -1,179 +1,179 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
通用游戏 SDK — 封装 [
|
|
4
|
-
|
|
5
|
-
## 特性
|
|
6
|
-
|
|
7
|
-
- **一套代码三端运行**:自动检测运行环境(wx / tt / H5),底层切换 `wx.request` / `tt.request` / `fetch`
|
|
8
|
-
- **Token 自动持久化**:登录后自动存到 `localStorage`(H5)或本地存储(小游戏),下次进入免登录
|
|
9
|
-
- **多租户隔离**:通过 `gameId` 隔离不同游戏的数据,一套服务端承载所有游戏
|
|
10
|
-
- **模拟模式**:`mock: true` 时不连服务端,返回假数据,方便模板开发与调试
|
|
11
|
-
- **全类型定义**:TypeScript 编写,自带 `.d.ts`,IDE 智能提示完备
|
|
12
|
-
- **零依赖**:打包后仅 8KB(gzip 后更小),不引入任何运行时依赖
|
|
13
|
-
|
|
14
|
-
## 安装
|
|
15
|
-
|
|
1
|
+
# xiangsuhuabu
|
|
2
|
+
|
|
3
|
+
通用游戏 SDK — 封装 [game-server-shared](https://github.com/) 所有 HTTP 接口,一套代码支持**微信小游戏 / 抖音小游戏 / H5** 三端。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- **一套代码三端运行**:自动检测运行环境(wx / tt / H5),底层切换 `wx.request` / `tt.request` / `fetch`
|
|
8
|
+
- **Token 自动持久化**:登录后自动存到 `localStorage`(H5)或本地存储(小游戏),下次进入免登录
|
|
9
|
+
- **多租户隔离**:通过 `gameId` 隔离不同游戏的数据,一套服务端承载所有游戏
|
|
10
|
+
- **模拟模式**:`mock: true` 时不连服务端,返回假数据,方便模板开发与调试
|
|
11
|
+
- **全类型定义**:TypeScript 编写,自带 `.d.ts`,IDE 智能提示完备
|
|
12
|
+
- **零依赖**:打包后仅 8KB(gzip 后更小),不引入任何运行时依赖
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
16
|
```bash
|
|
17
|
-
npm install
|
|
17
|
+
npm install xiangsuhuabu
|
|
18
18
|
# 或
|
|
19
|
-
pnpm add
|
|
19
|
+
pnpm add xiangsuhuabu
|
|
20
20
|
# 或
|
|
21
|
-
yarn add
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## 快速开始
|
|
25
|
-
|
|
26
|
-
### 工程化项目(npm 安装)
|
|
27
|
-
|
|
28
|
-
```ts
|
|
29
|
-
import { createGameSdk } from '
|
|
30
|
-
|
|
31
|
-
const sdk = createGameSdk({
|
|
32
|
-
gameId: 'merge-game', // 在平台后台登记的游戏 ID
|
|
33
|
-
baseUrl: 'https://api.example.com', // api-server 根地址
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// 登录(微信小游戏)
|
|
37
|
-
const { wx } = miniProgram; // 假设已拿到微信环境
|
|
38
|
-
const { code } = await wx.login();
|
|
39
|
-
await sdk.auth.login({ code, platform: 'wx' });
|
|
40
|
-
|
|
41
|
-
// 提交分数
|
|
42
|
-
const result = await sdk.score.submit({ score: 1000 });
|
|
43
|
-
console.log(result.rank, result.isHighScore);
|
|
44
|
-
|
|
45
|
-
// 拉排行榜
|
|
46
|
-
const top = await sdk.leaderboard.top(100);
|
|
47
|
-
const me = await sdk.leaderboard.myRank();
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### CDN 引入(轻量模板 / H5)
|
|
51
|
-
|
|
52
|
-
```html
|
|
53
|
-
<script src="https://unpkg.com
|
|
54
|
-
<script>
|
|
55
|
-
const sdk = GameSDK.createGameSdk({
|
|
56
|
-
gameId: 'merge-game',
|
|
57
|
-
baseUrl: 'https://api.example.com',
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// 访客登录(H5 无需微信/抖音凭据)
|
|
61
|
-
await sdk.auth.login({ code: 'device-id', platform: 'guest' });
|
|
62
|
-
await sdk.score.submit({ score: 1000 });
|
|
63
|
-
</script>
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### 模拟模式(不连服务端)
|
|
67
|
-
|
|
68
|
-
```ts
|
|
69
|
-
const sdk = createGameSdk({
|
|
70
|
-
gameId: 'demo',
|
|
71
|
-
baseUrl: '', // 不需要
|
|
72
|
-
mock: true, // 关键:走 mock
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
await sdk.auth.login({ code: 'x', platform: 'guest' });
|
|
76
|
-
const r = await sdk.score.submit({ score: 100 });
|
|
77
|
-
// r.isHighScore === true, r.rank === 1
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## API 参考
|
|
81
|
-
|
|
82
|
-
### `createGameSdk(config)` → `GameSdk`
|
|
83
|
-
|
|
84
|
-
| 参数 | 类型 | 说明 |
|
|
85
|
-
|---|---|---|
|
|
86
|
-
| `config.gameId` | `string` | 游戏 ID(必填) |
|
|
87
|
-
| `config.baseUrl` | `string` | api-server 根地址(必填,mock 模式可空) |
|
|
88
|
-
| `config.mock` | `boolean` | 模拟模式,默认 `false` |
|
|
89
|
-
|
|
90
|
-
### `sdk.auth` — 身份与登录
|
|
91
|
-
|
|
92
|
-
| 方法 | 说明 |
|
|
93
|
-
|---|---|
|
|
94
|
-
| `login(opts)` | 登录(wx/tt/guest),成功后自动持久化 token |
|
|
95
|
-
| `me()` | 获取当前登录玩家信息 |
|
|
96
|
-
| `logout()` | 登出,清除本地 token |
|
|
97
|
-
| `getToken()` | 获取当前 token(调试用) |
|
|
98
|
-
|
|
99
|
-
```ts
|
|
100
|
-
await sdk.auth.login({
|
|
101
|
-
code: 'wx-code', // wx/tt 的 login code,或 guest 的设备标识
|
|
102
|
-
platform: 'wx', // 'wx' | 'tt' | 'guest',默认 guest
|
|
103
|
-
deviceId?: 'xxx', // platform=guest 时必传
|
|
104
|
-
nickname?: '玩家名', // 首次登录设置
|
|
105
|
-
});
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### `sdk.score` — 分数
|
|
109
|
-
|
|
110
|
-
| 方法 | 说明 |
|
|
111
|
-
|---|---|
|
|
112
|
-
| `submit(opts)` | 提交分数,返回 `{ isHighScore, previousBest, bestScore, rank }` |
|
|
113
|
-
|
|
114
|
-
```ts
|
|
115
|
-
const r = await sdk.score.submit({
|
|
116
|
-
score: 1000,
|
|
117
|
-
extra?: { maxLevel: 5 }, // 游戏自定义数据
|
|
118
|
-
metadata?: { seed: 'xxx' }, // 反作弊元数据
|
|
119
|
-
});
|
|
120
|
-
// r.submitted / r.isHighScore / r.previousBest / r.bestScore / r.rank
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
### `sdk.leaderboard` — 排行榜
|
|
124
|
-
|
|
125
|
-
| 方法 | 说明 |
|
|
126
|
-
|---|---|
|
|
127
|
-
| `top(limit?)` | 获取 Top N(匿名可访问,默认 100) |
|
|
128
|
-
| `myRank()` | 获取当前玩家排名(需登录) |
|
|
129
|
-
| `nearby(range?)` | 获取附近排名(需登录,默认 ±5) |
|
|
130
|
-
|
|
131
|
-
### `sdk.player` — 玩家档案
|
|
132
|
-
|
|
133
|
-
| 方法 | 说明 |
|
|
134
|
-
|---|---|
|
|
135
|
-
| `getProfile()` | 获取当前玩家档案 |
|
|
136
|
-
| `updateProfile(patch)` | 更新昵称/头像/扩展字段 |
|
|
137
|
-
|
|
138
|
-
### `sdk.save` — 云存档(KV)
|
|
139
|
-
|
|
140
|
-
| 方法 | 说明 |
|
|
141
|
-
|---|---|
|
|
142
|
-
| `set(key, value)` | 保存数据到指定 key |
|
|
143
|
-
| `get(key)` | 读取指定 key |
|
|
144
|
-
| `getAll()` | 读取所有存档 |
|
|
145
|
-
| `remove(key)` | 删除指定 key |
|
|
146
|
-
| `clear()` | 清空所有存档 |
|
|
147
|
-
|
|
148
|
-
```ts
|
|
149
|
-
// 存进度
|
|
150
|
-
await sdk.save.set('progress', { level: 5, coins: 100 });
|
|
151
|
-
const progress = await sdk.save.get('progress');
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
## 运行环境检测
|
|
155
|
-
|
|
156
|
-
SDK 会自动检测当前环境:
|
|
157
|
-
|
|
158
|
-
| 环境 | 检测条件 | HTTP 实现 | 存储实现 |
|
|
159
|
-
|---|---|---|---|
|
|
160
|
-
| 微信小游戏 | `typeof wx !== 'undefined'` | `wx.request` | `wx.setStorageSync` |
|
|
161
|
-
| 抖音小游戏 | `typeof tt !== 'undefined'` | `tt.request` | `tt.setStorageSync` |
|
|
162
|
-
| H5 浏览器 | 其他 | `fetch` | `localStorage` |
|
|
163
|
-
|
|
164
|
-
无需手动指定,SDK 自动适配。
|
|
165
|
-
|
|
166
|
-
## 服务端
|
|
167
|
-
|
|
168
|
-
本 SDK 配套的通用游戏服务端在 [`apps/game-server-shared`](
|
|
169
|
-
|
|
170
|
-
- 多租户数据隔离(按 `gameId`)
|
|
171
|
-
- 身份(微信/抖音 code2session + JWT)/ 访客模式
|
|
172
|
-
- 分数 + 排行榜(Top / 我的排名 / 附近玩家)
|
|
173
|
-
- 云存档(KV)
|
|
174
|
-
- 玩家档案 + 封禁管理
|
|
175
|
-
- Admin 运营接口
|
|
176
|
-
|
|
177
|
-
## License
|
|
178
|
-
|
|
179
|
-
MIT
|
|
21
|
+
yarn add xiangsuhuabu
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 快速开始
|
|
25
|
+
|
|
26
|
+
### 工程化项目(npm 安装)
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { createGameSdk } from 'xiangsuhuabu';
|
|
30
|
+
|
|
31
|
+
const sdk = createGameSdk({
|
|
32
|
+
gameId: 'merge-game', // 在平台后台登记的游戏 ID
|
|
33
|
+
baseUrl: 'https://api.example.com', // api-server 根地址
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// 登录(微信小游戏)
|
|
37
|
+
const { wx } = miniProgram; // 假设已拿到微信环境
|
|
38
|
+
const { code } = await wx.login();
|
|
39
|
+
await sdk.auth.login({ code, platform: 'wx' });
|
|
40
|
+
|
|
41
|
+
// 提交分数
|
|
42
|
+
const result = await sdk.score.submit({ score: 1000 });
|
|
43
|
+
console.log(result.rank, result.isHighScore);
|
|
44
|
+
|
|
45
|
+
// 拉排行榜
|
|
46
|
+
const top = await sdk.leaderboard.top(100);
|
|
47
|
+
const me = await sdk.leaderboard.myRank();
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### CDN 引入(轻量模板 / H5)
|
|
51
|
+
|
|
52
|
+
```html
|
|
53
|
+
<script src="https://unpkg.com/xiangsuhuabu"></script>
|
|
54
|
+
<script>
|
|
55
|
+
const sdk = GameSDK.createGameSdk({
|
|
56
|
+
gameId: 'merge-game',
|
|
57
|
+
baseUrl: 'https://api.example.com',
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// 访客登录(H5 无需微信/抖音凭据)
|
|
61
|
+
await sdk.auth.login({ code: 'device-id', platform: 'guest' });
|
|
62
|
+
await sdk.score.submit({ score: 1000 });
|
|
63
|
+
</script>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 模拟模式(不连服务端)
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
const sdk = createGameSdk({
|
|
70
|
+
gameId: 'demo',
|
|
71
|
+
baseUrl: '', // 不需要
|
|
72
|
+
mock: true, // 关键:走 mock
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
await sdk.auth.login({ code: 'x', platform: 'guest' });
|
|
76
|
+
const r = await sdk.score.submit({ score: 100 });
|
|
77
|
+
// r.isHighScore === true, r.rank === 1
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## API 参考
|
|
81
|
+
|
|
82
|
+
### `createGameSdk(config)` → `GameSdk`
|
|
83
|
+
|
|
84
|
+
| 参数 | 类型 | 说明 |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `config.gameId` | `string` | 游戏 ID(必填) |
|
|
87
|
+
| `config.baseUrl` | `string` | api-server 根地址(必填,mock 模式可空) |
|
|
88
|
+
| `config.mock` | `boolean` | 模拟模式,默认 `false` |
|
|
89
|
+
|
|
90
|
+
### `sdk.auth` — 身份与登录
|
|
91
|
+
|
|
92
|
+
| 方法 | 说明 |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `login(opts)` | 登录(wx/tt/guest),成功后自动持久化 token |
|
|
95
|
+
| `me()` | 获取当前登录玩家信息 |
|
|
96
|
+
| `logout()` | 登出,清除本地 token |
|
|
97
|
+
| `getToken()` | 获取当前 token(调试用) |
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
await sdk.auth.login({
|
|
101
|
+
code: 'wx-code', // wx/tt 的 login code,或 guest 的设备标识
|
|
102
|
+
platform: 'wx', // 'wx' | 'tt' | 'guest',默认 guest
|
|
103
|
+
deviceId?: 'xxx', // platform=guest 时必传
|
|
104
|
+
nickname?: '玩家名', // 首次登录设置
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `sdk.score` — 分数
|
|
109
|
+
|
|
110
|
+
| 方法 | 说明 |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `submit(opts)` | 提交分数,返回 `{ isHighScore, previousBest, bestScore, rank }` |
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const r = await sdk.score.submit({
|
|
116
|
+
score: 1000,
|
|
117
|
+
extra?: { maxLevel: 5 }, // 游戏自定义数据
|
|
118
|
+
metadata?: { seed: 'xxx' }, // 反作弊元数据
|
|
119
|
+
});
|
|
120
|
+
// r.submitted / r.isHighScore / r.previousBest / r.bestScore / r.rank
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### `sdk.leaderboard` — 排行榜
|
|
124
|
+
|
|
125
|
+
| 方法 | 说明 |
|
|
126
|
+
|---|---|
|
|
127
|
+
| `top(limit?)` | 获取 Top N(匿名可访问,默认 100) |
|
|
128
|
+
| `myRank()` | 获取当前玩家排名(需登录) |
|
|
129
|
+
| `nearby(range?)` | 获取附近排名(需登录,默认 ±5) |
|
|
130
|
+
|
|
131
|
+
### `sdk.player` — 玩家档案
|
|
132
|
+
|
|
133
|
+
| 方法 | 说明 |
|
|
134
|
+
|---|---|
|
|
135
|
+
| `getProfile()` | 获取当前玩家档案 |
|
|
136
|
+
| `updateProfile(patch)` | 更新昵称/头像/扩展字段 |
|
|
137
|
+
|
|
138
|
+
### `sdk.save` — 云存档(KV)
|
|
139
|
+
|
|
140
|
+
| 方法 | 说明 |
|
|
141
|
+
|---|---|
|
|
142
|
+
| `set(key, value)` | 保存数据到指定 key |
|
|
143
|
+
| `get(key)` | 读取指定 key |
|
|
144
|
+
| `getAll()` | 读取所有存档 |
|
|
145
|
+
| `remove(key)` | 删除指定 key |
|
|
146
|
+
| `clear()` | 清空所有存档 |
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
// 存进度
|
|
150
|
+
await sdk.save.set('progress', { level: 5, coins: 100 });
|
|
151
|
+
const progress = await sdk.save.get('progress');
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 运行环境检测
|
|
155
|
+
|
|
156
|
+
SDK 会自动检测当前环境:
|
|
157
|
+
|
|
158
|
+
| 环境 | 检测条件 | HTTP 实现 | 存储实现 |
|
|
159
|
+
|---|---|---|---|
|
|
160
|
+
| 微信小游戏 | `typeof wx !== 'undefined'` | `wx.request` | `wx.setStorageSync` |
|
|
161
|
+
| 抖音小游戏 | `typeof tt !== 'undefined'` | `tt.request` | `tt.setStorageSync` |
|
|
162
|
+
| H5 浏览器 | 其他 | `fetch` | `localStorage` |
|
|
163
|
+
|
|
164
|
+
无需手动指定,SDK 自动适配。
|
|
165
|
+
|
|
166
|
+
## 服务端
|
|
167
|
+
|
|
168
|
+
本 SDK 配套的通用游戏服务端在 [`apps/game-server-shared`](../../apps/game-server-shared)(NestJS + MongoDB),提供:
|
|
169
|
+
|
|
170
|
+
- 多租户数据隔离(按 `gameId`)
|
|
171
|
+
- 身份(微信/抖音 code2session + JWT)/ 访客模式
|
|
172
|
+
- 分数 + 排行榜(Top / 我的排名 / 附近玩家)
|
|
173
|
+
- 云存档(KV)
|
|
174
|
+
- 玩家档案 + 封禁管理
|
|
175
|
+
- Admin 运营接口
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -5,10 +5,25 @@
|
|
|
5
5
|
type Platform = 'wx' | 'tt' | 'guest';
|
|
6
6
|
/** SDK 配置 */
|
|
7
7
|
interface GameSdkConfig {
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* 游戏 ID(平台创建游戏实例后生成)。
|
|
10
|
+
* - 模板开发阶段可不传(走 mock 模式)
|
|
11
|
+
* - 游戏运行阶段由平台构建时注入到 __GAME_CONFIG__
|
|
12
|
+
*/
|
|
13
|
+
gameId?: string;
|
|
10
14
|
/** api-server 根地址(如 https://api.example.com) */
|
|
11
15
|
baseUrl: string;
|
|
16
|
+
/**
|
|
17
|
+
* 应用标识(AccessKey),用于鉴权。
|
|
18
|
+
* 由平台颁发,构建时注入,运行时自动从 __GAME_CONFIG__ 读取。
|
|
19
|
+
*/
|
|
20
|
+
accessKey?: string;
|
|
21
|
+
/**
|
|
22
|
+
* 应用密钥(SecretKey),用于请求签名。
|
|
23
|
+
* 由平台颁发,构建时注入,运行时自动从 __GAME_CONFIG__ 读取。
|
|
24
|
+
* ⚠️ 此值不会出现在网络请求中,仅用于本地计算签名。
|
|
25
|
+
*/
|
|
26
|
+
secretKey?: string;
|
|
12
27
|
/** 模拟模式(不连服务端,返回假数据,用于模板开发) */
|
|
13
28
|
mock?: boolean;
|
|
14
29
|
}
|
|
@@ -78,16 +93,21 @@ interface SaveEntry {
|
|
|
78
93
|
*
|
|
79
94
|
* 用法(工程化):
|
|
80
95
|
* import { createGameSdk } from '@game-factory/game-sdk';
|
|
81
|
-
* const sdk = createGameSdk({
|
|
96
|
+
* const sdk = createGameSdk({ baseUrl: 'https://api.example.com' });
|
|
82
97
|
* await sdk.auth.login({ code: 'xxx', platform: 'wx' });
|
|
83
98
|
* await sdk.score.submit({ score: 1000 });
|
|
84
99
|
*
|
|
85
100
|
* 用法(CDN):
|
|
86
101
|
* <script src="https://cdn.example.com/game-sdk/index.umd.js"></script>
|
|
87
|
-
* const sdk = GameSDK.createGameSdk({
|
|
102
|
+
* const sdk = GameSDK.createGameSdk({ baseUrl: '...' });
|
|
88
103
|
*
|
|
89
104
|
* 用法(模拟模式,不连服务端):
|
|
90
|
-
* const sdk = createGameSdk({
|
|
105
|
+
* const sdk = createGameSdk({ baseUrl: '', mock: true });
|
|
106
|
+
*
|
|
107
|
+
* 凭证说明:
|
|
108
|
+
* gameId / accessKey / secretKey 由平台在构建时注入到 __GAME_CONFIG__,
|
|
109
|
+
* SDK 自动读取,开发者无需手动传入。
|
|
110
|
+
* 模板开发阶段使用 mock 模式,不需要凭证。
|
|
91
111
|
*/
|
|
92
112
|
|
|
93
113
|
declare class GameSdkError extends Error {
|
package/dist/index.d.ts
CHANGED
|
@@ -5,10 +5,25 @@
|
|
|
5
5
|
type Platform = 'wx' | 'tt' | 'guest';
|
|
6
6
|
/** SDK 配置 */
|
|
7
7
|
interface GameSdkConfig {
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* 游戏 ID(平台创建游戏实例后生成)。
|
|
10
|
+
* - 模板开发阶段可不传(走 mock 模式)
|
|
11
|
+
* - 游戏运行阶段由平台构建时注入到 __GAME_CONFIG__
|
|
12
|
+
*/
|
|
13
|
+
gameId?: string;
|
|
10
14
|
/** api-server 根地址(如 https://api.example.com) */
|
|
11
15
|
baseUrl: string;
|
|
16
|
+
/**
|
|
17
|
+
* 应用标识(AccessKey),用于鉴权。
|
|
18
|
+
* 由平台颁发,构建时注入,运行时自动从 __GAME_CONFIG__ 读取。
|
|
19
|
+
*/
|
|
20
|
+
accessKey?: string;
|
|
21
|
+
/**
|
|
22
|
+
* 应用密钥(SecretKey),用于请求签名。
|
|
23
|
+
* 由平台颁发,构建时注入,运行时自动从 __GAME_CONFIG__ 读取。
|
|
24
|
+
* ⚠️ 此值不会出现在网络请求中,仅用于本地计算签名。
|
|
25
|
+
*/
|
|
26
|
+
secretKey?: string;
|
|
12
27
|
/** 模拟模式(不连服务端,返回假数据,用于模板开发) */
|
|
13
28
|
mock?: boolean;
|
|
14
29
|
}
|
|
@@ -78,16 +93,21 @@ interface SaveEntry {
|
|
|
78
93
|
*
|
|
79
94
|
* 用法(工程化):
|
|
80
95
|
* import { createGameSdk } from '@game-factory/game-sdk';
|
|
81
|
-
* const sdk = createGameSdk({
|
|
96
|
+
* const sdk = createGameSdk({ baseUrl: 'https://api.example.com' });
|
|
82
97
|
* await sdk.auth.login({ code: 'xxx', platform: 'wx' });
|
|
83
98
|
* await sdk.score.submit({ score: 1000 });
|
|
84
99
|
*
|
|
85
100
|
* 用法(CDN):
|
|
86
101
|
* <script src="https://cdn.example.com/game-sdk/index.umd.js"></script>
|
|
87
|
-
* const sdk = GameSDK.createGameSdk({
|
|
102
|
+
* const sdk = GameSDK.createGameSdk({ baseUrl: '...' });
|
|
88
103
|
*
|
|
89
104
|
* 用法(模拟模式,不连服务端):
|
|
90
|
-
* const sdk = createGameSdk({
|
|
105
|
+
* const sdk = createGameSdk({ baseUrl: '', mock: true });
|
|
106
|
+
*
|
|
107
|
+
* 凭证说明:
|
|
108
|
+
* gameId / accessKey / secretKey 由平台在构建时注入到 __GAME_CONFIG__,
|
|
109
|
+
* SDK 自动读取,开发者无需手动传入。
|
|
110
|
+
* 模板开发阶段使用 mock 模式,不需要凭证。
|
|
91
111
|
*/
|
|
92
112
|
|
|
93
113
|
declare class GameSdkError extends Error {
|
package/dist/index.global.js
CHANGED
|
@@ -4,6 +4,12 @@ var GameSDK = (() => {
|
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
|
+
}) : x)(function(x) {
|
|
10
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
|
+
});
|
|
7
13
|
var __export = (target, all) => {
|
|
8
14
|
for (var name in all)
|
|
9
15
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -112,6 +118,69 @@ var GameSDK = (() => {
|
|
|
112
118
|
}
|
|
113
119
|
}
|
|
114
120
|
|
|
121
|
+
// src/sign.ts
|
|
122
|
+
function sha256Hex(input) {
|
|
123
|
+
const g = globalThis;
|
|
124
|
+
if (g.crypto?.createHash) {
|
|
125
|
+
return g.crypto.createHash("sha256").update(input).digest("hex");
|
|
126
|
+
}
|
|
127
|
+
if (typeof __require === "function") {
|
|
128
|
+
try {
|
|
129
|
+
const nodeCrypto = __require("crypto");
|
|
130
|
+
return nodeCrypto.createHash("sha256").update(input).digest("hex");
|
|
131
|
+
} catch {
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
let h1 = 2166136261;
|
|
135
|
+
for (let i = 0; i < input.length; i++) {
|
|
136
|
+
h1 ^= input.charCodeAt(i);
|
|
137
|
+
h1 = Math.imul(h1, 16777619);
|
|
138
|
+
}
|
|
139
|
+
return (h1 >>> 0).toString(16).padStart(8, "0").repeat(8);
|
|
140
|
+
}
|
|
141
|
+
function hmacSha256(key, message) {
|
|
142
|
+
const g = globalThis;
|
|
143
|
+
if (g.crypto?.createHmac) {
|
|
144
|
+
return g.crypto.createHmac("sha256", key).update(message).digest("hex");
|
|
145
|
+
}
|
|
146
|
+
if (typeof __require === "function") {
|
|
147
|
+
try {
|
|
148
|
+
const nodeCrypto = __require("crypto");
|
|
149
|
+
return nodeCrypto.createHmac("sha256", key).update(message).digest("hex");
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const blockLen = 64;
|
|
154
|
+
const k = key.length > blockLen ? sha256Hex(key) : key.padEnd(blockLen, "\0");
|
|
155
|
+
const ipad = Array.from(k).map((c) => String.fromCharCode(c.charCodeAt(0) ^ 54)).join("");
|
|
156
|
+
const opad = Array.from(k).map((c) => String.fromCharCode(c.charCodeAt(0) ^ 92)).join("");
|
|
157
|
+
return sha256Hex(opad + sha256Hex(ipad + message));
|
|
158
|
+
}
|
|
159
|
+
function generateNonce() {
|
|
160
|
+
const g = globalThis;
|
|
161
|
+
if (g.crypto?.randomBytes) {
|
|
162
|
+
return g.crypto.randomBytes(8).toString("hex");
|
|
163
|
+
}
|
|
164
|
+
let s = "";
|
|
165
|
+
const chars = "0123456789abcdef";
|
|
166
|
+
for (let i = 0; i < 16; i++) s += chars[Math.floor(Math.random() * 16)];
|
|
167
|
+
return s;
|
|
168
|
+
}
|
|
169
|
+
function buildSignHeaders(method, path, body, credential) {
|
|
170
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
171
|
+
const nonce = generateNonce();
|
|
172
|
+
const bodyStr = body !== void 0 && body !== null ? JSON.stringify(body) : "";
|
|
173
|
+
const bodyHash = sha256Hex(bodyStr);
|
|
174
|
+
const signStr = [method, path, String(timestamp), bodyHash, credential.accessKey].join("\n");
|
|
175
|
+
const signature = hmacSha256(credential.secretKey, signStr);
|
|
176
|
+
return {
|
|
177
|
+
"X-Access-Key": credential.accessKey,
|
|
178
|
+
"X-Timestamp": String(timestamp),
|
|
179
|
+
"X-Nonce": nonce,
|
|
180
|
+
"X-Signature": signature
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
115
184
|
// src/index.ts
|
|
116
185
|
var GameSdkError = class extends Error {
|
|
117
186
|
constructor(message, status, data) {
|
|
@@ -122,8 +191,21 @@ var GameSDK = (() => {
|
|
|
122
191
|
}
|
|
123
192
|
};
|
|
124
193
|
function createGameSdk(config) {
|
|
125
|
-
const
|
|
126
|
-
|
|
194
|
+
const runtimeConfig = resolveRuntimeConfig(config);
|
|
195
|
+
if (!runtimeConfig.mock && !runtimeConfig.gameId) {
|
|
196
|
+
throw new GameSdkError(
|
|
197
|
+
"\u7F3A\u5C11 gameId\uFF1A\u8BF7\u786E\u8BA4\u6E38\u620F\u5DF2\u901A\u8FC7\u5E73\u53F0\u6784\u5EFA\uFF0C\u6216\u4F7F\u7528 mock \u6A21\u5F0F\u8FDB\u884C\u6A21\u677F\u5F00\u53D1",
|
|
198
|
+
400
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
if (!runtimeConfig.mock && (!runtimeConfig.accessKey || !runtimeConfig.secretKey)) {
|
|
202
|
+
throw new GameSdkError(
|
|
203
|
+
"\u7F3A\u5C11 accessKey/secretKey\uFF1A\u8BF7\u786E\u8BA4\u6E38\u620F\u5DF2\u901A\u8FC7\u5E73\u53F0\u6784\u5EFA\u5E76\u9881\u53D1\u4E86\u6A21\u677F\u7EA7\u51ED\u8BC1",
|
|
204
|
+
400
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
const apiUrl = runtimeConfig.baseUrl ? `${runtimeConfig.baseUrl}/game-api/${runtimeConfig.gameId}` : "";
|
|
208
|
+
const tokenKey = `gf_token_${runtimeConfig.gameId ?? "mock"}`;
|
|
127
209
|
let token = storageGet(tokenKey);
|
|
128
210
|
function getToken() {
|
|
129
211
|
return token;
|
|
@@ -134,10 +216,19 @@ var GameSDK = (() => {
|
|
|
134
216
|
else storageRemove(tokenKey);
|
|
135
217
|
}
|
|
136
218
|
async function call(method, path, body) {
|
|
137
|
-
if (
|
|
219
|
+
if (runtimeConfig.mock) {
|
|
138
220
|
return mockResponse(method, path, body);
|
|
139
221
|
}
|
|
140
222
|
const headers = {};
|
|
223
|
+
if (runtimeConfig.accessKey && runtimeConfig.secretKey) {
|
|
224
|
+
const signHeaders = buildSignHeaders(
|
|
225
|
+
method,
|
|
226
|
+
path,
|
|
227
|
+
body,
|
|
228
|
+
{ accessKey: runtimeConfig.accessKey, secretKey: runtimeConfig.secretKey }
|
|
229
|
+
);
|
|
230
|
+
Object.assign(headers, signHeaders);
|
|
231
|
+
}
|
|
141
232
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
142
233
|
const res = await request({
|
|
143
234
|
url: `${apiUrl}${path}`,
|
|
@@ -184,6 +275,21 @@ var GameSDK = (() => {
|
|
|
184
275
|
};
|
|
185
276
|
return sdk;
|
|
186
277
|
}
|
|
278
|
+
function resolveRuntimeConfig(config) {
|
|
279
|
+
let injected = {};
|
|
280
|
+
try {
|
|
281
|
+
injected = typeof __GAME_CONFIG__ !== "undefined" ? __GAME_CONFIG__ : {};
|
|
282
|
+
} catch {
|
|
283
|
+
injected = {};
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
gameId: config.gameId ?? injected.gameId,
|
|
287
|
+
baseUrl: config.baseUrl ?? injected.baseUrl ?? "",
|
|
288
|
+
accessKey: config.accessKey ?? injected.accessKey,
|
|
289
|
+
secretKey: config.secretKey ?? injected.secretKey,
|
|
290
|
+
mock: config.mock
|
|
291
|
+
};
|
|
292
|
+
}
|
|
187
293
|
var mockScore = 0;
|
|
188
294
|
var mockSaves = {};
|
|
189
295
|
var mockToken = null;
|