connectbase-client 3.13.1 → 3.14.1
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/CHANGELOG.md +96 -0
- package/README.md +13 -1
- package/dist/connect-base.umd.js +4 -4
- package/dist/index.d.mts +182 -10
- package/dist/index.d.ts +182 -10
- package/dist/index.js +277 -78
- package/dist/index.mjs +276 -78
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -14,6 +14,20 @@ var AuthError = class extends Error {
|
|
|
14
14
|
this.name = "AuthError";
|
|
15
15
|
}
|
|
16
16
|
};
|
|
17
|
+
var GameError = class extends Error {
|
|
18
|
+
constructor(init) {
|
|
19
|
+
super(init.message || init.code || "GameError");
|
|
20
|
+
this.name = "GameError";
|
|
21
|
+
this.code = init.code || "UNKNOWN";
|
|
22
|
+
this.phase = init.phase;
|
|
23
|
+
this.feature = init.feature;
|
|
24
|
+
this.roomId = init.roomId;
|
|
25
|
+
this.scriptId = init.scriptId;
|
|
26
|
+
this.originClientId = init.originClientId;
|
|
27
|
+
this.requested = init.requested;
|
|
28
|
+
this.available = init.available;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
17
31
|
|
|
18
32
|
// src/core/abort.ts
|
|
19
33
|
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -6235,6 +6249,21 @@ var GameConfigAPI = class {
|
|
|
6235
6249
|
};
|
|
6236
6250
|
|
|
6237
6251
|
// src/api/game.ts
|
|
6252
|
+
function parseGameError(msg) {
|
|
6253
|
+
const payload = msg.data ?? msg;
|
|
6254
|
+
const get = (k) => payload[k];
|
|
6255
|
+
return new GameError({
|
|
6256
|
+
code: get("code") || msg.code,
|
|
6257
|
+
message: get("message") || "Unknown error",
|
|
6258
|
+
phase: get("phase") || msg.phase,
|
|
6259
|
+
feature: get("feature") || msg.feature,
|
|
6260
|
+
roomId: get("room_id") || msg.room_id,
|
|
6261
|
+
scriptId: get("script_id") || msg.script_id,
|
|
6262
|
+
originClientId: get("origin_client_id") || msg.origin_client_id,
|
|
6263
|
+
requested: get("requested") || msg.requested,
|
|
6264
|
+
available: get("available") || msg.available
|
|
6265
|
+
});
|
|
6266
|
+
}
|
|
6238
6267
|
var getDefaultGameServerUrl = () => {
|
|
6239
6268
|
if (typeof window !== "undefined") {
|
|
6240
6269
|
const hostname = window.location.hostname;
|
|
@@ -6264,6 +6293,8 @@ var GameRoom = class {
|
|
|
6264
6293
|
this.actionSequence = 0;
|
|
6265
6294
|
this._roomId = null;
|
|
6266
6295
|
this._state = null;
|
|
6296
|
+
this._scriptName = null;
|
|
6297
|
+
this._scriptVersion = null;
|
|
6267
6298
|
this._isConnected = false;
|
|
6268
6299
|
this.msgIdCounter = 0;
|
|
6269
6300
|
this.config = {
|
|
@@ -6353,21 +6384,48 @@ var GameRoom = class {
|
|
|
6353
6384
|
}
|
|
6354
6385
|
this._isConnected = false;
|
|
6355
6386
|
this._roomId = null;
|
|
6387
|
+
this._scriptName = null;
|
|
6388
|
+
this._scriptVersion = null;
|
|
6356
6389
|
}
|
|
6357
6390
|
/**
|
|
6358
|
-
* 룸 생성
|
|
6391
|
+
* 룸 생성 (호환 시그니처) — 초기 상태만 반환.
|
|
6392
|
+
*
|
|
6393
|
+
* Attached script 의 검증(scriptName/scriptVersion echo 검사) 이 필요하면
|
|
6394
|
+
* 인스턴스 getter (`room.scriptName`, `room.scriptVersion`) 를 함께 사용하거나,
|
|
6395
|
+
* 메타까지 한 번에 받고 싶다면 {@link createRoomDetailed} 를 사용한다.
|
|
6396
|
+
*
|
|
6397
|
+
* 미존재/비활성 scriptName 은 서버가 `SCRIPT_NOT_FOUND` 로 즉시 reject 한다.
|
|
6398
|
+
* reject 값은 `GameError` 인스턴스이며 `.code`/`.requested`/`.available` 로 분기 가능.
|
|
6359
6399
|
*/
|
|
6360
6400
|
createRoom(config = {}) {
|
|
6401
|
+
return this.createRoomDetailed(config).then((r) => r.state);
|
|
6402
|
+
}
|
|
6403
|
+
/**
|
|
6404
|
+
* 룸 생성 + 메타 — server 가 attach 한 lua script 이름/버전을 함께 반환.
|
|
6405
|
+
*
|
|
6406
|
+
* @example
|
|
6407
|
+
* const { state, scriptName, scriptVersion } = await room.createRoomDetailed({ scriptName: 'njb-main' })
|
|
6408
|
+
* if (scriptName !== 'njb-main') throw new Error('script mismatch')
|
|
6409
|
+
* console.log('attached', scriptName, 'v', scriptVersion)
|
|
6410
|
+
*/
|
|
6411
|
+
createRoomDetailed(config = {}) {
|
|
6361
6412
|
return new Promise((resolve, reject) => {
|
|
6362
6413
|
const handler = (msg) => {
|
|
6363
6414
|
if (msg.type === "room_created") {
|
|
6364
6415
|
const data = msg.data;
|
|
6365
6416
|
this._roomId = data.room_id;
|
|
6366
6417
|
this._state = data.initial_state;
|
|
6367
|
-
|
|
6418
|
+
this._scriptName = data.script_name ?? null;
|
|
6419
|
+
this._scriptVersion = data.script_version ?? null;
|
|
6420
|
+
resolve({
|
|
6421
|
+
roomId: data.room_id,
|
|
6422
|
+
state: data.initial_state,
|
|
6423
|
+
scriptName: data.script_name,
|
|
6424
|
+
scriptVersion: data.script_version
|
|
6425
|
+
});
|
|
6368
6426
|
return true;
|
|
6369
6427
|
} else if (msg.type === "error") {
|
|
6370
|
-
reject(
|
|
6428
|
+
reject(parseGameError(msg));
|
|
6371
6429
|
return true;
|
|
6372
6430
|
}
|
|
6373
6431
|
return false;
|
|
@@ -6375,6 +6433,14 @@ var GameRoom = class {
|
|
|
6375
6433
|
this.sendWithHandler("create_room", toCreateRoomWire(config), handler, 15e3, reject);
|
|
6376
6434
|
});
|
|
6377
6435
|
}
|
|
6436
|
+
/** Attached lua script 이름 — createRoom 이후 server 가 echo 한 값. */
|
|
6437
|
+
get scriptName() {
|
|
6438
|
+
return this._scriptName;
|
|
6439
|
+
}
|
|
6440
|
+
/** Attached lua script 의 active version. */
|
|
6441
|
+
get scriptVersion() {
|
|
6442
|
+
return this._scriptVersion;
|
|
6443
|
+
}
|
|
6378
6444
|
/**
|
|
6379
6445
|
* 룸 참가
|
|
6380
6446
|
*/
|
|
@@ -6385,10 +6451,12 @@ var GameRoom = class {
|
|
|
6385
6451
|
const data = msg.data;
|
|
6386
6452
|
this._roomId = data.room_id;
|
|
6387
6453
|
this._state = data.initial_state;
|
|
6454
|
+
this._scriptName = null;
|
|
6455
|
+
this._scriptVersion = null;
|
|
6388
6456
|
resolve(data.initial_state);
|
|
6389
6457
|
return true;
|
|
6390
6458
|
} else if (msg.type === "error") {
|
|
6391
|
-
reject(
|
|
6459
|
+
reject(parseGameError(msg));
|
|
6392
6460
|
return true;
|
|
6393
6461
|
}
|
|
6394
6462
|
return false;
|
|
@@ -6402,17 +6470,19 @@ var GameRoom = class {
|
|
|
6402
6470
|
leaveRoom() {
|
|
6403
6471
|
return new Promise((resolve, reject) => {
|
|
6404
6472
|
if (!this._roomId) {
|
|
6405
|
-
reject(new
|
|
6473
|
+
reject(new GameError({ code: "NOT_IN_ROOM", message: "Not in a room" }));
|
|
6406
6474
|
return;
|
|
6407
6475
|
}
|
|
6408
6476
|
const handler = (msg) => {
|
|
6409
6477
|
if (msg.type === "room_left") {
|
|
6410
6478
|
this._roomId = null;
|
|
6411
6479
|
this._state = null;
|
|
6480
|
+
this._scriptName = null;
|
|
6481
|
+
this._scriptVersion = null;
|
|
6412
6482
|
resolve();
|
|
6413
6483
|
return true;
|
|
6414
6484
|
} else if (msg.type === "error") {
|
|
6415
|
-
reject(
|
|
6485
|
+
reject(parseGameError(msg));
|
|
6416
6486
|
return true;
|
|
6417
6487
|
}
|
|
6418
6488
|
return false;
|
|
@@ -6425,7 +6495,7 @@ var GameRoom = class {
|
|
|
6425
6495
|
*/
|
|
6426
6496
|
sendAction(action) {
|
|
6427
6497
|
if (!this._roomId) {
|
|
6428
|
-
throw new
|
|
6498
|
+
throw new GameError({ code: "NOT_IN_ROOM", message: "Not in a room" });
|
|
6429
6499
|
}
|
|
6430
6500
|
this.send("action", {
|
|
6431
6501
|
type: action.type,
|
|
@@ -6439,7 +6509,7 @@ var GameRoom = class {
|
|
|
6439
6509
|
*/
|
|
6440
6510
|
sendChat(message) {
|
|
6441
6511
|
if (!this._roomId) {
|
|
6442
|
-
throw new
|
|
6512
|
+
throw new GameError({ code: "NOT_IN_ROOM", message: "Not in a room" });
|
|
6443
6513
|
}
|
|
6444
6514
|
this.send("chat", { message });
|
|
6445
6515
|
}
|
|
@@ -6449,7 +6519,7 @@ var GameRoom = class {
|
|
|
6449
6519
|
requestState() {
|
|
6450
6520
|
return new Promise((resolve, reject) => {
|
|
6451
6521
|
if (!this._roomId) {
|
|
6452
|
-
reject(new
|
|
6522
|
+
reject(new GameError({ code: "NOT_IN_ROOM", message: "Not in a room" }));
|
|
6453
6523
|
return;
|
|
6454
6524
|
}
|
|
6455
6525
|
const handler = (msg) => {
|
|
@@ -6459,7 +6529,7 @@ var GameRoom = class {
|
|
|
6459
6529
|
resolve(state);
|
|
6460
6530
|
return true;
|
|
6461
6531
|
} else if (msg.type === "error") {
|
|
6462
|
-
reject(
|
|
6532
|
+
reject(parseGameError(msg));
|
|
6463
6533
|
return true;
|
|
6464
6534
|
}
|
|
6465
6535
|
return false;
|
|
@@ -6478,7 +6548,7 @@ var GameRoom = class {
|
|
|
6478
6548
|
resolve(data.rooms);
|
|
6479
6549
|
return true;
|
|
6480
6550
|
} else if (msg.type === "error") {
|
|
6481
|
-
reject(
|
|
6551
|
+
reject(parseGameError(msg));
|
|
6482
6552
|
return true;
|
|
6483
6553
|
}
|
|
6484
6554
|
return false;
|
|
@@ -6500,7 +6570,7 @@ var GameRoom = class {
|
|
|
6500
6570
|
resolve(rtt);
|
|
6501
6571
|
return true;
|
|
6502
6572
|
} else if (msg.type === "error") {
|
|
6503
|
-
reject(
|
|
6573
|
+
reject(parseGameError(msg));
|
|
6504
6574
|
return true;
|
|
6505
6575
|
}
|
|
6506
6576
|
return false;
|
|
@@ -6558,12 +6628,12 @@ var GameRoom = class {
|
|
|
6558
6628
|
this.ws?.addEventListener("message", messageHandler);
|
|
6559
6629
|
timeoutId = setTimeout(() => {
|
|
6560
6630
|
cleanup();
|
|
6561
|
-
const err = new
|
|
6562
|
-
onError?.(err);
|
|
6563
|
-
this.handlers.onError?.({
|
|
6631
|
+
const err = new GameError({
|
|
6564
6632
|
code: "TIMEOUT",
|
|
6565
|
-
message:
|
|
6633
|
+
message: `Request '${type}' timed out after ${timeoutMs}ms`
|
|
6566
6634
|
});
|
|
6635
|
+
onError?.(err);
|
|
6636
|
+
this.handlers.onError?.(err);
|
|
6567
6637
|
}, timeoutMs);
|
|
6568
6638
|
try {
|
|
6569
6639
|
this.send(type, data, msgId);
|
|
@@ -6597,10 +6667,7 @@ var GameRoom = class {
|
|
|
6597
6667
|
});
|
|
6598
6668
|
break;
|
|
6599
6669
|
case "error":
|
|
6600
|
-
this.handlers.onError?.(
|
|
6601
|
-
code: msg.code || "UNKNOWN",
|
|
6602
|
-
message: msg.message || "Unknown error"
|
|
6603
|
-
});
|
|
6670
|
+
this.handlers.onError?.(parseGameError(msg));
|
|
6604
6671
|
break;
|
|
6605
6672
|
default:
|
|
6606
6673
|
break;
|
|
@@ -6678,10 +6745,10 @@ var GameRoom = class {
|
|
|
6678
6745
|
scheduleReconnect(roomId) {
|
|
6679
6746
|
if (this.reconnectAttempts >= (this.config.maxReconnectAttempts ?? 5)) {
|
|
6680
6747
|
console.error("Max reconnect attempts reached");
|
|
6681
|
-
this.handlers.onError?.({
|
|
6748
|
+
this.handlers.onError?.(new GameError({
|
|
6682
6749
|
code: "MAX_RECONNECT_ATTEMPTS",
|
|
6683
6750
|
message: "Maximum reconnection attempts reached"
|
|
6684
|
-
});
|
|
6751
|
+
}));
|
|
6685
6752
|
return;
|
|
6686
6753
|
}
|
|
6687
6754
|
const delay = Math.min(
|
|
@@ -6702,10 +6769,11 @@ var GameRoom = class {
|
|
|
6702
6769
|
console.log(`Successfully rejoined room ${previousRoomId}`);
|
|
6703
6770
|
} catch (rejoinError) {
|
|
6704
6771
|
console.error(`Failed to rejoin room ${previousRoomId}:`, rejoinError);
|
|
6705
|
-
this.handlers.onError?.({
|
|
6772
|
+
this.handlers.onError?.(new GameError({
|
|
6706
6773
|
code: "REJOIN_FAILED",
|
|
6707
|
-
message: `Failed to rejoin room: ${rejoinError}
|
|
6708
|
-
|
|
6774
|
+
message: `Failed to rejoin room: ${rejoinError}`,
|
|
6775
|
+
roomId: previousRoomId
|
|
6776
|
+
}));
|
|
6709
6777
|
}
|
|
6710
6778
|
}
|
|
6711
6779
|
} catch {
|
|
@@ -6914,6 +6982,38 @@ var GameAPI = class {
|
|
|
6914
6982
|
}
|
|
6915
6983
|
return headers;
|
|
6916
6984
|
}
|
|
6985
|
+
// game-server (`game.connectbase.world`) 전용 fetch.
|
|
6986
|
+
//
|
|
6987
|
+
// 회귀 가드 (v3.14.1, NJB report 2026-05-14): matchqueue / leaderboard / scripts 16개
|
|
6988
|
+
// 메서드가 v3.0.0 BREAKING 이후 `this.http.*` 를 통해 기본 baseUrl (=core-server,
|
|
6989
|
+
// `api.connectbase.world`) 로 라우팅되면서 404 회귀. 호스트 분리된 game-server 경로는
|
|
6990
|
+
// 반드시 `this.gameServerUrl` 을 사용해야 한다. 회귀 재발 방지 테스트:
|
|
6991
|
+
// test/game-host-routing.test.ts.
|
|
6992
|
+
async gameFetch(method, path, body, errCode = "GAME_REQUEST_FAILED") {
|
|
6993
|
+
const init = { method, headers: { ...this.getHeaders() } };
|
|
6994
|
+
if (body !== void 0) {
|
|
6995
|
+
init.headers["Content-Type"] = "application/json";
|
|
6996
|
+
init.body = JSON.stringify(body);
|
|
6997
|
+
}
|
|
6998
|
+
const response = await fetch(`${this.gameServerUrl}${path}`, init);
|
|
6999
|
+
if (!response.ok) {
|
|
7000
|
+
let serverCode = errCode;
|
|
7001
|
+
let message = `${method} ${path} failed: ${response.statusText}`;
|
|
7002
|
+
try {
|
|
7003
|
+
const data = await response.json();
|
|
7004
|
+
if (data && typeof data.error === "string") {
|
|
7005
|
+
message = data.error;
|
|
7006
|
+
serverCode = data.error;
|
|
7007
|
+
}
|
|
7008
|
+
} catch {
|
|
7009
|
+
}
|
|
7010
|
+
throw new ApiError(response.status, message, serverCode);
|
|
7011
|
+
}
|
|
7012
|
+
if (response.status === 204) return void 0;
|
|
7013
|
+
const contentType = response.headers.get("content-type") || "";
|
|
7014
|
+
if (!contentType.includes("application/json")) return void 0;
|
|
7015
|
+
return response.json();
|
|
7016
|
+
}
|
|
6917
7017
|
// ─────────────────────────────────────────────────────────────────────
|
|
6918
7018
|
// Phase 3 — Matchqueue primitive (mechanism only)
|
|
6919
7019
|
//
|
|
@@ -6934,9 +7034,11 @@ var GameAPI = class {
|
|
|
6934
7034
|
* @constraints appId/queueKey/ticketId 는 [a-zA-Z0-9_-] 만 허용. ttlSec=0 이면 만료 없음.
|
|
6935
7035
|
*/
|
|
6936
7036
|
async enqueueMatch(appId, queueKey, ticketId, attributes, ttlSec) {
|
|
6937
|
-
return this.
|
|
7037
|
+
return this.gameFetch(
|
|
7038
|
+
"POST",
|
|
6938
7039
|
`/v1/game/${appId}/matchqueue/${queueKey}/tickets`,
|
|
6939
|
-
{ ticket_id: ticketId, attributes, ttl_sec: ttlSec ?? 0 }
|
|
7040
|
+
{ ticket_id: ticketId, attributes, ttl_sec: ttlSec ?? 0 },
|
|
7041
|
+
"GAME_ENQUEUE_MATCH_FAILED"
|
|
6940
7042
|
);
|
|
6941
7043
|
}
|
|
6942
7044
|
/**
|
|
@@ -6945,16 +7047,22 @@ var GameAPI = class {
|
|
|
6945
7047
|
* @example const tickets = await cb.game.listMatchqueue(appId, "ranked")
|
|
6946
7048
|
*/
|
|
6947
7049
|
async listMatchqueue(appId, queueKey) {
|
|
6948
|
-
return this.
|
|
6949
|
-
|
|
7050
|
+
return this.gameFetch(
|
|
7051
|
+
"GET",
|
|
7052
|
+
`/v1/game/${appId}/matchqueue/${queueKey}`,
|
|
7053
|
+
void 0,
|
|
7054
|
+
"GAME_LIST_MATCHQUEUE_FAILED"
|
|
6950
7055
|
);
|
|
6951
7056
|
}
|
|
6952
7057
|
/**
|
|
6953
7058
|
* 매칭 큐에서 ticket 제거 (예: 사용자가 매칭 취소).
|
|
6954
7059
|
*/
|
|
6955
7060
|
async cancelMatch(appId, queueKey, ticketId) {
|
|
6956
|
-
await this.
|
|
6957
|
-
|
|
7061
|
+
await this.gameFetch(
|
|
7062
|
+
"DELETE",
|
|
7063
|
+
`/v1/game/${appId}/matchqueue/${queueKey}/tickets/${ticketId}`,
|
|
7064
|
+
void 0,
|
|
7065
|
+
"GAME_CANCEL_MATCH_FAILED"
|
|
6958
7066
|
);
|
|
6959
7067
|
}
|
|
6960
7068
|
// ─────────────────────────────────────────────────────────────────────
|
|
@@ -6970,47 +7078,66 @@ var GameAPI = class {
|
|
|
6970
7078
|
* await cb.game.submitScore(appId, "global_elo", userId, 32, "incr")
|
|
6971
7079
|
*/
|
|
6972
7080
|
async submitScore(appId, leaderboardKey, member, score, mode = "set") {
|
|
6973
|
-
return this.
|
|
7081
|
+
return this.gameFetch(
|
|
7082
|
+
"POST",
|
|
6974
7083
|
`/v1/game/${appId}/leaderboards/${leaderboardKey}/scores`,
|
|
6975
|
-
{ member, score, mode }
|
|
7084
|
+
{ member, score, mode },
|
|
7085
|
+
"GAME_SUBMIT_SCORE_FAILED"
|
|
6976
7086
|
);
|
|
6977
7087
|
}
|
|
6978
7088
|
/**
|
|
6979
7089
|
* 상위 n 명 조회 (기본 10).
|
|
6980
7090
|
*/
|
|
6981
7091
|
async getTopScores(appId, leaderboardKey, n = 10) {
|
|
6982
|
-
return this.
|
|
6983
|
-
|
|
7092
|
+
return this.gameFetch(
|
|
7093
|
+
"GET",
|
|
7094
|
+
`/v1/game/${appId}/leaderboards/${leaderboardKey}/top?n=${n}`,
|
|
7095
|
+
void 0,
|
|
7096
|
+
"GAME_GET_TOP_SCORES_FAILED"
|
|
6984
7097
|
);
|
|
6985
7098
|
}
|
|
6986
7099
|
/**
|
|
6987
7100
|
* 단일 member 의 rank + score.
|
|
6988
7101
|
*/
|
|
6989
7102
|
async getMemberRank(appId, leaderboardKey, member) {
|
|
6990
|
-
return this.
|
|
6991
|
-
|
|
7103
|
+
return this.gameFetch(
|
|
7104
|
+
"GET",
|
|
7105
|
+
`/v1/game/${appId}/leaderboards/${leaderboardKey}/members/${member}`,
|
|
7106
|
+
void 0,
|
|
7107
|
+
"GAME_GET_MEMBER_RANK_FAILED"
|
|
6992
7108
|
);
|
|
6993
7109
|
}
|
|
6994
7110
|
/**
|
|
6995
7111
|
* member 주변 (위 above 명 + 본인 + 아래 below 명) 조회.
|
|
6996
7112
|
*/
|
|
6997
7113
|
async getRankAround(appId, leaderboardKey, member, above = 5, below = 5) {
|
|
6998
|
-
return this.
|
|
6999
|
-
|
|
7114
|
+
return this.gameFetch(
|
|
7115
|
+
"GET",
|
|
7116
|
+
`/v1/game/${appId}/leaderboards/${leaderboardKey}/around/${member}?above=${above}&below=${below}`,
|
|
7117
|
+
void 0,
|
|
7118
|
+
"GAME_GET_RANK_AROUND_FAILED"
|
|
7000
7119
|
);
|
|
7001
7120
|
}
|
|
7002
7121
|
/**
|
|
7003
7122
|
* leaderboard 전체 reset (시즌 종료 시).
|
|
7004
7123
|
*/
|
|
7005
7124
|
async resetLeaderboard(appId, leaderboardKey) {
|
|
7006
|
-
await this.
|
|
7125
|
+
await this.gameFetch(
|
|
7126
|
+
"DELETE",
|
|
7127
|
+
`/v1/game/${appId}/leaderboards/${leaderboardKey}`,
|
|
7128
|
+
void 0,
|
|
7129
|
+
"GAME_RESET_LEADERBOARD_FAILED"
|
|
7130
|
+
);
|
|
7007
7131
|
}
|
|
7008
7132
|
/**
|
|
7009
7133
|
* 특정 member 만 제거 (계정 삭제 등).
|
|
7010
7134
|
*/
|
|
7011
7135
|
async removeFromLeaderboard(appId, leaderboardKey, member) {
|
|
7012
|
-
await this.
|
|
7013
|
-
|
|
7136
|
+
await this.gameFetch(
|
|
7137
|
+
"DELETE",
|
|
7138
|
+
`/v1/game/${appId}/leaderboards/${leaderboardKey}/members/${member}`,
|
|
7139
|
+
void 0,
|
|
7140
|
+
"GAME_REMOVE_FROM_LEADERBOARD_FAILED"
|
|
7014
7141
|
);
|
|
7015
7142
|
}
|
|
7016
7143
|
// ─────────────────────────────────────────────────────────────────────
|
|
@@ -7025,54 +7152,100 @@ var GameAPI = class {
|
|
|
7025
7152
|
* await cb.game.uploadScript(appId, "ranked_br", fs.readFileSync("./ranked.lua", "utf-8"))
|
|
7026
7153
|
*/
|
|
7027
7154
|
async uploadScript(appId, name, code) {
|
|
7028
|
-
return this.
|
|
7155
|
+
return this.gameFetch(
|
|
7156
|
+
"POST",
|
|
7029
7157
|
`/v1/game/${appId}/scripts`,
|
|
7030
|
-
{ name, code }
|
|
7158
|
+
{ name, code },
|
|
7159
|
+
"GAME_UPLOAD_SCRIPT_FAILED"
|
|
7031
7160
|
);
|
|
7032
7161
|
}
|
|
7033
7162
|
/**
|
|
7034
7163
|
* app 의 모든 스크립트 메타데이터 목록.
|
|
7035
7164
|
*/
|
|
7036
7165
|
async listScripts(appId) {
|
|
7037
|
-
return this.
|
|
7166
|
+
return this.gameFetch(
|
|
7167
|
+
"GET",
|
|
7168
|
+
`/v1/game/${appId}/scripts`,
|
|
7169
|
+
void 0,
|
|
7170
|
+
"GAME_LIST_SCRIPTS_FAILED"
|
|
7171
|
+
);
|
|
7038
7172
|
}
|
|
7039
7173
|
/**
|
|
7040
7174
|
* 단일 스크립트 메타 + active version code.
|
|
7041
7175
|
*/
|
|
7042
7176
|
async getScript(appId, name) {
|
|
7043
|
-
return this.
|
|
7177
|
+
return this.gameFetch(
|
|
7178
|
+
"GET",
|
|
7179
|
+
`/v1/game/${appId}/scripts/${name}`,
|
|
7180
|
+
void 0,
|
|
7181
|
+
"GAME_GET_SCRIPT_FAILED"
|
|
7182
|
+
);
|
|
7044
7183
|
}
|
|
7045
7184
|
/**
|
|
7046
7185
|
* 단일 스크립트의 모든 버전 이력.
|
|
7047
7186
|
*/
|
|
7048
7187
|
async listScriptVersions(appId, name) {
|
|
7049
|
-
return this.
|
|
7050
|
-
|
|
7188
|
+
return this.gameFetch(
|
|
7189
|
+
"GET",
|
|
7190
|
+
`/v1/game/${appId}/scripts/${name}/versions`,
|
|
7191
|
+
void 0,
|
|
7192
|
+
"GAME_LIST_SCRIPT_VERSIONS_FAILED"
|
|
7051
7193
|
);
|
|
7052
7194
|
}
|
|
7053
7195
|
/**
|
|
7054
7196
|
* 특정 버전 활성화 (hot reload 자동). version=0 또는 미지정 → latest 활성화.
|
|
7055
7197
|
*/
|
|
7056
7198
|
async activateScript(appId, name, version) {
|
|
7057
|
-
return this.
|
|
7199
|
+
return this.gameFetch(
|
|
7200
|
+
"POST",
|
|
7058
7201
|
`/v1/game/${appId}/scripts/${name}/activate`,
|
|
7059
|
-
{ version: version ?? 0 }
|
|
7202
|
+
{ version: version ?? 0 },
|
|
7203
|
+
"GAME_ACTIVATE_SCRIPT_FAILED"
|
|
7060
7204
|
);
|
|
7061
7205
|
}
|
|
7062
7206
|
/**
|
|
7063
7207
|
* 직전 active 버전으로 롤백 (hot reload 자동).
|
|
7064
7208
|
*/
|
|
7065
7209
|
async rollbackScript(appId, name) {
|
|
7066
|
-
return this.
|
|
7210
|
+
return this.gameFetch(
|
|
7211
|
+
"POST",
|
|
7067
7212
|
`/v1/game/${appId}/scripts/${name}/rollback`,
|
|
7068
|
-
{}
|
|
7213
|
+
{},
|
|
7214
|
+
"GAME_ROLLBACK_SCRIPT_FAILED"
|
|
7069
7215
|
);
|
|
7070
7216
|
}
|
|
7071
7217
|
/**
|
|
7072
|
-
* 스크립트 비활성화 (status=disabled).
|
|
7218
|
+
* 스크립트 비활성화 (status=disabled, ActiveVersion=0). 코드/모든 버전은 보존되며
|
|
7219
|
+
* 이후 {@link activateScript} 로 재활성화할 수 있다.
|
|
7220
|
+
*
|
|
7221
|
+
* v3.14.0 BREAKING — 이전엔 `disableScript` 가 `DELETE /scripts/:name` 을 호출해
|
|
7222
|
+
* Disable 매핑이었으나, server 측 `DELETE` 가 hard-delete 로 분리됨에 따라
|
|
7223
|
+
* `POST /scripts/:name/deactivate` 로 endpoint 이동. 메서드명도 의도 명확화 위해 rename.
|
|
7073
7224
|
*/
|
|
7074
|
-
async
|
|
7075
|
-
await this.
|
|
7225
|
+
async deactivateScript(appId, name) {
|
|
7226
|
+
await this.gameFetch(
|
|
7227
|
+
"POST",
|
|
7228
|
+
`/v1/game/${appId}/scripts/${name}/deactivate`,
|
|
7229
|
+
{},
|
|
7230
|
+
"GAME_DEACTIVATE_SCRIPT_FAILED"
|
|
7231
|
+
);
|
|
7232
|
+
}
|
|
7233
|
+
/**
|
|
7234
|
+
* 스크립트 영구 삭제 (hard-delete) — 메타 + 모든 버전 영구 제거. 복구 불가.
|
|
7235
|
+
*
|
|
7236
|
+
* 잘못 업로드된 스크립트나 더 이상 사용하지 않는 슬롯을 정리할 때 사용. 단순 비활성화는
|
|
7237
|
+
* {@link deactivateScript} 를 사용한다 (코드 보존, 재활성화 가능).
|
|
7238
|
+
*
|
|
7239
|
+
* v3.14.0 신규 — server 측 `DELETE /scripts/:name` 의 의미가 Disable → hard-delete 로
|
|
7240
|
+
* 변경된 것에 대응.
|
|
7241
|
+
*/
|
|
7242
|
+
async deleteScript(appId, name) {
|
|
7243
|
+
await this.gameFetch(
|
|
7244
|
+
"DELETE",
|
|
7245
|
+
`/v1/game/${appId}/scripts/${name}`,
|
|
7246
|
+
void 0,
|
|
7247
|
+
"GAME_DELETE_SCRIPT_FAILED"
|
|
7248
|
+
);
|
|
7076
7249
|
}
|
|
7077
7250
|
};
|
|
7078
7251
|
|
|
@@ -9378,7 +9551,7 @@ var WebTransportTransport = class {
|
|
|
9378
9551
|
}
|
|
9379
9552
|
send(data, reliable = true) {
|
|
9380
9553
|
if (!this.transport) {
|
|
9381
|
-
throw new
|
|
9554
|
+
throw new GameError({ code: "NOT_CONNECTED", message: "Not connected" });
|
|
9382
9555
|
}
|
|
9383
9556
|
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
9384
9557
|
if (reliable) {
|
|
@@ -9469,7 +9642,7 @@ var WebSocketTransport = class {
|
|
|
9469
9642
|
}
|
|
9470
9643
|
send(data, _reliable) {
|
|
9471
9644
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
9472
|
-
throw new
|
|
9645
|
+
throw new GameError({ code: "NOT_CONNECTED", message: "Not connected" });
|
|
9473
9646
|
}
|
|
9474
9647
|
if (typeof data === "string") {
|
|
9475
9648
|
this.ws.send(data);
|
|
@@ -9494,6 +9667,8 @@ var GameRoomTransport = class {
|
|
|
9494
9667
|
this.actionSequence = 0;
|
|
9495
9668
|
this._roomId = null;
|
|
9496
9669
|
this._state = null;
|
|
9670
|
+
this._scriptName = null;
|
|
9671
|
+
this._scriptVersion = null;
|
|
9497
9672
|
this._isConnected = false;
|
|
9498
9673
|
this._connectionStatus = "disconnected";
|
|
9499
9674
|
this._lastError = null;
|
|
@@ -9546,6 +9721,14 @@ var GameRoomTransport = class {
|
|
|
9546
9721
|
get isConnected() {
|
|
9547
9722
|
return this._isConnected;
|
|
9548
9723
|
}
|
|
9724
|
+
/** Attached lua script 이름 — createRoom 이후 server 가 echo 한 값. */
|
|
9725
|
+
get scriptName() {
|
|
9726
|
+
return this._scriptName;
|
|
9727
|
+
}
|
|
9728
|
+
/** Attached lua script 의 active version. */
|
|
9729
|
+
get scriptVersion() {
|
|
9730
|
+
return this._scriptVersion;
|
|
9731
|
+
}
|
|
9549
9732
|
/**
|
|
9550
9733
|
* Connection state information
|
|
9551
9734
|
*/
|
|
@@ -9598,7 +9781,7 @@ var GameRoomTransport = class {
|
|
|
9598
9781
|
const onError = (error) => {
|
|
9599
9782
|
this._connectionStatus = "error";
|
|
9600
9783
|
this._lastError = error;
|
|
9601
|
-
this.handlers.onError?.({ code: "CONNECTION_ERROR", message: error.message });
|
|
9784
|
+
this.handlers.onError?.(new GameError({ code: "CONNECTION_ERROR", message: error.message }));
|
|
9602
9785
|
};
|
|
9603
9786
|
if (useWebTransport) {
|
|
9604
9787
|
try {
|
|
@@ -9660,18 +9843,35 @@ var GameRoomTransport = class {
|
|
|
9660
9843
|
this._state = null;
|
|
9661
9844
|
}
|
|
9662
9845
|
/**
|
|
9663
|
-
* Create a new room
|
|
9846
|
+
* Create a new room (호환 시그니처) — 초기 상태만 반환.
|
|
9847
|
+
*
|
|
9848
|
+
* Attached script 메타가 필요하면 {@link createRoomDetailed} 또는 인스턴스 getter
|
|
9849
|
+
* (`transport.scriptName`, `transport.scriptVersion`) 를 사용.
|
|
9664
9850
|
*/
|
|
9665
9851
|
async createRoom(config = {}) {
|
|
9852
|
+
const r = await this.createRoomDetailed(config);
|
|
9853
|
+
return r.state;
|
|
9854
|
+
}
|
|
9855
|
+
/**
|
|
9856
|
+
* Create a new room — server 가 attach 한 lua script 메타 함께 반환.
|
|
9857
|
+
*/
|
|
9858
|
+
async createRoomDetailed(config = {}) {
|
|
9666
9859
|
return new Promise((resolve, reject) => {
|
|
9667
9860
|
const handler = (msg) => {
|
|
9668
9861
|
if (msg.type === "room_created") {
|
|
9669
9862
|
const data = msg.data;
|
|
9670
9863
|
this._roomId = data.room_id;
|
|
9671
9864
|
this._state = data.initial_state;
|
|
9672
|
-
|
|
9865
|
+
this._scriptName = data.script_name ?? null;
|
|
9866
|
+
this._scriptVersion = data.script_version ?? null;
|
|
9867
|
+
resolve({
|
|
9868
|
+
roomId: data.room_id,
|
|
9869
|
+
state: data.initial_state,
|
|
9870
|
+
scriptName: data.script_name,
|
|
9871
|
+
scriptVersion: data.script_version
|
|
9872
|
+
});
|
|
9673
9873
|
} else if (msg.type === "error") {
|
|
9674
|
-
reject(
|
|
9874
|
+
reject(parseGameError(msg));
|
|
9675
9875
|
}
|
|
9676
9876
|
};
|
|
9677
9877
|
this.sendWithHandler("create_room", toCreateRoomWire(config), handler);
|
|
@@ -9687,9 +9887,11 @@ var GameRoomTransport = class {
|
|
|
9687
9887
|
const data = msg.data;
|
|
9688
9888
|
this._roomId = data.room_id;
|
|
9689
9889
|
this._state = data.initial_state;
|
|
9890
|
+
this._scriptName = null;
|
|
9891
|
+
this._scriptVersion = null;
|
|
9690
9892
|
resolve(data.initial_state);
|
|
9691
9893
|
} else if (msg.type === "error") {
|
|
9692
|
-
reject(
|
|
9894
|
+
reject(parseGameError(msg));
|
|
9693
9895
|
}
|
|
9694
9896
|
};
|
|
9695
9897
|
this.sendWithHandler("join_room", { room_id: roomId, metadata }, handler);
|
|
@@ -9701,16 +9903,18 @@ var GameRoomTransport = class {
|
|
|
9701
9903
|
async leaveRoom() {
|
|
9702
9904
|
return new Promise((resolve, reject) => {
|
|
9703
9905
|
if (!this._roomId) {
|
|
9704
|
-
reject(new
|
|
9906
|
+
reject(new GameError({ code: "NOT_IN_ROOM", message: "Not in a room" }));
|
|
9705
9907
|
return;
|
|
9706
9908
|
}
|
|
9707
9909
|
const handler = (msg) => {
|
|
9708
9910
|
if (msg.type === "room_left") {
|
|
9709
9911
|
this._roomId = null;
|
|
9710
9912
|
this._state = null;
|
|
9913
|
+
this._scriptName = null;
|
|
9914
|
+
this._scriptVersion = null;
|
|
9711
9915
|
resolve();
|
|
9712
9916
|
} else if (msg.type === "error") {
|
|
9713
|
-
reject(
|
|
9917
|
+
reject(parseGameError(msg));
|
|
9714
9918
|
}
|
|
9715
9919
|
};
|
|
9716
9920
|
this.sendWithHandler("leave_room", {}, handler);
|
|
@@ -9722,7 +9926,7 @@ var GameRoomTransport = class {
|
|
|
9722
9926
|
*/
|
|
9723
9927
|
sendAction(action, reliable = false) {
|
|
9724
9928
|
if (!this._roomId) {
|
|
9725
|
-
throw new
|
|
9929
|
+
throw new GameError({ code: "NOT_IN_ROOM", message: "Not in a room" });
|
|
9726
9930
|
}
|
|
9727
9931
|
const message = JSON.stringify({
|
|
9728
9932
|
type: "action",
|
|
@@ -9741,7 +9945,7 @@ var GameRoomTransport = class {
|
|
|
9741
9945
|
*/
|
|
9742
9946
|
sendChat(message) {
|
|
9743
9947
|
if (!this._roomId) {
|
|
9744
|
-
throw new
|
|
9948
|
+
throw new GameError({ code: "NOT_IN_ROOM", message: "Not in a room" });
|
|
9745
9949
|
}
|
|
9746
9950
|
this.send("chat", { message });
|
|
9747
9951
|
}
|
|
@@ -9751,7 +9955,7 @@ var GameRoomTransport = class {
|
|
|
9751
9955
|
async requestState() {
|
|
9752
9956
|
return new Promise((resolve, reject) => {
|
|
9753
9957
|
if (!this._roomId) {
|
|
9754
|
-
reject(new
|
|
9958
|
+
reject(new GameError({ code: "NOT_IN_ROOM", message: "Not in a room" }));
|
|
9755
9959
|
return;
|
|
9756
9960
|
}
|
|
9757
9961
|
const handler = (msg) => {
|
|
@@ -9760,7 +9964,7 @@ var GameRoomTransport = class {
|
|
|
9760
9964
|
this._state = state;
|
|
9761
9965
|
resolve(state);
|
|
9762
9966
|
} else if (msg.type === "error") {
|
|
9763
|
-
reject(
|
|
9967
|
+
reject(parseGameError(msg));
|
|
9764
9968
|
}
|
|
9765
9969
|
};
|
|
9766
9970
|
this.sendWithHandler("get_state", {}, handler);
|
|
@@ -9780,7 +9984,7 @@ var GameRoomTransport = class {
|
|
|
9780
9984
|
this.handlers.onPong?.(pong);
|
|
9781
9985
|
resolve(rtt);
|
|
9782
9986
|
} else if (msg.type === "error") {
|
|
9783
|
-
reject(
|
|
9987
|
+
reject(parseGameError(msg));
|
|
9784
9988
|
}
|
|
9785
9989
|
};
|
|
9786
9990
|
this.sendWithHandler("ping", { timestamp }, handler);
|
|
@@ -9789,7 +9993,7 @@ var GameRoomTransport = class {
|
|
|
9789
9993
|
// Private methods
|
|
9790
9994
|
send(type, data) {
|
|
9791
9995
|
if (!this.transport?.isConnected()) {
|
|
9792
|
-
throw new
|
|
9996
|
+
throw new GameError({ code: "NOT_CONNECTED", message: "Not connected" });
|
|
9793
9997
|
}
|
|
9794
9998
|
const message = JSON.stringify({ type, data });
|
|
9795
9999
|
this.transport.send(message, true);
|
|
@@ -9832,14 +10036,7 @@ var GameRoomTransport = class {
|
|
|
9832
10036
|
});
|
|
9833
10037
|
break;
|
|
9834
10038
|
case "error":
|
|
9835
|
-
this.handlers.onError?.(
|
|
9836
|
-
code: msg.code || "UNKNOWN",
|
|
9837
|
-
message: msg.message || "Unknown error",
|
|
9838
|
-
phase: msg.phase,
|
|
9839
|
-
feature: msg.feature,
|
|
9840
|
-
roomId: msg.room_id,
|
|
9841
|
-
scriptId: msg.script_id
|
|
9842
|
-
});
|
|
10039
|
+
this.handlers.onError?.(parseGameError(msg));
|
|
9843
10040
|
break;
|
|
9844
10041
|
case "room_stale":
|
|
9845
10042
|
if (this.handlers.onRoomStale) {
|
|
@@ -10049,6 +10246,7 @@ export {
|
|
|
10049
10246
|
EndpointAPI,
|
|
10050
10247
|
GameAPI,
|
|
10051
10248
|
GameConfigAPI,
|
|
10249
|
+
GameError,
|
|
10052
10250
|
GameRoom,
|
|
10053
10251
|
GameRoomTransport,
|
|
10054
10252
|
NativeAPI,
|