@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 CHANGED
@@ -1,179 +1,179 @@
1
- # @zhimakechuang/game-sdk
2
-
3
- 通用游戏 SDK — 封装 [`apps/game-server-shared`](https://github.com/Chuang0516/game-factory/tree/main/apps/game-server-shared) 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
-
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 @zhimakechuang/game-sdk
17
+ npm install xiangsuhuabu
18
18
  # 或
19
- pnpm add @zhimakechuang/game-sdk
19
+ pnpm add xiangsuhuabu
20
20
  # 或
21
- yarn add @zhimakechuang/game-sdk
22
- ```
23
-
24
- ## 快速开始
25
-
26
- ### 工程化项目(npm 安装)
27
-
28
- ```ts
29
- import { createGameSdk } from '@zhimakechuang/game-sdk';
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/@zhimakechuang/game-sdk"></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`](https://github.com/Chuang0516/game-factory/tree/main/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
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
- /** 游戏 ID(在平台后台登记) */
9
- gameId: string;
10
- /** api-server 根地址(如 https://api.example.com) */
11
- baseUrl: string;
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
- * @game-factory/game-sdk
92
+ * @zhimakechuang/game-sdk
76
93
  *
77
- * 通用游戏 SDK,封装 game-server-shared 所有 HTTP 接口。
94
+ * game-factory 平台专属游戏 SDK,封装 game-server 所有 HTTP 接口。
78
95
  *
79
- * 用法(工程化):
80
- * import { createGameSdk } from '@game-factory/game-sdk';
81
- * const sdk = createGameSdk({ gameId: 'merge-game', baseUrl: 'https://api.example.com' });
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://cdn.example.com/game-sdk/index.umd.js"></script>
87
- * const sdk = GameSDK.createGameSdk({ gameId: 'merge-game', baseUrl: '...' });
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({ gameId: 'demo', baseUrl: '', mock: true });
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
- /** 游戏 ID(在平台后台登记) */
9
- gameId: string;
10
- /** api-server 根地址(如 https://api.example.com) */
11
- baseUrl: string;
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
- * @game-factory/game-sdk
92
+ * @zhimakechuang/game-sdk
76
93
  *
77
- * 通用游戏 SDK,封装 game-server-shared 所有 HTTP 接口。
94
+ * game-factory 平台专属游戏 SDK,封装 game-server 所有 HTTP 接口。
78
95
  *
79
- * 用法(工程化):
80
- * import { createGameSdk } from '@game-factory/game-sdk';
81
- * const sdk = createGameSdk({ gameId: 'merge-game', baseUrl: 'https://api.example.com' });
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://cdn.example.com/game-sdk/index.umd.js"></script>
87
- * const sdk = GameSDK.createGameSdk({ gameId: 'merge-game', baseUrl: '...' });
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({ gameId: 'demo', baseUrl: '', mock: true });
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 {
@@ -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
- const apiUrl = `${config.baseUrl}/game-api/${config.gameId}`;
126
- const tokenKey = `gf_token_${config.gameId}`;
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;