@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 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,10 +5,25 @@
5
5
  type Platform = 'wx' | 'tt' | 'guest';
6
6
  /** SDK 配置 */
7
7
  interface GameSdkConfig {
8
- /** 游戏 ID(在平台后台登记) */
9
- gameId: string;
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({ gameId: 'merge-game', baseUrl: 'https://api.example.com' });
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({ gameId: 'merge-game', baseUrl: '...' });
102
+ * const sdk = GameSDK.createGameSdk({ baseUrl: '...' });
88
103
  *
89
104
  * 用法(模拟模式,不连服务端):
90
- * const sdk = createGameSdk({ gameId: 'demo', baseUrl: '', mock: true });
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
- /** 游戏 ID(在平台后台登记) */
9
- gameId: string;
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({ gameId: 'merge-game', baseUrl: 'https://api.example.com' });
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({ gameId: 'merge-game', baseUrl: '...' });
102
+ * const sdk = GameSDK.createGameSdk({ baseUrl: '...' });
88
103
  *
89
104
  * 用法(模拟模式,不连服务端):
90
- * const sdk = createGameSdk({ gameId: 'demo', baseUrl: '', mock: true });
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 {
@@ -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 apiUrl = `${config.baseUrl}/game-api/${config.gameId}`;
126
- const tokenKey = `gf_token_${config.gameId}`;
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 (config.mock) {
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;