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.js
CHANGED
|
@@ -29,6 +29,7 @@ __export(index_exports, {
|
|
|
29
29
|
EndpointAPI: () => EndpointAPI,
|
|
30
30
|
GameAPI: () => GameAPI,
|
|
31
31
|
GameConfigAPI: () => GameConfigAPI,
|
|
32
|
+
GameError: () => GameError,
|
|
32
33
|
GameRoom: () => GameRoom,
|
|
33
34
|
GameRoomTransport: () => GameRoomTransport,
|
|
34
35
|
NativeAPI: () => NativeAPI,
|
|
@@ -56,6 +57,20 @@ var AuthError = class extends Error {
|
|
|
56
57
|
this.name = "AuthError";
|
|
57
58
|
}
|
|
58
59
|
};
|
|
60
|
+
var GameError = class extends Error {
|
|
61
|
+
constructor(init) {
|
|
62
|
+
super(init.message || init.code || "GameError");
|
|
63
|
+
this.name = "GameError";
|
|
64
|
+
this.code = init.code || "UNKNOWN";
|
|
65
|
+
this.phase = init.phase;
|
|
66
|
+
this.feature = init.feature;
|
|
67
|
+
this.roomId = init.roomId;
|
|
68
|
+
this.scriptId = init.scriptId;
|
|
69
|
+
this.originClientId = init.originClientId;
|
|
70
|
+
this.requested = init.requested;
|
|
71
|
+
this.available = init.available;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
59
74
|
|
|
60
75
|
// src/core/abort.ts
|
|
61
76
|
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -6277,6 +6292,21 @@ var GameConfigAPI = class {
|
|
|
6277
6292
|
};
|
|
6278
6293
|
|
|
6279
6294
|
// src/api/game.ts
|
|
6295
|
+
function parseGameError(msg) {
|
|
6296
|
+
const payload = msg.data ?? msg;
|
|
6297
|
+
const get = (k) => payload[k];
|
|
6298
|
+
return new GameError({
|
|
6299
|
+
code: get("code") || msg.code,
|
|
6300
|
+
message: get("message") || "Unknown error",
|
|
6301
|
+
phase: get("phase") || msg.phase,
|
|
6302
|
+
feature: get("feature") || msg.feature,
|
|
6303
|
+
roomId: get("room_id") || msg.room_id,
|
|
6304
|
+
scriptId: get("script_id") || msg.script_id,
|
|
6305
|
+
originClientId: get("origin_client_id") || msg.origin_client_id,
|
|
6306
|
+
requested: get("requested") || msg.requested,
|
|
6307
|
+
available: get("available") || msg.available
|
|
6308
|
+
});
|
|
6309
|
+
}
|
|
6280
6310
|
var getDefaultGameServerUrl = () => {
|
|
6281
6311
|
if (typeof window !== "undefined") {
|
|
6282
6312
|
const hostname = window.location.hostname;
|
|
@@ -6306,6 +6336,8 @@ var GameRoom = class {
|
|
|
6306
6336
|
this.actionSequence = 0;
|
|
6307
6337
|
this._roomId = null;
|
|
6308
6338
|
this._state = null;
|
|
6339
|
+
this._scriptName = null;
|
|
6340
|
+
this._scriptVersion = null;
|
|
6309
6341
|
this._isConnected = false;
|
|
6310
6342
|
this.msgIdCounter = 0;
|
|
6311
6343
|
this.config = {
|
|
@@ -6395,21 +6427,48 @@ var GameRoom = class {
|
|
|
6395
6427
|
}
|
|
6396
6428
|
this._isConnected = false;
|
|
6397
6429
|
this._roomId = null;
|
|
6430
|
+
this._scriptName = null;
|
|
6431
|
+
this._scriptVersion = null;
|
|
6398
6432
|
}
|
|
6399
6433
|
/**
|
|
6400
|
-
* 룸 생성
|
|
6434
|
+
* 룸 생성 (호환 시그니처) — 초기 상태만 반환.
|
|
6435
|
+
*
|
|
6436
|
+
* Attached script 의 검증(scriptName/scriptVersion echo 검사) 이 필요하면
|
|
6437
|
+
* 인스턴스 getter (`room.scriptName`, `room.scriptVersion`) 를 함께 사용하거나,
|
|
6438
|
+
* 메타까지 한 번에 받고 싶다면 {@link createRoomDetailed} 를 사용한다.
|
|
6439
|
+
*
|
|
6440
|
+
* 미존재/비활성 scriptName 은 서버가 `SCRIPT_NOT_FOUND` 로 즉시 reject 한다.
|
|
6441
|
+
* reject 값은 `GameError` 인스턴스이며 `.code`/`.requested`/`.available` 로 분기 가능.
|
|
6401
6442
|
*/
|
|
6402
6443
|
createRoom(config = {}) {
|
|
6444
|
+
return this.createRoomDetailed(config).then((r) => r.state);
|
|
6445
|
+
}
|
|
6446
|
+
/**
|
|
6447
|
+
* 룸 생성 + 메타 — server 가 attach 한 lua script 이름/버전을 함께 반환.
|
|
6448
|
+
*
|
|
6449
|
+
* @example
|
|
6450
|
+
* const { state, scriptName, scriptVersion } = await room.createRoomDetailed({ scriptName: 'njb-main' })
|
|
6451
|
+
* if (scriptName !== 'njb-main') throw new Error('script mismatch')
|
|
6452
|
+
* console.log('attached', scriptName, 'v', scriptVersion)
|
|
6453
|
+
*/
|
|
6454
|
+
createRoomDetailed(config = {}) {
|
|
6403
6455
|
return new Promise((resolve, reject) => {
|
|
6404
6456
|
const handler = (msg) => {
|
|
6405
6457
|
if (msg.type === "room_created") {
|
|
6406
6458
|
const data = msg.data;
|
|
6407
6459
|
this._roomId = data.room_id;
|
|
6408
6460
|
this._state = data.initial_state;
|
|
6409
|
-
|
|
6461
|
+
this._scriptName = data.script_name ?? null;
|
|
6462
|
+
this._scriptVersion = data.script_version ?? null;
|
|
6463
|
+
resolve({
|
|
6464
|
+
roomId: data.room_id,
|
|
6465
|
+
state: data.initial_state,
|
|
6466
|
+
scriptName: data.script_name,
|
|
6467
|
+
scriptVersion: data.script_version
|
|
6468
|
+
});
|
|
6410
6469
|
return true;
|
|
6411
6470
|
} else if (msg.type === "error") {
|
|
6412
|
-
reject(
|
|
6471
|
+
reject(parseGameError(msg));
|
|
6413
6472
|
return true;
|
|
6414
6473
|
}
|
|
6415
6474
|
return false;
|
|
@@ -6417,6 +6476,14 @@ var GameRoom = class {
|
|
|
6417
6476
|
this.sendWithHandler("create_room", toCreateRoomWire(config), handler, 15e3, reject);
|
|
6418
6477
|
});
|
|
6419
6478
|
}
|
|
6479
|
+
/** Attached lua script 이름 — createRoom 이후 server 가 echo 한 값. */
|
|
6480
|
+
get scriptName() {
|
|
6481
|
+
return this._scriptName;
|
|
6482
|
+
}
|
|
6483
|
+
/** Attached lua script 의 active version. */
|
|
6484
|
+
get scriptVersion() {
|
|
6485
|
+
return this._scriptVersion;
|
|
6486
|
+
}
|
|
6420
6487
|
/**
|
|
6421
6488
|
* 룸 참가
|
|
6422
6489
|
*/
|
|
@@ -6427,10 +6494,12 @@ var GameRoom = class {
|
|
|
6427
6494
|
const data = msg.data;
|
|
6428
6495
|
this._roomId = data.room_id;
|
|
6429
6496
|
this._state = data.initial_state;
|
|
6497
|
+
this._scriptName = null;
|
|
6498
|
+
this._scriptVersion = null;
|
|
6430
6499
|
resolve(data.initial_state);
|
|
6431
6500
|
return true;
|
|
6432
6501
|
} else if (msg.type === "error") {
|
|
6433
|
-
reject(
|
|
6502
|
+
reject(parseGameError(msg));
|
|
6434
6503
|
return true;
|
|
6435
6504
|
}
|
|
6436
6505
|
return false;
|
|
@@ -6444,17 +6513,19 @@ var GameRoom = class {
|
|
|
6444
6513
|
leaveRoom() {
|
|
6445
6514
|
return new Promise((resolve, reject) => {
|
|
6446
6515
|
if (!this._roomId) {
|
|
6447
|
-
reject(new
|
|
6516
|
+
reject(new GameError({ code: "NOT_IN_ROOM", message: "Not in a room" }));
|
|
6448
6517
|
return;
|
|
6449
6518
|
}
|
|
6450
6519
|
const handler = (msg) => {
|
|
6451
6520
|
if (msg.type === "room_left") {
|
|
6452
6521
|
this._roomId = null;
|
|
6453
6522
|
this._state = null;
|
|
6523
|
+
this._scriptName = null;
|
|
6524
|
+
this._scriptVersion = null;
|
|
6454
6525
|
resolve();
|
|
6455
6526
|
return true;
|
|
6456
6527
|
} else if (msg.type === "error") {
|
|
6457
|
-
reject(
|
|
6528
|
+
reject(parseGameError(msg));
|
|
6458
6529
|
return true;
|
|
6459
6530
|
}
|
|
6460
6531
|
return false;
|
|
@@ -6467,7 +6538,7 @@ var GameRoom = class {
|
|
|
6467
6538
|
*/
|
|
6468
6539
|
sendAction(action) {
|
|
6469
6540
|
if (!this._roomId) {
|
|
6470
|
-
throw new
|
|
6541
|
+
throw new GameError({ code: "NOT_IN_ROOM", message: "Not in a room" });
|
|
6471
6542
|
}
|
|
6472
6543
|
this.send("action", {
|
|
6473
6544
|
type: action.type,
|
|
@@ -6481,7 +6552,7 @@ var GameRoom = class {
|
|
|
6481
6552
|
*/
|
|
6482
6553
|
sendChat(message) {
|
|
6483
6554
|
if (!this._roomId) {
|
|
6484
|
-
throw new
|
|
6555
|
+
throw new GameError({ code: "NOT_IN_ROOM", message: "Not in a room" });
|
|
6485
6556
|
}
|
|
6486
6557
|
this.send("chat", { message });
|
|
6487
6558
|
}
|
|
@@ -6491,7 +6562,7 @@ var GameRoom = class {
|
|
|
6491
6562
|
requestState() {
|
|
6492
6563
|
return new Promise((resolve, reject) => {
|
|
6493
6564
|
if (!this._roomId) {
|
|
6494
|
-
reject(new
|
|
6565
|
+
reject(new GameError({ code: "NOT_IN_ROOM", message: "Not in a room" }));
|
|
6495
6566
|
return;
|
|
6496
6567
|
}
|
|
6497
6568
|
const handler = (msg) => {
|
|
@@ -6501,7 +6572,7 @@ var GameRoom = class {
|
|
|
6501
6572
|
resolve(state);
|
|
6502
6573
|
return true;
|
|
6503
6574
|
} else if (msg.type === "error") {
|
|
6504
|
-
reject(
|
|
6575
|
+
reject(parseGameError(msg));
|
|
6505
6576
|
return true;
|
|
6506
6577
|
}
|
|
6507
6578
|
return false;
|
|
@@ -6520,7 +6591,7 @@ var GameRoom = class {
|
|
|
6520
6591
|
resolve(data.rooms);
|
|
6521
6592
|
return true;
|
|
6522
6593
|
} else if (msg.type === "error") {
|
|
6523
|
-
reject(
|
|
6594
|
+
reject(parseGameError(msg));
|
|
6524
6595
|
return true;
|
|
6525
6596
|
}
|
|
6526
6597
|
return false;
|
|
@@ -6542,7 +6613,7 @@ var GameRoom = class {
|
|
|
6542
6613
|
resolve(rtt);
|
|
6543
6614
|
return true;
|
|
6544
6615
|
} else if (msg.type === "error") {
|
|
6545
|
-
reject(
|
|
6616
|
+
reject(parseGameError(msg));
|
|
6546
6617
|
return true;
|
|
6547
6618
|
}
|
|
6548
6619
|
return false;
|
|
@@ -6600,12 +6671,12 @@ var GameRoom = class {
|
|
|
6600
6671
|
this.ws?.addEventListener("message", messageHandler);
|
|
6601
6672
|
timeoutId = setTimeout(() => {
|
|
6602
6673
|
cleanup();
|
|
6603
|
-
const err = new
|
|
6604
|
-
onError?.(err);
|
|
6605
|
-
this.handlers.onError?.({
|
|
6674
|
+
const err = new GameError({
|
|
6606
6675
|
code: "TIMEOUT",
|
|
6607
|
-
message:
|
|
6676
|
+
message: `Request '${type}' timed out after ${timeoutMs}ms`
|
|
6608
6677
|
});
|
|
6678
|
+
onError?.(err);
|
|
6679
|
+
this.handlers.onError?.(err);
|
|
6609
6680
|
}, timeoutMs);
|
|
6610
6681
|
try {
|
|
6611
6682
|
this.send(type, data, msgId);
|
|
@@ -6639,10 +6710,7 @@ var GameRoom = class {
|
|
|
6639
6710
|
});
|
|
6640
6711
|
break;
|
|
6641
6712
|
case "error":
|
|
6642
|
-
this.handlers.onError?.(
|
|
6643
|
-
code: msg.code || "UNKNOWN",
|
|
6644
|
-
message: msg.message || "Unknown error"
|
|
6645
|
-
});
|
|
6713
|
+
this.handlers.onError?.(parseGameError(msg));
|
|
6646
6714
|
break;
|
|
6647
6715
|
default:
|
|
6648
6716
|
break;
|
|
@@ -6720,10 +6788,10 @@ var GameRoom = class {
|
|
|
6720
6788
|
scheduleReconnect(roomId) {
|
|
6721
6789
|
if (this.reconnectAttempts >= (this.config.maxReconnectAttempts ?? 5)) {
|
|
6722
6790
|
console.error("Max reconnect attempts reached");
|
|
6723
|
-
this.handlers.onError?.({
|
|
6791
|
+
this.handlers.onError?.(new GameError({
|
|
6724
6792
|
code: "MAX_RECONNECT_ATTEMPTS",
|
|
6725
6793
|
message: "Maximum reconnection attempts reached"
|
|
6726
|
-
});
|
|
6794
|
+
}));
|
|
6727
6795
|
return;
|
|
6728
6796
|
}
|
|
6729
6797
|
const delay = Math.min(
|
|
@@ -6744,10 +6812,11 @@ var GameRoom = class {
|
|
|
6744
6812
|
console.log(`Successfully rejoined room ${previousRoomId}`);
|
|
6745
6813
|
} catch (rejoinError) {
|
|
6746
6814
|
console.error(`Failed to rejoin room ${previousRoomId}:`, rejoinError);
|
|
6747
|
-
this.handlers.onError?.({
|
|
6815
|
+
this.handlers.onError?.(new GameError({
|
|
6748
6816
|
code: "REJOIN_FAILED",
|
|
6749
|
-
message: `Failed to rejoin room: ${rejoinError}
|
|
6750
|
-
|
|
6817
|
+
message: `Failed to rejoin room: ${rejoinError}`,
|
|
6818
|
+
roomId: previousRoomId
|
|
6819
|
+
}));
|
|
6751
6820
|
}
|
|
6752
6821
|
}
|
|
6753
6822
|
} catch {
|
|
@@ -6956,6 +7025,38 @@ var GameAPI = class {
|
|
|
6956
7025
|
}
|
|
6957
7026
|
return headers;
|
|
6958
7027
|
}
|
|
7028
|
+
// game-server (`game.connectbase.world`) 전용 fetch.
|
|
7029
|
+
//
|
|
7030
|
+
// 회귀 가드 (v3.14.1, NJB report 2026-05-14): matchqueue / leaderboard / scripts 16개
|
|
7031
|
+
// 메서드가 v3.0.0 BREAKING 이후 `this.http.*` 를 통해 기본 baseUrl (=core-server,
|
|
7032
|
+
// `api.connectbase.world`) 로 라우팅되면서 404 회귀. 호스트 분리된 game-server 경로는
|
|
7033
|
+
// 반드시 `this.gameServerUrl` 을 사용해야 한다. 회귀 재발 방지 테스트:
|
|
7034
|
+
// test/game-host-routing.test.ts.
|
|
7035
|
+
async gameFetch(method, path, body, errCode = "GAME_REQUEST_FAILED") {
|
|
7036
|
+
const init = { method, headers: { ...this.getHeaders() } };
|
|
7037
|
+
if (body !== void 0) {
|
|
7038
|
+
init.headers["Content-Type"] = "application/json";
|
|
7039
|
+
init.body = JSON.stringify(body);
|
|
7040
|
+
}
|
|
7041
|
+
const response = await fetch(`${this.gameServerUrl}${path}`, init);
|
|
7042
|
+
if (!response.ok) {
|
|
7043
|
+
let serverCode = errCode;
|
|
7044
|
+
let message = `${method} ${path} failed: ${response.statusText}`;
|
|
7045
|
+
try {
|
|
7046
|
+
const data = await response.json();
|
|
7047
|
+
if (data && typeof data.error === "string") {
|
|
7048
|
+
message = data.error;
|
|
7049
|
+
serverCode = data.error;
|
|
7050
|
+
}
|
|
7051
|
+
} catch {
|
|
7052
|
+
}
|
|
7053
|
+
throw new ApiError(response.status, message, serverCode);
|
|
7054
|
+
}
|
|
7055
|
+
if (response.status === 204) return void 0;
|
|
7056
|
+
const contentType = response.headers.get("content-type") || "";
|
|
7057
|
+
if (!contentType.includes("application/json")) return void 0;
|
|
7058
|
+
return response.json();
|
|
7059
|
+
}
|
|
6959
7060
|
// ─────────────────────────────────────────────────────────────────────
|
|
6960
7061
|
// Phase 3 — Matchqueue primitive (mechanism only)
|
|
6961
7062
|
//
|
|
@@ -6976,9 +7077,11 @@ var GameAPI = class {
|
|
|
6976
7077
|
* @constraints appId/queueKey/ticketId 는 [a-zA-Z0-9_-] 만 허용. ttlSec=0 이면 만료 없음.
|
|
6977
7078
|
*/
|
|
6978
7079
|
async enqueueMatch(appId, queueKey, ticketId, attributes, ttlSec) {
|
|
6979
|
-
return this.
|
|
7080
|
+
return this.gameFetch(
|
|
7081
|
+
"POST",
|
|
6980
7082
|
`/v1/game/${appId}/matchqueue/${queueKey}/tickets`,
|
|
6981
|
-
{ ticket_id: ticketId, attributes, ttl_sec: ttlSec ?? 0 }
|
|
7083
|
+
{ ticket_id: ticketId, attributes, ttl_sec: ttlSec ?? 0 },
|
|
7084
|
+
"GAME_ENQUEUE_MATCH_FAILED"
|
|
6982
7085
|
);
|
|
6983
7086
|
}
|
|
6984
7087
|
/**
|
|
@@ -6987,16 +7090,22 @@ var GameAPI = class {
|
|
|
6987
7090
|
* @example const tickets = await cb.game.listMatchqueue(appId, "ranked")
|
|
6988
7091
|
*/
|
|
6989
7092
|
async listMatchqueue(appId, queueKey) {
|
|
6990
|
-
return this.
|
|
6991
|
-
|
|
7093
|
+
return this.gameFetch(
|
|
7094
|
+
"GET",
|
|
7095
|
+
`/v1/game/${appId}/matchqueue/${queueKey}`,
|
|
7096
|
+
void 0,
|
|
7097
|
+
"GAME_LIST_MATCHQUEUE_FAILED"
|
|
6992
7098
|
);
|
|
6993
7099
|
}
|
|
6994
7100
|
/**
|
|
6995
7101
|
* 매칭 큐에서 ticket 제거 (예: 사용자가 매칭 취소).
|
|
6996
7102
|
*/
|
|
6997
7103
|
async cancelMatch(appId, queueKey, ticketId) {
|
|
6998
|
-
await this.
|
|
6999
|
-
|
|
7104
|
+
await this.gameFetch(
|
|
7105
|
+
"DELETE",
|
|
7106
|
+
`/v1/game/${appId}/matchqueue/${queueKey}/tickets/${ticketId}`,
|
|
7107
|
+
void 0,
|
|
7108
|
+
"GAME_CANCEL_MATCH_FAILED"
|
|
7000
7109
|
);
|
|
7001
7110
|
}
|
|
7002
7111
|
// ─────────────────────────────────────────────────────────────────────
|
|
@@ -7012,47 +7121,66 @@ var GameAPI = class {
|
|
|
7012
7121
|
* await cb.game.submitScore(appId, "global_elo", userId, 32, "incr")
|
|
7013
7122
|
*/
|
|
7014
7123
|
async submitScore(appId, leaderboardKey, member, score, mode = "set") {
|
|
7015
|
-
return this.
|
|
7124
|
+
return this.gameFetch(
|
|
7125
|
+
"POST",
|
|
7016
7126
|
`/v1/game/${appId}/leaderboards/${leaderboardKey}/scores`,
|
|
7017
|
-
{ member, score, mode }
|
|
7127
|
+
{ member, score, mode },
|
|
7128
|
+
"GAME_SUBMIT_SCORE_FAILED"
|
|
7018
7129
|
);
|
|
7019
7130
|
}
|
|
7020
7131
|
/**
|
|
7021
7132
|
* 상위 n 명 조회 (기본 10).
|
|
7022
7133
|
*/
|
|
7023
7134
|
async getTopScores(appId, leaderboardKey, n = 10) {
|
|
7024
|
-
return this.
|
|
7025
|
-
|
|
7135
|
+
return this.gameFetch(
|
|
7136
|
+
"GET",
|
|
7137
|
+
`/v1/game/${appId}/leaderboards/${leaderboardKey}/top?n=${n}`,
|
|
7138
|
+
void 0,
|
|
7139
|
+
"GAME_GET_TOP_SCORES_FAILED"
|
|
7026
7140
|
);
|
|
7027
7141
|
}
|
|
7028
7142
|
/**
|
|
7029
7143
|
* 단일 member 의 rank + score.
|
|
7030
7144
|
*/
|
|
7031
7145
|
async getMemberRank(appId, leaderboardKey, member) {
|
|
7032
|
-
return this.
|
|
7033
|
-
|
|
7146
|
+
return this.gameFetch(
|
|
7147
|
+
"GET",
|
|
7148
|
+
`/v1/game/${appId}/leaderboards/${leaderboardKey}/members/${member}`,
|
|
7149
|
+
void 0,
|
|
7150
|
+
"GAME_GET_MEMBER_RANK_FAILED"
|
|
7034
7151
|
);
|
|
7035
7152
|
}
|
|
7036
7153
|
/**
|
|
7037
7154
|
* member 주변 (위 above 명 + 본인 + 아래 below 명) 조회.
|
|
7038
7155
|
*/
|
|
7039
7156
|
async getRankAround(appId, leaderboardKey, member, above = 5, below = 5) {
|
|
7040
|
-
return this.
|
|
7041
|
-
|
|
7157
|
+
return this.gameFetch(
|
|
7158
|
+
"GET",
|
|
7159
|
+
`/v1/game/${appId}/leaderboards/${leaderboardKey}/around/${member}?above=${above}&below=${below}`,
|
|
7160
|
+
void 0,
|
|
7161
|
+
"GAME_GET_RANK_AROUND_FAILED"
|
|
7042
7162
|
);
|
|
7043
7163
|
}
|
|
7044
7164
|
/**
|
|
7045
7165
|
* leaderboard 전체 reset (시즌 종료 시).
|
|
7046
7166
|
*/
|
|
7047
7167
|
async resetLeaderboard(appId, leaderboardKey) {
|
|
7048
|
-
await this.
|
|
7168
|
+
await this.gameFetch(
|
|
7169
|
+
"DELETE",
|
|
7170
|
+
`/v1/game/${appId}/leaderboards/${leaderboardKey}`,
|
|
7171
|
+
void 0,
|
|
7172
|
+
"GAME_RESET_LEADERBOARD_FAILED"
|
|
7173
|
+
);
|
|
7049
7174
|
}
|
|
7050
7175
|
/**
|
|
7051
7176
|
* 특정 member 만 제거 (계정 삭제 등).
|
|
7052
7177
|
*/
|
|
7053
7178
|
async removeFromLeaderboard(appId, leaderboardKey, member) {
|
|
7054
|
-
await this.
|
|
7055
|
-
|
|
7179
|
+
await this.gameFetch(
|
|
7180
|
+
"DELETE",
|
|
7181
|
+
`/v1/game/${appId}/leaderboards/${leaderboardKey}/members/${member}`,
|
|
7182
|
+
void 0,
|
|
7183
|
+
"GAME_REMOVE_FROM_LEADERBOARD_FAILED"
|
|
7056
7184
|
);
|
|
7057
7185
|
}
|
|
7058
7186
|
// ─────────────────────────────────────────────────────────────────────
|
|
@@ -7067,54 +7195,100 @@ var GameAPI = class {
|
|
|
7067
7195
|
* await cb.game.uploadScript(appId, "ranked_br", fs.readFileSync("./ranked.lua", "utf-8"))
|
|
7068
7196
|
*/
|
|
7069
7197
|
async uploadScript(appId, name, code) {
|
|
7070
|
-
return this.
|
|
7198
|
+
return this.gameFetch(
|
|
7199
|
+
"POST",
|
|
7071
7200
|
`/v1/game/${appId}/scripts`,
|
|
7072
|
-
{ name, code }
|
|
7201
|
+
{ name, code },
|
|
7202
|
+
"GAME_UPLOAD_SCRIPT_FAILED"
|
|
7073
7203
|
);
|
|
7074
7204
|
}
|
|
7075
7205
|
/**
|
|
7076
7206
|
* app 의 모든 스크립트 메타데이터 목록.
|
|
7077
7207
|
*/
|
|
7078
7208
|
async listScripts(appId) {
|
|
7079
|
-
return this.
|
|
7209
|
+
return this.gameFetch(
|
|
7210
|
+
"GET",
|
|
7211
|
+
`/v1/game/${appId}/scripts`,
|
|
7212
|
+
void 0,
|
|
7213
|
+
"GAME_LIST_SCRIPTS_FAILED"
|
|
7214
|
+
);
|
|
7080
7215
|
}
|
|
7081
7216
|
/**
|
|
7082
7217
|
* 단일 스크립트 메타 + active version code.
|
|
7083
7218
|
*/
|
|
7084
7219
|
async getScript(appId, name) {
|
|
7085
|
-
return this.
|
|
7220
|
+
return this.gameFetch(
|
|
7221
|
+
"GET",
|
|
7222
|
+
`/v1/game/${appId}/scripts/${name}`,
|
|
7223
|
+
void 0,
|
|
7224
|
+
"GAME_GET_SCRIPT_FAILED"
|
|
7225
|
+
);
|
|
7086
7226
|
}
|
|
7087
7227
|
/**
|
|
7088
7228
|
* 단일 스크립트의 모든 버전 이력.
|
|
7089
7229
|
*/
|
|
7090
7230
|
async listScriptVersions(appId, name) {
|
|
7091
|
-
return this.
|
|
7092
|
-
|
|
7231
|
+
return this.gameFetch(
|
|
7232
|
+
"GET",
|
|
7233
|
+
`/v1/game/${appId}/scripts/${name}/versions`,
|
|
7234
|
+
void 0,
|
|
7235
|
+
"GAME_LIST_SCRIPT_VERSIONS_FAILED"
|
|
7093
7236
|
);
|
|
7094
7237
|
}
|
|
7095
7238
|
/**
|
|
7096
7239
|
* 특정 버전 활성화 (hot reload 자동). version=0 또는 미지정 → latest 활성화.
|
|
7097
7240
|
*/
|
|
7098
7241
|
async activateScript(appId, name, version) {
|
|
7099
|
-
return this.
|
|
7242
|
+
return this.gameFetch(
|
|
7243
|
+
"POST",
|
|
7100
7244
|
`/v1/game/${appId}/scripts/${name}/activate`,
|
|
7101
|
-
{ version: version ?? 0 }
|
|
7245
|
+
{ version: version ?? 0 },
|
|
7246
|
+
"GAME_ACTIVATE_SCRIPT_FAILED"
|
|
7102
7247
|
);
|
|
7103
7248
|
}
|
|
7104
7249
|
/**
|
|
7105
7250
|
* 직전 active 버전으로 롤백 (hot reload 자동).
|
|
7106
7251
|
*/
|
|
7107
7252
|
async rollbackScript(appId, name) {
|
|
7108
|
-
return this.
|
|
7253
|
+
return this.gameFetch(
|
|
7254
|
+
"POST",
|
|
7109
7255
|
`/v1/game/${appId}/scripts/${name}/rollback`,
|
|
7110
|
-
{}
|
|
7256
|
+
{},
|
|
7257
|
+
"GAME_ROLLBACK_SCRIPT_FAILED"
|
|
7111
7258
|
);
|
|
7112
7259
|
}
|
|
7113
7260
|
/**
|
|
7114
|
-
* 스크립트 비활성화 (status=disabled).
|
|
7261
|
+
* 스크립트 비활성화 (status=disabled, ActiveVersion=0). 코드/모든 버전은 보존되며
|
|
7262
|
+
* 이후 {@link activateScript} 로 재활성화할 수 있다.
|
|
7263
|
+
*
|
|
7264
|
+
* v3.14.0 BREAKING — 이전엔 `disableScript` 가 `DELETE /scripts/:name` 을 호출해
|
|
7265
|
+
* Disable 매핑이었으나, server 측 `DELETE` 가 hard-delete 로 분리됨에 따라
|
|
7266
|
+
* `POST /scripts/:name/deactivate` 로 endpoint 이동. 메서드명도 의도 명확화 위해 rename.
|
|
7115
7267
|
*/
|
|
7116
|
-
async
|
|
7117
|
-
await this.
|
|
7268
|
+
async deactivateScript(appId, name) {
|
|
7269
|
+
await this.gameFetch(
|
|
7270
|
+
"POST",
|
|
7271
|
+
`/v1/game/${appId}/scripts/${name}/deactivate`,
|
|
7272
|
+
{},
|
|
7273
|
+
"GAME_DEACTIVATE_SCRIPT_FAILED"
|
|
7274
|
+
);
|
|
7275
|
+
}
|
|
7276
|
+
/**
|
|
7277
|
+
* 스크립트 영구 삭제 (hard-delete) — 메타 + 모든 버전 영구 제거. 복구 불가.
|
|
7278
|
+
*
|
|
7279
|
+
* 잘못 업로드된 스크립트나 더 이상 사용하지 않는 슬롯을 정리할 때 사용. 단순 비활성화는
|
|
7280
|
+
* {@link deactivateScript} 를 사용한다 (코드 보존, 재활성화 가능).
|
|
7281
|
+
*
|
|
7282
|
+
* v3.14.0 신규 — server 측 `DELETE /scripts/:name` 의 의미가 Disable → hard-delete 로
|
|
7283
|
+
* 변경된 것에 대응.
|
|
7284
|
+
*/
|
|
7285
|
+
async deleteScript(appId, name) {
|
|
7286
|
+
await this.gameFetch(
|
|
7287
|
+
"DELETE",
|
|
7288
|
+
`/v1/game/${appId}/scripts/${name}`,
|
|
7289
|
+
void 0,
|
|
7290
|
+
"GAME_DELETE_SCRIPT_FAILED"
|
|
7291
|
+
);
|
|
7118
7292
|
}
|
|
7119
7293
|
};
|
|
7120
7294
|
|
|
@@ -9420,7 +9594,7 @@ var WebTransportTransport = class {
|
|
|
9420
9594
|
}
|
|
9421
9595
|
send(data, reliable = true) {
|
|
9422
9596
|
if (!this.transport) {
|
|
9423
|
-
throw new
|
|
9597
|
+
throw new GameError({ code: "NOT_CONNECTED", message: "Not connected" });
|
|
9424
9598
|
}
|
|
9425
9599
|
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
9426
9600
|
if (reliable) {
|
|
@@ -9511,7 +9685,7 @@ var WebSocketTransport = class {
|
|
|
9511
9685
|
}
|
|
9512
9686
|
send(data, _reliable) {
|
|
9513
9687
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
9514
|
-
throw new
|
|
9688
|
+
throw new GameError({ code: "NOT_CONNECTED", message: "Not connected" });
|
|
9515
9689
|
}
|
|
9516
9690
|
if (typeof data === "string") {
|
|
9517
9691
|
this.ws.send(data);
|
|
@@ -9536,6 +9710,8 @@ var GameRoomTransport = class {
|
|
|
9536
9710
|
this.actionSequence = 0;
|
|
9537
9711
|
this._roomId = null;
|
|
9538
9712
|
this._state = null;
|
|
9713
|
+
this._scriptName = null;
|
|
9714
|
+
this._scriptVersion = null;
|
|
9539
9715
|
this._isConnected = false;
|
|
9540
9716
|
this._connectionStatus = "disconnected";
|
|
9541
9717
|
this._lastError = null;
|
|
@@ -9588,6 +9764,14 @@ var GameRoomTransport = class {
|
|
|
9588
9764
|
get isConnected() {
|
|
9589
9765
|
return this._isConnected;
|
|
9590
9766
|
}
|
|
9767
|
+
/** Attached lua script 이름 — createRoom 이후 server 가 echo 한 값. */
|
|
9768
|
+
get scriptName() {
|
|
9769
|
+
return this._scriptName;
|
|
9770
|
+
}
|
|
9771
|
+
/** Attached lua script 의 active version. */
|
|
9772
|
+
get scriptVersion() {
|
|
9773
|
+
return this._scriptVersion;
|
|
9774
|
+
}
|
|
9591
9775
|
/**
|
|
9592
9776
|
* Connection state information
|
|
9593
9777
|
*/
|
|
@@ -9640,7 +9824,7 @@ var GameRoomTransport = class {
|
|
|
9640
9824
|
const onError = (error) => {
|
|
9641
9825
|
this._connectionStatus = "error";
|
|
9642
9826
|
this._lastError = error;
|
|
9643
|
-
this.handlers.onError?.({ code: "CONNECTION_ERROR", message: error.message });
|
|
9827
|
+
this.handlers.onError?.(new GameError({ code: "CONNECTION_ERROR", message: error.message }));
|
|
9644
9828
|
};
|
|
9645
9829
|
if (useWebTransport) {
|
|
9646
9830
|
try {
|
|
@@ -9702,18 +9886,35 @@ var GameRoomTransport = class {
|
|
|
9702
9886
|
this._state = null;
|
|
9703
9887
|
}
|
|
9704
9888
|
/**
|
|
9705
|
-
* Create a new room
|
|
9889
|
+
* Create a new room (호환 시그니처) — 초기 상태만 반환.
|
|
9890
|
+
*
|
|
9891
|
+
* Attached script 메타가 필요하면 {@link createRoomDetailed} 또는 인스턴스 getter
|
|
9892
|
+
* (`transport.scriptName`, `transport.scriptVersion`) 를 사용.
|
|
9706
9893
|
*/
|
|
9707
9894
|
async createRoom(config = {}) {
|
|
9895
|
+
const r = await this.createRoomDetailed(config);
|
|
9896
|
+
return r.state;
|
|
9897
|
+
}
|
|
9898
|
+
/**
|
|
9899
|
+
* Create a new room — server 가 attach 한 lua script 메타 함께 반환.
|
|
9900
|
+
*/
|
|
9901
|
+
async createRoomDetailed(config = {}) {
|
|
9708
9902
|
return new Promise((resolve, reject) => {
|
|
9709
9903
|
const handler = (msg) => {
|
|
9710
9904
|
if (msg.type === "room_created") {
|
|
9711
9905
|
const data = msg.data;
|
|
9712
9906
|
this._roomId = data.room_id;
|
|
9713
9907
|
this._state = data.initial_state;
|
|
9714
|
-
|
|
9908
|
+
this._scriptName = data.script_name ?? null;
|
|
9909
|
+
this._scriptVersion = data.script_version ?? null;
|
|
9910
|
+
resolve({
|
|
9911
|
+
roomId: data.room_id,
|
|
9912
|
+
state: data.initial_state,
|
|
9913
|
+
scriptName: data.script_name,
|
|
9914
|
+
scriptVersion: data.script_version
|
|
9915
|
+
});
|
|
9715
9916
|
} else if (msg.type === "error") {
|
|
9716
|
-
reject(
|
|
9917
|
+
reject(parseGameError(msg));
|
|
9717
9918
|
}
|
|
9718
9919
|
};
|
|
9719
9920
|
this.sendWithHandler("create_room", toCreateRoomWire(config), handler);
|
|
@@ -9729,9 +9930,11 @@ var GameRoomTransport = class {
|
|
|
9729
9930
|
const data = msg.data;
|
|
9730
9931
|
this._roomId = data.room_id;
|
|
9731
9932
|
this._state = data.initial_state;
|
|
9933
|
+
this._scriptName = null;
|
|
9934
|
+
this._scriptVersion = null;
|
|
9732
9935
|
resolve(data.initial_state);
|
|
9733
9936
|
} else if (msg.type === "error") {
|
|
9734
|
-
reject(
|
|
9937
|
+
reject(parseGameError(msg));
|
|
9735
9938
|
}
|
|
9736
9939
|
};
|
|
9737
9940
|
this.sendWithHandler("join_room", { room_id: roomId, metadata }, handler);
|
|
@@ -9743,16 +9946,18 @@ var GameRoomTransport = class {
|
|
|
9743
9946
|
async leaveRoom() {
|
|
9744
9947
|
return new Promise((resolve, reject) => {
|
|
9745
9948
|
if (!this._roomId) {
|
|
9746
|
-
reject(new
|
|
9949
|
+
reject(new GameError({ code: "NOT_IN_ROOM", message: "Not in a room" }));
|
|
9747
9950
|
return;
|
|
9748
9951
|
}
|
|
9749
9952
|
const handler = (msg) => {
|
|
9750
9953
|
if (msg.type === "room_left") {
|
|
9751
9954
|
this._roomId = null;
|
|
9752
9955
|
this._state = null;
|
|
9956
|
+
this._scriptName = null;
|
|
9957
|
+
this._scriptVersion = null;
|
|
9753
9958
|
resolve();
|
|
9754
9959
|
} else if (msg.type === "error") {
|
|
9755
|
-
reject(
|
|
9960
|
+
reject(parseGameError(msg));
|
|
9756
9961
|
}
|
|
9757
9962
|
};
|
|
9758
9963
|
this.sendWithHandler("leave_room", {}, handler);
|
|
@@ -9764,7 +9969,7 @@ var GameRoomTransport = class {
|
|
|
9764
9969
|
*/
|
|
9765
9970
|
sendAction(action, reliable = false) {
|
|
9766
9971
|
if (!this._roomId) {
|
|
9767
|
-
throw new
|
|
9972
|
+
throw new GameError({ code: "NOT_IN_ROOM", message: "Not in a room" });
|
|
9768
9973
|
}
|
|
9769
9974
|
const message = JSON.stringify({
|
|
9770
9975
|
type: "action",
|
|
@@ -9783,7 +9988,7 @@ var GameRoomTransport = class {
|
|
|
9783
9988
|
*/
|
|
9784
9989
|
sendChat(message) {
|
|
9785
9990
|
if (!this._roomId) {
|
|
9786
|
-
throw new
|
|
9991
|
+
throw new GameError({ code: "NOT_IN_ROOM", message: "Not in a room" });
|
|
9787
9992
|
}
|
|
9788
9993
|
this.send("chat", { message });
|
|
9789
9994
|
}
|
|
@@ -9793,7 +9998,7 @@ var GameRoomTransport = class {
|
|
|
9793
9998
|
async requestState() {
|
|
9794
9999
|
return new Promise((resolve, reject) => {
|
|
9795
10000
|
if (!this._roomId) {
|
|
9796
|
-
reject(new
|
|
10001
|
+
reject(new GameError({ code: "NOT_IN_ROOM", message: "Not in a room" }));
|
|
9797
10002
|
return;
|
|
9798
10003
|
}
|
|
9799
10004
|
const handler = (msg) => {
|
|
@@ -9802,7 +10007,7 @@ var GameRoomTransport = class {
|
|
|
9802
10007
|
this._state = state;
|
|
9803
10008
|
resolve(state);
|
|
9804
10009
|
} else if (msg.type === "error") {
|
|
9805
|
-
reject(
|
|
10010
|
+
reject(parseGameError(msg));
|
|
9806
10011
|
}
|
|
9807
10012
|
};
|
|
9808
10013
|
this.sendWithHandler("get_state", {}, handler);
|
|
@@ -9822,7 +10027,7 @@ var GameRoomTransport = class {
|
|
|
9822
10027
|
this.handlers.onPong?.(pong);
|
|
9823
10028
|
resolve(rtt);
|
|
9824
10029
|
} else if (msg.type === "error") {
|
|
9825
|
-
reject(
|
|
10030
|
+
reject(parseGameError(msg));
|
|
9826
10031
|
}
|
|
9827
10032
|
};
|
|
9828
10033
|
this.sendWithHandler("ping", { timestamp }, handler);
|
|
@@ -9831,7 +10036,7 @@ var GameRoomTransport = class {
|
|
|
9831
10036
|
// Private methods
|
|
9832
10037
|
send(type, data) {
|
|
9833
10038
|
if (!this.transport?.isConnected()) {
|
|
9834
|
-
throw new
|
|
10039
|
+
throw new GameError({ code: "NOT_CONNECTED", message: "Not connected" });
|
|
9835
10040
|
}
|
|
9836
10041
|
const message = JSON.stringify({ type, data });
|
|
9837
10042
|
this.transport.send(message, true);
|
|
@@ -9874,14 +10079,7 @@ var GameRoomTransport = class {
|
|
|
9874
10079
|
});
|
|
9875
10080
|
break;
|
|
9876
10081
|
case "error":
|
|
9877
|
-
this.handlers.onError?.(
|
|
9878
|
-
code: msg.code || "UNKNOWN",
|
|
9879
|
-
message: msg.message || "Unknown error",
|
|
9880
|
-
phase: msg.phase,
|
|
9881
|
-
feature: msg.feature,
|
|
9882
|
-
roomId: msg.room_id,
|
|
9883
|
-
scriptId: msg.script_id
|
|
9884
|
-
});
|
|
10082
|
+
this.handlers.onError?.(parseGameError(msg));
|
|
9885
10083
|
break;
|
|
9886
10084
|
case "room_stale":
|
|
9887
10085
|
if (this.handlers.onRoomStale) {
|
|
@@ -10092,6 +10290,7 @@ var index_default = ConnectBase;
|
|
|
10092
10290
|
EndpointAPI,
|
|
10093
10291
|
GameAPI,
|
|
10094
10292
|
GameConfigAPI,
|
|
10293
|
+
GameError,
|
|
10095
10294
|
GameRoom,
|
|
10096
10295
|
GameRoomTransport,
|
|
10097
10296
|
NativeAPI,
|