@zhimakechuang/game-sdk 1.0.0 → 1.2.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 +40 -14
- package/dist/index.d.ts +40 -14
- package/dist/index.global.js +108 -2
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +102 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +109 -2
- 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,11 +5,28 @@
|
|
|
5
5
|
type Platform = 'wx' | 'tt' | 'guest';
|
|
6
6
|
/** SDK 配置 */
|
|
7
7
|
interface GameSdkConfig {
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
/**
|
|
9
|
+
* 模板 ID(模板开发者必填)。
|
|
10
|
+
* 标识当前开发的是哪个模板,由平台在模板创建时生成。
|
|
11
|
+
*/
|
|
12
|
+
templateId: string;
|
|
13
|
+
/**
|
|
14
|
+
* 应用标识(AccessKey),用于鉴权。
|
|
15
|
+
* 由平台颁发给入驻开发者,必须在 SDK 初始化时传入。
|
|
16
|
+
* 未入驻的开发者无法获取,从而阻止未授权调用。
|
|
17
|
+
*/
|
|
18
|
+
accessKey: string;
|
|
19
|
+
/**
|
|
20
|
+
* 应用密钥(SecretKey),用于请求签名。
|
|
21
|
+
* 由平台颁发给入驻开发者,必须在 SDK 初始化时传入。
|
|
22
|
+
* ⚠️ 此值不会出现在网络请求中,仅用于本地计算签名。
|
|
23
|
+
*/
|
|
24
|
+
secretKey: string;
|
|
25
|
+
/**
|
|
26
|
+
* 模拟模式(不连服务端,返回假数据,用于模板开发)。
|
|
27
|
+
* mock 模式下仍需传入 templateId/accessKey/secretKey,
|
|
28
|
+
* 但不会发起真实网络请求。
|
|
29
|
+
*/
|
|
13
30
|
mock?: boolean;
|
|
14
31
|
}
|
|
15
32
|
interface LoginOptions {
|
|
@@ -72,22 +89,31 @@ interface SaveEntry {
|
|
|
72
89
|
}
|
|
73
90
|
|
|
74
91
|
/**
|
|
75
|
-
* @
|
|
92
|
+
* @zhimakechuang/game-sdk
|
|
76
93
|
*
|
|
77
|
-
*
|
|
94
|
+
* game-factory 平台专属游戏 SDK,封装 game-server 所有 HTTP 接口。
|
|
78
95
|
*
|
|
79
|
-
*
|
|
80
|
-
* import { createGameSdk } from '@
|
|
81
|
-
* const sdk = createGameSdk({
|
|
96
|
+
* 用法(模板开发 + 生产运行统一入口):
|
|
97
|
+
* import { createGameSdk } from '@zhimakechuang/game-sdk';
|
|
98
|
+
* const sdk = createGameSdk({
|
|
99
|
+
* templateId: 'tpl_xxx', // 模板 ID
|
|
100
|
+
* accessKey: 'ak_tpl_xxx', // 平台入驻后颁发的凭证
|
|
101
|
+
* secretKey: 'sk_tpl_xxx', // 平台入驻后颁发的密钥
|
|
102
|
+
* });
|
|
82
103
|
* await sdk.auth.login({ code: 'xxx', platform: 'wx' });
|
|
83
104
|
* await sdk.score.submit({ score: 1000 });
|
|
84
105
|
*
|
|
85
106
|
* 用法(CDN):
|
|
86
|
-
* <script src="https://
|
|
87
|
-
* const sdk = GameSDK.createGameSdk({
|
|
107
|
+
* <script src="https://unpkg.com/@zhimakechuang/game-sdk/dist/index.global.js"></script>
|
|
108
|
+
* const sdk = GameSDK.createGameSdk({ templateId, accessKey, secretKey });
|
|
88
109
|
*
|
|
89
|
-
*
|
|
90
|
-
* const sdk = createGameSdk({
|
|
110
|
+
* 用法(模拟模式,模板开发调试,不连服务端):
|
|
111
|
+
* const sdk = createGameSdk({ templateId, accessKey, secretKey, mock: true });
|
|
112
|
+
*
|
|
113
|
+
* 鉴权说明:
|
|
114
|
+
* - accessKey/secretKey 必须传入,只有平台入驻开发者才能获取
|
|
115
|
+
* - gameId 由平台构建时注入到 __GAME_CONFIG__,SDK 自动读取,开发者无需传入
|
|
116
|
+
* - baseUrl 内置默认值,指向平台 API 服务
|
|
91
117
|
*/
|
|
92
118
|
|
|
93
119
|
declare class GameSdkError extends Error {
|
package/dist/index.d.ts
CHANGED
|
@@ -5,11 +5,28 @@
|
|
|
5
5
|
type Platform = 'wx' | 'tt' | 'guest';
|
|
6
6
|
/** SDK 配置 */
|
|
7
7
|
interface GameSdkConfig {
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
/**
|
|
9
|
+
* 模板 ID(模板开发者必填)。
|
|
10
|
+
* 标识当前开发的是哪个模板,由平台在模板创建时生成。
|
|
11
|
+
*/
|
|
12
|
+
templateId: string;
|
|
13
|
+
/**
|
|
14
|
+
* 应用标识(AccessKey),用于鉴权。
|
|
15
|
+
* 由平台颁发给入驻开发者,必须在 SDK 初始化时传入。
|
|
16
|
+
* 未入驻的开发者无法获取,从而阻止未授权调用。
|
|
17
|
+
*/
|
|
18
|
+
accessKey: string;
|
|
19
|
+
/**
|
|
20
|
+
* 应用密钥(SecretKey),用于请求签名。
|
|
21
|
+
* 由平台颁发给入驻开发者,必须在 SDK 初始化时传入。
|
|
22
|
+
* ⚠️ 此值不会出现在网络请求中,仅用于本地计算签名。
|
|
23
|
+
*/
|
|
24
|
+
secretKey: string;
|
|
25
|
+
/**
|
|
26
|
+
* 模拟模式(不连服务端,返回假数据,用于模板开发)。
|
|
27
|
+
* mock 模式下仍需传入 templateId/accessKey/secretKey,
|
|
28
|
+
* 但不会发起真实网络请求。
|
|
29
|
+
*/
|
|
13
30
|
mock?: boolean;
|
|
14
31
|
}
|
|
15
32
|
interface LoginOptions {
|
|
@@ -72,22 +89,31 @@ interface SaveEntry {
|
|
|
72
89
|
}
|
|
73
90
|
|
|
74
91
|
/**
|
|
75
|
-
* @
|
|
92
|
+
* @zhimakechuang/game-sdk
|
|
76
93
|
*
|
|
77
|
-
*
|
|
94
|
+
* game-factory 平台专属游戏 SDK,封装 game-server 所有 HTTP 接口。
|
|
78
95
|
*
|
|
79
|
-
*
|
|
80
|
-
* import { createGameSdk } from '@
|
|
81
|
-
* const sdk = createGameSdk({
|
|
96
|
+
* 用法(模板开发 + 生产运行统一入口):
|
|
97
|
+
* import { createGameSdk } from '@zhimakechuang/game-sdk';
|
|
98
|
+
* const sdk = createGameSdk({
|
|
99
|
+
* templateId: 'tpl_xxx', // 模板 ID
|
|
100
|
+
* accessKey: 'ak_tpl_xxx', // 平台入驻后颁发的凭证
|
|
101
|
+
* secretKey: 'sk_tpl_xxx', // 平台入驻后颁发的密钥
|
|
102
|
+
* });
|
|
82
103
|
* await sdk.auth.login({ code: 'xxx', platform: 'wx' });
|
|
83
104
|
* await sdk.score.submit({ score: 1000 });
|
|
84
105
|
*
|
|
85
106
|
* 用法(CDN):
|
|
86
|
-
* <script src="https://
|
|
87
|
-
* const sdk = GameSDK.createGameSdk({
|
|
107
|
+
* <script src="https://unpkg.com/@zhimakechuang/game-sdk/dist/index.global.js"></script>
|
|
108
|
+
* const sdk = GameSDK.createGameSdk({ templateId, accessKey, secretKey });
|
|
88
109
|
*
|
|
89
|
-
*
|
|
90
|
-
* const sdk = createGameSdk({
|
|
110
|
+
* 用法(模拟模式,模板开发调试,不连服务端):
|
|
111
|
+
* const sdk = createGameSdk({ templateId, accessKey, secretKey, mock: true });
|
|
112
|
+
*
|
|
113
|
+
* 鉴权说明:
|
|
114
|
+
* - accessKey/secretKey 必须传入,只有平台入驻开发者才能获取
|
|
115
|
+
* - gameId 由平台构建时注入到 __GAME_CONFIG__,SDK 自动读取,开发者无需传入
|
|
116
|
+
* - baseUrl 内置默认值,指向平台 API 服务
|
|
91
117
|
*/
|
|
92
118
|
|
|
93
119
|
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,27 @@ var GameSDK = (() => {
|
|
|
122
191
|
}
|
|
123
192
|
};
|
|
124
193
|
function createGameSdk(config) {
|
|
125
|
-
|
|
126
|
-
|
|
194
|
+
if (!config.templateId) {
|
|
195
|
+
throw new GameSdkError(
|
|
196
|
+
"\u7F3A\u5C11 templateId\uFF1A\u8BF7\u5728\u5E73\u53F0\u521B\u5EFA\u6A21\u677F\u540E\u83B7\u53D6\u6A21\u677F ID",
|
|
197
|
+
400
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
if (!config.accessKey || !config.secretKey) {
|
|
201
|
+
throw new GameSdkError(
|
|
202
|
+
"\u7F3A\u5C11 accessKey/secretKey\uFF1A\u8BF7\u786E\u8BA4\u5DF2\u5728\u5E73\u53F0\u5165\u9A7B\u5E76\u83B7\u53D6\u5F00\u53D1\u8005\u51ED\u8BC1",
|
|
203
|
+
400
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
const { gameId, baseUrl } = resolveInjectedConfig();
|
|
207
|
+
if (!config.mock && !gameId) {
|
|
208
|
+
throw new GameSdkError(
|
|
209
|
+
"\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",
|
|
210
|
+
400
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
const apiUrl = gameId ? `${baseUrl}/game-api/${gameId}` : "";
|
|
214
|
+
const tokenKey = `gf_token_${gameId ?? "mock"}`;
|
|
127
215
|
let token = storageGet(tokenKey);
|
|
128
216
|
function getToken() {
|
|
129
217
|
return token;
|
|
@@ -138,6 +226,13 @@ var GameSDK = (() => {
|
|
|
138
226
|
return mockResponse(method, path, body);
|
|
139
227
|
}
|
|
140
228
|
const headers = {};
|
|
229
|
+
const signHeaders = buildSignHeaders(
|
|
230
|
+
method,
|
|
231
|
+
path,
|
|
232
|
+
body,
|
|
233
|
+
{ accessKey: config.accessKey, secretKey: config.secretKey }
|
|
234
|
+
);
|
|
235
|
+
Object.assign(headers, signHeaders);
|
|
141
236
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
142
237
|
const res = await request({
|
|
143
238
|
url: `${apiUrl}${path}`,
|
|
@@ -184,6 +279,17 @@ var GameSDK = (() => {
|
|
|
184
279
|
};
|
|
185
280
|
return sdk;
|
|
186
281
|
}
|
|
282
|
+
function resolveInjectedConfig() {
|
|
283
|
+
try {
|
|
284
|
+
const injected = typeof __GAME_CONFIG__ !== "undefined" ? __GAME_CONFIG__ : {};
|
|
285
|
+
return {
|
|
286
|
+
gameId: injected.gameId,
|
|
287
|
+
baseUrl: injected.baseUrl ?? ""
|
|
288
|
+
};
|
|
289
|
+
} catch {
|
|
290
|
+
return { gameId: void 0, baseUrl: "" };
|
|
291
|
+
}
|
|
292
|
+
}
|
|
187
293
|
var mockScore = 0;
|
|
188
294
|
var mockSaves = {};
|
|
189
295
|
var mockToken = null;
|