connectbase-client 3.13.0 → 3.14.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/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
- resolve(data.initial_state);
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(new Error(msg.data.message));
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(new Error(msg.data.message));
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 Error("Not in a room"));
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(new Error(msg.data.message));
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 Error("Not in a room");
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 Error("Not in a room");
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 Error("Not in a room"));
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(new Error(msg.data.message));
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(new Error(msg.data.message));
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(new Error(msg.data.message));
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 Error(`Request '${type}' timed out after ${timeoutMs}ms`);
6604
- onError?.(err);
6605
- this.handlers.onError?.({
6674
+ const err = new GameError({
6606
6675
  code: "TIMEOUT",
6607
- message: err.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 {
@@ -7111,9 +7180,26 @@ var GameAPI = class {
7111
7180
  );
7112
7181
  }
7113
7182
  /**
7114
- * 스크립트 비활성화 (status=disabled). 코드/버전은 보존.
7183
+ * 스크립트 비활성화 (status=disabled, ActiveVersion=0). 코드/모든 버전은 보존되며
7184
+ * 이후 {@link activateScript} 로 재활성화할 수 있다.
7185
+ *
7186
+ * v3.14.0 BREAKING — 이전엔 `disableScript` 가 `DELETE /scripts/:name` 을 호출해
7187
+ * Disable 매핑이었으나, server 측 `DELETE` 가 hard-delete 로 분리됨에 따라
7188
+ * `POST /scripts/:name/deactivate` 로 endpoint 이동. 메서드명도 의도 명확화 위해 rename.
7189
+ */
7190
+ async deactivateScript(appId, name) {
7191
+ await this.http.post(`/v1/game/${appId}/scripts/${name}/deactivate`, {});
7192
+ }
7193
+ /**
7194
+ * 스크립트 영구 삭제 (hard-delete) — 메타 + 모든 버전 영구 제거. 복구 불가.
7195
+ *
7196
+ * 잘못 업로드된 스크립트나 더 이상 사용하지 않는 슬롯을 정리할 때 사용. 단순 비활성화는
7197
+ * {@link deactivateScript} 를 사용한다 (코드 보존, 재활성화 가능).
7198
+ *
7199
+ * v3.14.0 신규 — server 측 `DELETE /scripts/:name` 의 의미가 Disable → hard-delete 로
7200
+ * 변경된 것에 대응.
7115
7201
  */
7116
- async disableScript(appId, name) {
7202
+ async deleteScript(appId, name) {
7117
7203
  await this.http.delete(`/v1/game/${appId}/scripts/${name}`);
7118
7204
  }
7119
7205
  };
@@ -9420,7 +9506,7 @@ var WebTransportTransport = class {
9420
9506
  }
9421
9507
  send(data, reliable = true) {
9422
9508
  if (!this.transport) {
9423
- throw new Error("Not connected");
9509
+ throw new GameError({ code: "NOT_CONNECTED", message: "Not connected" });
9424
9510
  }
9425
9511
  const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
9426
9512
  if (reliable) {
@@ -9511,7 +9597,7 @@ var WebSocketTransport = class {
9511
9597
  }
9512
9598
  send(data, _reliable) {
9513
9599
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
9514
- throw new Error("Not connected");
9600
+ throw new GameError({ code: "NOT_CONNECTED", message: "Not connected" });
9515
9601
  }
9516
9602
  if (typeof data === "string") {
9517
9603
  this.ws.send(data);
@@ -9536,6 +9622,8 @@ var GameRoomTransport = class {
9536
9622
  this.actionSequence = 0;
9537
9623
  this._roomId = null;
9538
9624
  this._state = null;
9625
+ this._scriptName = null;
9626
+ this._scriptVersion = null;
9539
9627
  this._isConnected = false;
9540
9628
  this._connectionStatus = "disconnected";
9541
9629
  this._lastError = null;
@@ -9588,6 +9676,14 @@ var GameRoomTransport = class {
9588
9676
  get isConnected() {
9589
9677
  return this._isConnected;
9590
9678
  }
9679
+ /** Attached lua script 이름 — createRoom 이후 server 가 echo 한 값. */
9680
+ get scriptName() {
9681
+ return this._scriptName;
9682
+ }
9683
+ /** Attached lua script 의 active version. */
9684
+ get scriptVersion() {
9685
+ return this._scriptVersion;
9686
+ }
9591
9687
  /**
9592
9688
  * Connection state information
9593
9689
  */
@@ -9640,7 +9736,7 @@ var GameRoomTransport = class {
9640
9736
  const onError = (error) => {
9641
9737
  this._connectionStatus = "error";
9642
9738
  this._lastError = error;
9643
- this.handlers.onError?.({ code: "CONNECTION_ERROR", message: error.message });
9739
+ this.handlers.onError?.(new GameError({ code: "CONNECTION_ERROR", message: error.message }));
9644
9740
  };
9645
9741
  if (useWebTransport) {
9646
9742
  try {
@@ -9702,18 +9798,35 @@ var GameRoomTransport = class {
9702
9798
  this._state = null;
9703
9799
  }
9704
9800
  /**
9705
- * Create a new room
9801
+ * Create a new room (호환 시그니처) — 초기 상태만 반환.
9802
+ *
9803
+ * Attached script 메타가 필요하면 {@link createRoomDetailed} 또는 인스턴스 getter
9804
+ * (`transport.scriptName`, `transport.scriptVersion`) 를 사용.
9706
9805
  */
9707
9806
  async createRoom(config = {}) {
9807
+ const r = await this.createRoomDetailed(config);
9808
+ return r.state;
9809
+ }
9810
+ /**
9811
+ * Create a new room — server 가 attach 한 lua script 메타 함께 반환.
9812
+ */
9813
+ async createRoomDetailed(config = {}) {
9708
9814
  return new Promise((resolve, reject) => {
9709
9815
  const handler = (msg) => {
9710
9816
  if (msg.type === "room_created") {
9711
9817
  const data = msg.data;
9712
9818
  this._roomId = data.room_id;
9713
9819
  this._state = data.initial_state;
9714
- resolve(data.initial_state);
9820
+ this._scriptName = data.script_name ?? null;
9821
+ this._scriptVersion = data.script_version ?? null;
9822
+ resolve({
9823
+ roomId: data.room_id,
9824
+ state: data.initial_state,
9825
+ scriptName: data.script_name,
9826
+ scriptVersion: data.script_version
9827
+ });
9715
9828
  } else if (msg.type === "error") {
9716
- reject(new Error(msg.data.message));
9829
+ reject(parseGameError(msg));
9717
9830
  }
9718
9831
  };
9719
9832
  this.sendWithHandler("create_room", toCreateRoomWire(config), handler);
@@ -9729,9 +9842,11 @@ var GameRoomTransport = class {
9729
9842
  const data = msg.data;
9730
9843
  this._roomId = data.room_id;
9731
9844
  this._state = data.initial_state;
9845
+ this._scriptName = null;
9846
+ this._scriptVersion = null;
9732
9847
  resolve(data.initial_state);
9733
9848
  } else if (msg.type === "error") {
9734
- reject(new Error(msg.data.message));
9849
+ reject(parseGameError(msg));
9735
9850
  }
9736
9851
  };
9737
9852
  this.sendWithHandler("join_room", { room_id: roomId, metadata }, handler);
@@ -9743,16 +9858,18 @@ var GameRoomTransport = class {
9743
9858
  async leaveRoom() {
9744
9859
  return new Promise((resolve, reject) => {
9745
9860
  if (!this._roomId) {
9746
- reject(new Error("Not in a room"));
9861
+ reject(new GameError({ code: "NOT_IN_ROOM", message: "Not in a room" }));
9747
9862
  return;
9748
9863
  }
9749
9864
  const handler = (msg) => {
9750
9865
  if (msg.type === "room_left") {
9751
9866
  this._roomId = null;
9752
9867
  this._state = null;
9868
+ this._scriptName = null;
9869
+ this._scriptVersion = null;
9753
9870
  resolve();
9754
9871
  } else if (msg.type === "error") {
9755
- reject(new Error(msg.data.message));
9872
+ reject(parseGameError(msg));
9756
9873
  }
9757
9874
  };
9758
9875
  this.sendWithHandler("leave_room", {}, handler);
@@ -9764,7 +9881,7 @@ var GameRoomTransport = class {
9764
9881
  */
9765
9882
  sendAction(action, reliable = false) {
9766
9883
  if (!this._roomId) {
9767
- throw new Error("Not in a room");
9884
+ throw new GameError({ code: "NOT_IN_ROOM", message: "Not in a room" });
9768
9885
  }
9769
9886
  const message = JSON.stringify({
9770
9887
  type: "action",
@@ -9783,7 +9900,7 @@ var GameRoomTransport = class {
9783
9900
  */
9784
9901
  sendChat(message) {
9785
9902
  if (!this._roomId) {
9786
- throw new Error("Not in a room");
9903
+ throw new GameError({ code: "NOT_IN_ROOM", message: "Not in a room" });
9787
9904
  }
9788
9905
  this.send("chat", { message });
9789
9906
  }
@@ -9793,7 +9910,7 @@ var GameRoomTransport = class {
9793
9910
  async requestState() {
9794
9911
  return new Promise((resolve, reject) => {
9795
9912
  if (!this._roomId) {
9796
- reject(new Error("Not in a room"));
9913
+ reject(new GameError({ code: "NOT_IN_ROOM", message: "Not in a room" }));
9797
9914
  return;
9798
9915
  }
9799
9916
  const handler = (msg) => {
@@ -9802,7 +9919,7 @@ var GameRoomTransport = class {
9802
9919
  this._state = state;
9803
9920
  resolve(state);
9804
9921
  } else if (msg.type === "error") {
9805
- reject(new Error(msg.data.message));
9922
+ reject(parseGameError(msg));
9806
9923
  }
9807
9924
  };
9808
9925
  this.sendWithHandler("get_state", {}, handler);
@@ -9822,7 +9939,7 @@ var GameRoomTransport = class {
9822
9939
  this.handlers.onPong?.(pong);
9823
9940
  resolve(rtt);
9824
9941
  } else if (msg.type === "error") {
9825
- reject(new Error(msg.data.message));
9942
+ reject(parseGameError(msg));
9826
9943
  }
9827
9944
  };
9828
9945
  this.sendWithHandler("ping", { timestamp }, handler);
@@ -9831,7 +9948,7 @@ var GameRoomTransport = class {
9831
9948
  // Private methods
9832
9949
  send(type, data) {
9833
9950
  if (!this.transport?.isConnected()) {
9834
- throw new Error("Not connected");
9951
+ throw new GameError({ code: "NOT_CONNECTED", message: "Not connected" });
9835
9952
  }
9836
9953
  const message = JSON.stringify({ type, data });
9837
9954
  this.transport.send(message, true);
@@ -9874,14 +9991,7 @@ var GameRoomTransport = class {
9874
9991
  });
9875
9992
  break;
9876
9993
  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
- });
9994
+ this.handlers.onError?.(parseGameError(msg));
9885
9995
  break;
9886
9996
  case "room_stale":
9887
9997
  if (this.handlers.onRoomStale) {
@@ -10092,6 +10202,7 @@ var index_default = ConnectBase;
10092
10202
  EndpointAPI,
10093
10203
  GameAPI,
10094
10204
  GameConfigAPI,
10205
+ GameError,
10095
10206
  GameRoom,
10096
10207
  GameRoomTransport,
10097
10208
  NativeAPI,