connectbase-client 6.13.0 → 6.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/CHANGELOG.md +27 -0
- package/dist/cli.js +1 -1
- package/dist/connect-base.umd.js +5 -5
- package/dist/index.d.mts +50 -2
- package/dist/index.d.ts +50 -2
- package/dist/index.js +325 -49
- package/dist/index.mjs +324 -49
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -41,6 +41,7 @@ __export(index_exports, {
|
|
|
41
41
|
RolesAPI: () => RolesAPI,
|
|
42
42
|
SessionManager: () => SessionManager,
|
|
43
43
|
VideoProcessingError: () => VideoProcessingError,
|
|
44
|
+
WsTicketError: () => WsTicketError,
|
|
44
45
|
default: () => index_default,
|
|
45
46
|
detectInAppBrowser: () => detectInAppBrowser,
|
|
46
47
|
escapeToExternalBrowser: () => escapeToExternalBrowser,
|
|
@@ -170,9 +171,9 @@ var AdsAPI = class {
|
|
|
170
171
|
|
|
171
172
|
// src/types/error.ts
|
|
172
173
|
var ApiError = class extends Error {
|
|
173
|
-
constructor(
|
|
174
|
+
constructor(statusCode2, message, code, details) {
|
|
174
175
|
super(message);
|
|
175
|
-
this.statusCode =
|
|
176
|
+
this.statusCode = statusCode2;
|
|
176
177
|
this.name = "ApiError";
|
|
177
178
|
this.code = code;
|
|
178
179
|
this.details = details;
|
|
@@ -308,7 +309,7 @@ var RealtimeConnectionError = class extends Error {
|
|
|
308
309
|
}
|
|
309
310
|
};
|
|
310
311
|
function isRetryableRealtimeRejection(code) {
|
|
311
|
-
return code === "connection_limit_exceeded" || code === "server_at_capacity" || code === "rate_limited" || code === "auth_unavailable" || code === "internal_error";
|
|
312
|
+
return code === "connection_limit_exceeded" || code === "server_at_capacity" || code === "rate_limited" || code === "auth_unavailable" || code === "internal_error" || code === "invalid_ws_ticket" || code === "ws_ticket_unavailable";
|
|
312
313
|
}
|
|
313
314
|
var RealtimeRequestError = class extends Error {
|
|
314
315
|
constructor(init) {
|
|
@@ -3698,9 +3699,107 @@ var HttpClient = class {
|
|
|
3698
3699
|
}
|
|
3699
3700
|
};
|
|
3700
3701
|
|
|
3702
|
+
// src/core/ws-ticket.ts
|
|
3703
|
+
var WsTicketError = class extends Error {
|
|
3704
|
+
constructor(init) {
|
|
3705
|
+
super(
|
|
3706
|
+
init.message || init.code || `WebSocket ticket request failed (${init.status})`
|
|
3707
|
+
);
|
|
3708
|
+
this.name = "WsTicketError";
|
|
3709
|
+
this.status = init.status;
|
|
3710
|
+
this.code = init.code || "ws_ticket_rejected";
|
|
3711
|
+
this.retryAfterMs = init.retryAfterMs;
|
|
3712
|
+
}
|
|
3713
|
+
};
|
|
3714
|
+
var DEFAULT_TICKET_TIMEOUT_MS = 5e3;
|
|
3715
|
+
var unsupported = /* @__PURE__ */ new Set();
|
|
3716
|
+
function supportKey(url, kind) {
|
|
3717
|
+
try {
|
|
3718
|
+
const u = new URL(url);
|
|
3719
|
+
return `${u.origin}|${kind}`;
|
|
3720
|
+
} catch {
|
|
3721
|
+
return `${url}|${kind}`;
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
async function fetchWsTicket(url, kind, credential, options = {}) {
|
|
3725
|
+
const key = supportKey(url, kind);
|
|
3726
|
+
if (unsupported.has(key)) return null;
|
|
3727
|
+
const headers = {};
|
|
3728
|
+
if (credential.kind === "access_token") {
|
|
3729
|
+
headers.Authorization = `Bearer ${credential.value}`;
|
|
3730
|
+
} else {
|
|
3731
|
+
headers["X-Public-Key"] = credential.value;
|
|
3732
|
+
}
|
|
3733
|
+
const { signal, cleanup } = createTimeoutController({
|
|
3734
|
+
timeout: Math.max(
|
|
3735
|
+
1,
|
|
3736
|
+
Math.min(
|
|
3737
|
+
options.timeoutMs ?? DEFAULT_TICKET_TIMEOUT_MS,
|
|
3738
|
+
DEFAULT_TICKET_TIMEOUT_MS
|
|
3739
|
+
)
|
|
3740
|
+
),
|
|
3741
|
+
signal: options.signal
|
|
3742
|
+
});
|
|
3743
|
+
let response;
|
|
3744
|
+
try {
|
|
3745
|
+
response = await withAbort(
|
|
3746
|
+
fetch(url, { method: "POST", headers, credentials: "omit", signal }),
|
|
3747
|
+
signal
|
|
3748
|
+
);
|
|
3749
|
+
} catch (err) {
|
|
3750
|
+
cleanup();
|
|
3751
|
+
if (options.signal?.aborted) throw err;
|
|
3752
|
+
return null;
|
|
3753
|
+
}
|
|
3754
|
+
try {
|
|
3755
|
+
if (response.ok) {
|
|
3756
|
+
const body2 = await response.json().catch(() => null);
|
|
3757
|
+
return typeof body2?.ticket === "string" && body2.ticket !== "" ? body2.ticket : null;
|
|
3758
|
+
}
|
|
3759
|
+
if (response.status === 404 || response.status === 405 || response.status === 501) {
|
|
3760
|
+
unsupported.add(key);
|
|
3761
|
+
return null;
|
|
3762
|
+
}
|
|
3763
|
+
if (response.status >= 500) return null;
|
|
3764
|
+
const body = await response.json().catch(() => null);
|
|
3765
|
+
const raw = typeof body?.error === "string" ? body.error : void 0;
|
|
3766
|
+
const isCode = raw !== void 0 && /^[a-z0-9_]+$/.test(raw);
|
|
3767
|
+
const code = isCode ? raw : statusCode(response.status);
|
|
3768
|
+
const message = typeof body?.message === "string" ? body.message : raw;
|
|
3769
|
+
const retryAfterMs = typeof body?.retry_after_ms === "number" && body.retry_after_ms > 0 ? body.retry_after_ms : void 0;
|
|
3770
|
+
throw new WsTicketError({
|
|
3771
|
+
status: response.status,
|
|
3772
|
+
code,
|
|
3773
|
+
message,
|
|
3774
|
+
retryAfterMs
|
|
3775
|
+
});
|
|
3776
|
+
} finally {
|
|
3777
|
+
cleanup();
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
function statusCode(status) {
|
|
3781
|
+
switch (status) {
|
|
3782
|
+
case 401:
|
|
3783
|
+
return "unauthorized";
|
|
3784
|
+
case 402:
|
|
3785
|
+
return "quota_exceeded";
|
|
3786
|
+
case 403:
|
|
3787
|
+
return "forbidden";
|
|
3788
|
+
case 429:
|
|
3789
|
+
return "rate_limited";
|
|
3790
|
+
default:
|
|
3791
|
+
return "ws_ticket_rejected";
|
|
3792
|
+
}
|
|
3793
|
+
}
|
|
3794
|
+
function ticketUrl(base, path) {
|
|
3795
|
+
const httpBase = base.replace(/^ws(s?):\/\//, "http$1://").replace(/\/+$/, "");
|
|
3796
|
+
return `${httpBase}${path}`;
|
|
3797
|
+
}
|
|
3798
|
+
|
|
3701
3799
|
// src/api/database.ts
|
|
3702
3800
|
var REALTIME_TOKEN_MIN_VALIDITY_SEC = 600;
|
|
3703
3801
|
var REALTIME_CONNECT_TOKEN_MIN_VALIDITY_SEC = 60;
|
|
3802
|
+
var REALTIME_CONNECT_TIMEOUT_MS = 15e3;
|
|
3704
3803
|
var REALTIME_AUTH_REFRESH_RETRY_MS = 3e4;
|
|
3705
3804
|
var REALTIME_CLOSING_NOTICE_CODES = /* @__PURE__ */ new Set([
|
|
3706
3805
|
"TOKEN_EXPIRED",
|
|
@@ -4645,15 +4744,52 @@ var DatabaseAPI = class {
|
|
|
4645
4744
|
);
|
|
4646
4745
|
if (fresh) opts.accessToken = fresh;
|
|
4647
4746
|
}
|
|
4648
|
-
|
|
4747
|
+
let ticket;
|
|
4748
|
+
try {
|
|
4749
|
+
ticket = await this.realtimeTicket();
|
|
4750
|
+
} catch (e) {
|
|
4751
|
+
if (e instanceof WsTicketError) {
|
|
4752
|
+
if (e.status === 429 && this.realtimeOptions) {
|
|
4753
|
+
this.attemptRealtimeReconnect();
|
|
4754
|
+
} else {
|
|
4755
|
+
this.setRealtimeState("disconnected");
|
|
4756
|
+
}
|
|
4757
|
+
const err = new RealtimeConnectionError({
|
|
4758
|
+
code: e.code,
|
|
4759
|
+
message: e.message,
|
|
4760
|
+
closeCode: 4e3 + e.status,
|
|
4761
|
+
retryAfterMs: e.retryAfterMs
|
|
4762
|
+
});
|
|
4763
|
+
this.realtimeOnError?.(err);
|
|
4764
|
+
throw err;
|
|
4765
|
+
}
|
|
4766
|
+
throw e;
|
|
4767
|
+
}
|
|
4768
|
+
return this.doRealtimeConnect(ticket);
|
|
4769
|
+
}
|
|
4770
|
+
/**
|
|
4771
|
+
* 연결 시도 한 번에 쓸 1회용 티켓을 받는다(core/ws-ticket.ts). 브라우저 WebSocket 은 헤더를 못 붙여 예전에는 멤버 토큰을
|
|
4772
|
+
* URL(`?access_token=`)에 실었다 — 이제 헤더로 티켓을 받아 `?ticket=` 만 싣는다. 서버가 티켓을 모르거나(구버전) 잠시 못
|
|
4773
|
+
* 주면 null 이고 예전처럼 붙는다. cross-app OAuth 토큰도 같은 경로다(서버가 티켓에서 토큰을 되살려 같은 검증을 한다).
|
|
4774
|
+
*/
|
|
4775
|
+
async realtimeTicket() {
|
|
4776
|
+
const opts = this.realtimeOptions;
|
|
4777
|
+
if (!opts?.accessToken) return null;
|
|
4778
|
+
const baseUrl = opts.dataServerUrl || this.http.getBaseUrl();
|
|
4779
|
+
return fetchWsTicket(
|
|
4780
|
+
ticketUrl(baseUrl, "/v1/database/realtime/ws-ticket"),
|
|
4781
|
+
"data.realtime",
|
|
4782
|
+
{ kind: "access_token", value: opts.accessToken },
|
|
4783
|
+
{ timeoutMs: REALTIME_CONNECT_TIMEOUT_MS }
|
|
4784
|
+
);
|
|
4649
4785
|
}
|
|
4650
|
-
doRealtimeConnect() {
|
|
4786
|
+
doRealtimeConnect(ticket = null) {
|
|
4651
4787
|
if (!this.realtimeOptions)
|
|
4652
4788
|
return Promise.reject(new Error("No realtime options"));
|
|
4653
4789
|
this.setRealtimeState("connecting");
|
|
4654
4790
|
const baseUrl = this.realtimeOptions.dataServerUrl || this.http.getBaseUrl();
|
|
4655
4791
|
const wsUrl = baseUrl.replace(/^http/, "ws");
|
|
4656
|
-
let url = `${wsUrl}/v1/database/realtime/ws?access_token=${encodeURIComponent(this.realtimeOptions.accessToken)}`;
|
|
4792
|
+
let url = ticket ? `${wsUrl}/v1/database/realtime/ws?ticket=${encodeURIComponent(ticket)}` : `${wsUrl}/v1/database/realtime/ws?access_token=${encodeURIComponent(this.realtimeOptions.accessToken)}`;
|
|
4657
4793
|
this.realtimeTokenRefresh = this.http.canRefreshMemberToken(
|
|
4658
4794
|
this.realtimeOptions.accessToken
|
|
4659
4795
|
);
|
|
@@ -4672,7 +4808,7 @@ var DatabaseAPI = class {
|
|
|
4672
4808
|
this.setRealtimeState("disconnected");
|
|
4673
4809
|
reject(new Error("Connection timeout"));
|
|
4674
4810
|
}
|
|
4675
|
-
},
|
|
4811
|
+
}, REALTIME_CONNECT_TIMEOUT_MS);
|
|
4676
4812
|
this.realtimeWs.onopen = () => {
|
|
4677
4813
|
if (!settled) {
|
|
4678
4814
|
settled = true;
|
|
@@ -5213,7 +5349,7 @@ var EndpointAPI = class {
|
|
|
5213
5349
|
);
|
|
5214
5350
|
}
|
|
5215
5351
|
const baseUrl = this.http.getBaseUrl().replace(/\/+$/, "");
|
|
5216
|
-
const
|
|
5352
|
+
const ticketUrl2 = `${baseUrl}/v1/proxy/${encodeURIComponent(label)}/ws-ticket`;
|
|
5217
5353
|
const ticketHeaders = new Headers();
|
|
5218
5354
|
const pk = this.http.getPublicKey();
|
|
5219
5355
|
if (!pk) {
|
|
@@ -5222,7 +5358,7 @@ var EndpointAPI = class {
|
|
|
5222
5358
|
);
|
|
5223
5359
|
}
|
|
5224
5360
|
ticketHeaders.set("X-Public-Key", pk);
|
|
5225
|
-
const ticketRes = await fetch(
|
|
5361
|
+
const ticketRes = await fetch(ticketUrl2, {
|
|
5226
5362
|
method: "POST",
|
|
5227
5363
|
headers: ticketHeaders,
|
|
5228
5364
|
signal: opts.signal
|
|
@@ -5920,20 +6056,54 @@ var GameRoom = class {
|
|
|
5920
6056
|
* 게임 서버에 연결
|
|
5921
6057
|
*/
|
|
5922
6058
|
connect(roomId) {
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
6059
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
6060
|
+
return Promise.resolve();
|
|
6061
|
+
}
|
|
6062
|
+
if (!this.config.appId) {
|
|
6063
|
+
return Promise.reject(
|
|
6064
|
+
new Error(
|
|
6065
|
+
"cb.game: appId is required to connect. Pass it to `new ConnectBase({ appId })` or per-client via `cb.game.createClient({ appId, clientId })`."
|
|
6066
|
+
)
|
|
6067
|
+
);
|
|
6068
|
+
}
|
|
6069
|
+
if (!this.config.clientId) {
|
|
6070
|
+
return Promise.reject(
|
|
6071
|
+
new Error(
|
|
6072
|
+
"cb.game: clientId is required to build a game connection URL. Pass it via `cb.game.createClient({ appId, clientId })`."
|
|
6073
|
+
)
|
|
6074
|
+
);
|
|
6075
|
+
}
|
|
6076
|
+
return this.connectionTicket().then(
|
|
6077
|
+
(ticket) => this.openSocket(roomId, ticket)
|
|
6078
|
+
);
|
|
6079
|
+
}
|
|
6080
|
+
/**
|
|
6081
|
+
* 연결 시도 한 번에 쓸 1회용 티켓을 받는다(core/ws-ticket.ts). 브라우저 WebSocket 은 헤더를 못 붙여 예전에는 멤버
|
|
6082
|
+
* 토큰(`?token=`)과 공개 키(`?public_key=`)를 URL 에 실었다 — 이제 헤더로 티켓을 받아 `?ticket=` 만 싣는다. 서버가
|
|
6083
|
+
* 티켓을 모르거나(구버전) 잠시 못 주면 null 이고 예전처럼 붙는다. 발급이 자격증명을 거부하면 {@link GameError} 로 던진다.
|
|
6084
|
+
*/
|
|
6085
|
+
async connectionTicket() {
|
|
6086
|
+
const { accessToken, publicKey, appId, gameServerUrl } = this.config;
|
|
6087
|
+
if (!accessToken && !publicKey || !appId || !gameServerUrl) return null;
|
|
6088
|
+
try {
|
|
6089
|
+
return await fetchWsTicket(
|
|
6090
|
+
ticketUrl(
|
|
6091
|
+
gameServerUrl,
|
|
6092
|
+
`/v1/game/${encodeURIComponent(appId)}/ws-ticket`
|
|
6093
|
+
),
|
|
6094
|
+
"game.ws",
|
|
6095
|
+
accessToken ? { kind: "access_token", value: accessToken } : { kind: "public_key", value: publicKey }
|
|
6096
|
+
);
|
|
6097
|
+
} catch (e) {
|
|
6098
|
+
if (e instanceof WsTicketError) {
|
|
6099
|
+
throw new GameError({ code: e.code, message: e.message });
|
|
5935
6100
|
}
|
|
5936
|
-
|
|
6101
|
+
throw e;
|
|
6102
|
+
}
|
|
6103
|
+
}
|
|
6104
|
+
openSocket(roomId, ticket) {
|
|
6105
|
+
return new Promise((resolve, reject) => {
|
|
6106
|
+
const url = this.buildConnectionUrl(roomId, ticket);
|
|
5937
6107
|
this.ws = new WebSocket(url);
|
|
5938
6108
|
const onOpen = () => {
|
|
5939
6109
|
this._isConnected = true;
|
|
@@ -6189,7 +6359,7 @@ var GameRoom = class {
|
|
|
6189
6359
|
});
|
|
6190
6360
|
}
|
|
6191
6361
|
// Private methods
|
|
6192
|
-
buildConnectionUrl(roomId) {
|
|
6362
|
+
buildConnectionUrl(roomId, ticket) {
|
|
6193
6363
|
const baseUrl = this.config.gameServerUrl;
|
|
6194
6364
|
const wsUrl = baseUrl.replace(/^http/, "ws");
|
|
6195
6365
|
const params = new URLSearchParams();
|
|
@@ -6202,11 +6372,15 @@ var GameRoom = class {
|
|
|
6202
6372
|
if (roomId) {
|
|
6203
6373
|
params.set("room_id", roomId);
|
|
6204
6374
|
}
|
|
6205
|
-
if (
|
|
6206
|
-
params.set("
|
|
6207
|
-
}
|
|
6208
|
-
|
|
6209
|
-
|
|
6375
|
+
if (ticket) {
|
|
6376
|
+
params.set("ticket", ticket);
|
|
6377
|
+
} else {
|
|
6378
|
+
if (this.config.publicKey) {
|
|
6379
|
+
params.set("public_key", this.config.publicKey);
|
|
6380
|
+
}
|
|
6381
|
+
if (this.config.accessToken) {
|
|
6382
|
+
params.set("token", this.config.accessToken);
|
|
6383
|
+
}
|
|
6210
6384
|
}
|
|
6211
6385
|
if (!this.config.appId) {
|
|
6212
6386
|
throw new Error(
|
|
@@ -10063,7 +10237,14 @@ var ConnectCancelledError = class extends Error {
|
|
|
10063
10237
|
}
|
|
10064
10238
|
};
|
|
10065
10239
|
var MAX_HANDSHAKE_RETRIES = 2;
|
|
10066
|
-
var RETRY_IN_CONNECT_CODES = /* @__PURE__ */ new Set([
|
|
10240
|
+
var RETRY_IN_CONNECT_CODES = /* @__PURE__ */ new Set([
|
|
10241
|
+
"rate_limited",
|
|
10242
|
+
"auth_unavailable",
|
|
10243
|
+
// 1회용 티켓이 업그레이드에서 거부됐다(만료, 이미 쓰임, 저장소 일시 장애). 다음 시도는 새 티켓을 받거나, 저장소가
|
|
10244
|
+
// 없으면 예전 방식으로 붙는다(core/ws-ticket.ts).
|
|
10245
|
+
"invalid_ws_ticket",
|
|
10246
|
+
"ws_ticket_unavailable"
|
|
10247
|
+
]);
|
|
10067
10248
|
var CONNECTION_TOKEN_MIN_VALIDITY_SEC = 600;
|
|
10068
10249
|
var CONNECT_TOKEN_MIN_VALIDITY_SEC = 60;
|
|
10069
10250
|
var AUTH_REFRESH_RETRY_MS = 3e4;
|
|
@@ -11178,7 +11359,10 @@ var RealtimeAPI = class {
|
|
|
11178
11359
|
for (let attempt = 1; ; attempt++) {
|
|
11179
11360
|
try {
|
|
11180
11361
|
await this.prepareConnectToken();
|
|
11181
|
-
await this.
|
|
11362
|
+
const ticket = await this.connectTicket(
|
|
11363
|
+
Math.max(deadline - Date.now(), 1)
|
|
11364
|
+
);
|
|
11365
|
+
await this.doConnect(Math.max(deadline - Date.now(), 1), ticket);
|
|
11182
11366
|
return;
|
|
11183
11367
|
} catch (e) {
|
|
11184
11368
|
let wait = null;
|
|
@@ -11221,7 +11405,39 @@ var RealtimeAPI = class {
|
|
|
11221
11405
|
this.initialConnecting = false;
|
|
11222
11406
|
}
|
|
11223
11407
|
}
|
|
11224
|
-
|
|
11408
|
+
/**
|
|
11409
|
+
* 연결 시도 한 번에 쓸 1회용 티켓을 받는다(core/ws-ticket.ts). 브라우저 WebSocket 은 헤더를 못 붙여 예전에는 멤버
|
|
11410
|
+
* 토큰과 공개 키를 URL 에 실었다 — 이제 헤더로 티켓을 받아 `?ticket=` 만 싣는다. 티켓은 한 번만 쓰이므로 시도마다
|
|
11411
|
+
* 새로 받는다.
|
|
11412
|
+
*
|
|
11413
|
+
* 서버가 티켓을 모르거나(구버전) 잠시 못 주면 null 이고, doConnect 가 예전처럼 자격증명을 URL 에 싣는다. 발급이
|
|
11414
|
+
* 자격증명을 거부하면 업그레이드 거부와 같은 {@link RealtimeConnectionError} 로 던진다 — 재시도 규칙이 같다.
|
|
11415
|
+
*/
|
|
11416
|
+
async connectTicket(timeoutMs) {
|
|
11417
|
+
const accessToken = this.options.accessToken;
|
|
11418
|
+
const publicKey = accessToken ? void 0 : this.http.getPublicKey();
|
|
11419
|
+
if (!accessToken && !publicKey) return null;
|
|
11420
|
+
try {
|
|
11421
|
+
return await fetchWsTicket(
|
|
11422
|
+
ticketUrl(this.socketUrl, "/v1/realtime/ws-ticket"),
|
|
11423
|
+
"socket.realtime",
|
|
11424
|
+
accessToken ? { kind: "access_token", value: accessToken } : { kind: "public_key", value: publicKey },
|
|
11425
|
+
{ timeoutMs }
|
|
11426
|
+
);
|
|
11427
|
+
} catch (e) {
|
|
11428
|
+
if (e instanceof WsTicketError) {
|
|
11429
|
+
this.log(`WebSocket ticket rejected: ${e.code} (${e.status})`);
|
|
11430
|
+
throw new RealtimeConnectionError({
|
|
11431
|
+
code: e.code,
|
|
11432
|
+
message: e.message,
|
|
11433
|
+
closeCode: 4e3 + e.status,
|
|
11434
|
+
retryAfterMs: e.retryAfterMs
|
|
11435
|
+
});
|
|
11436
|
+
}
|
|
11437
|
+
throw e;
|
|
11438
|
+
}
|
|
11439
|
+
}
|
|
11440
|
+
async doConnect(timeoutMs = this.options.timeout, ticket = null) {
|
|
11225
11441
|
return new Promise((resolve, reject) => {
|
|
11226
11442
|
this.state = "connecting";
|
|
11227
11443
|
this.notifyStateChange();
|
|
@@ -11230,9 +11446,10 @@ var RealtimeAPI = class {
|
|
|
11230
11446
|
const usingDirect = !!directWsBase;
|
|
11231
11447
|
const wsUrl = (directWsBase ?? this.socketUrl).replace(/^http/, "ws");
|
|
11232
11448
|
let url;
|
|
11449
|
+
const auth = ticket ? `ticket=${encodeURIComponent(ticket)}` : void 0;
|
|
11233
11450
|
this.tokenRefreshNegotiated = false;
|
|
11234
11451
|
if (this.options.accessToken) {
|
|
11235
|
-
url = `${wsUrl}/v1/realtime/auth
|
|
11452
|
+
url = `${wsUrl}/v1/realtime/auth?${auth ?? `access_token=${encodeURIComponent(this.options.accessToken)}`}&client_id=${this.clientId}`;
|
|
11236
11453
|
if (this.http.canRefreshMemberToken(this.options.accessToken)) {
|
|
11237
11454
|
this.tokenRefreshNegotiated = true;
|
|
11238
11455
|
url += "&token_refresh=1";
|
|
@@ -11248,7 +11465,7 @@ var RealtimeAPI = class {
|
|
|
11248
11465
|
reject(error);
|
|
11249
11466
|
return;
|
|
11250
11467
|
}
|
|
11251
|
-
url = `${wsUrl}/v1/realtime/auth
|
|
11468
|
+
url = `${wsUrl}/v1/realtime/auth?${auth ?? `public_key=${encodeURIComponent(publicKey)}`}&client_id=${this.clientId}`;
|
|
11252
11469
|
this.log("Using API Key authentication");
|
|
11253
11470
|
}
|
|
11254
11471
|
if (this.userId) {
|
|
@@ -11728,7 +11945,9 @@ var RealtimeAPI = class {
|
|
|
11728
11945
|
try {
|
|
11729
11946
|
await this.prepareConnectToken();
|
|
11730
11947
|
if (this.state !== "reconnecting") return;
|
|
11731
|
-
await this.
|
|
11948
|
+
const ticket = await this.connectTicket(this.options.timeout);
|
|
11949
|
+
if (this.state !== "reconnecting") return;
|
|
11950
|
+
await this.doConnect(void 0, ticket);
|
|
11732
11951
|
this.log("Reconnected successfully, restoring subscriptions...");
|
|
11733
11952
|
const snapshot = this.pendingRestore;
|
|
11734
11953
|
this.pendingRestore = null;
|
|
@@ -13331,8 +13550,8 @@ var SupportAPI = class {
|
|
|
13331
13550
|
}
|
|
13332
13551
|
};
|
|
13333
13552
|
function detectSdkVersion() {
|
|
13334
|
-
if ("6.
|
|
13335
|
-
return "6.
|
|
13553
|
+
if ("6.14.0") {
|
|
13554
|
+
return "6.14.0";
|
|
13336
13555
|
}
|
|
13337
13556
|
return "unknown";
|
|
13338
13557
|
}
|
|
@@ -14301,9 +14520,35 @@ var WebRTCAPI = class _WebRTCAPI {
|
|
|
14301
14520
|
}
|
|
14302
14521
|
return this.connectWebSocket();
|
|
14303
14522
|
}
|
|
14304
|
-
connectWebSocket() {
|
|
14523
|
+
async connectWebSocket() {
|
|
14524
|
+
const before = this.wsGeneration;
|
|
14525
|
+
const ticket = await this.signalingTicket();
|
|
14526
|
+
if (this.wsGeneration !== before) {
|
|
14527
|
+
throw new Error("\uC5F0\uACB0\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4");
|
|
14528
|
+
}
|
|
14529
|
+
return this.openSignalingSocket(ticket);
|
|
14530
|
+
}
|
|
14531
|
+
/**
|
|
14532
|
+
* 연결 시도 한 번에 쓸 1회용 티켓을 받는다(core/ws-ticket.ts). 브라우저 WebSocket 은 헤더를 못 붙여 예전에는 멤버 토큰
|
|
14533
|
+
* (`?access_token=`)과 공개 키(`?public_key=`)를 URL 에 실었다 — 이제 헤더로 티켓을 받아 `?ticket=` 만 싣는다. 서버가
|
|
14534
|
+
* 티켓을 모르거나(구버전) 잠시 못 주면 null 이고 예전처럼 붙는다. 발급이 자격증명을 거부하면 그 이유로 던진다.
|
|
14535
|
+
*/
|
|
14536
|
+
async signalingTicket() {
|
|
14537
|
+
const accessToken = this.http.getAccessToken();
|
|
14538
|
+
const publicKey = accessToken ? void 0 : this.http.getPublicKey();
|
|
14539
|
+
if (!accessToken && !publicKey || !this.appId) return null;
|
|
14540
|
+
return fetchWsTicket(
|
|
14541
|
+
ticketUrl(
|
|
14542
|
+
this.webrtcUrl,
|
|
14543
|
+
`/v1/apps/${encodeURIComponent(this.appId)}/signaling/ws-ticket`
|
|
14544
|
+
),
|
|
14545
|
+
"webrtc.signaling",
|
|
14546
|
+
accessToken ? { kind: "access_token", value: accessToken } : { kind: "public_key", value: publicKey }
|
|
14547
|
+
);
|
|
14548
|
+
}
|
|
14549
|
+
openSignalingSocket(ticket) {
|
|
14305
14550
|
return new Promise((resolve, reject) => {
|
|
14306
|
-
const wsUrl = this.buildWebSocketUrl();
|
|
14551
|
+
const wsUrl = this.buildWebSocketUrl(ticket);
|
|
14307
14552
|
const generation = ++this.wsGeneration;
|
|
14308
14553
|
const ws = new WebSocket(wsUrl);
|
|
14309
14554
|
this.ws = ws;
|
|
@@ -14372,12 +14617,14 @@ var WebRTCAPI = class _WebRTCAPI {
|
|
|
14372
14617
|
ws.onerror = null;
|
|
14373
14618
|
ws.onclose = null;
|
|
14374
14619
|
}
|
|
14375
|
-
buildWebSocketUrl() {
|
|
14620
|
+
buildWebSocketUrl(ticket = null) {
|
|
14376
14621
|
const wsBase = this.webrtcUrl.replace("https://", "wss://").replace("http://", "ws://");
|
|
14377
14622
|
const publicKey = this.http.getPublicKey();
|
|
14378
14623
|
const accessToken = this.http.getAccessToken();
|
|
14379
14624
|
let authParam = "";
|
|
14380
|
-
if (
|
|
14625
|
+
if (ticket) {
|
|
14626
|
+
authParam = `ticket=${encodeURIComponent(ticket)}`;
|
|
14627
|
+
} else if (accessToken) {
|
|
14381
14628
|
authParam = `access_token=${encodeURIComponent(accessToken)}`;
|
|
14382
14629
|
} else if (publicKey) {
|
|
14383
14630
|
authParam = `public_key=${encodeURIComponent(publicKey)}`;
|
|
@@ -15035,11 +15282,35 @@ var WebSocketTransport = class {
|
|
|
15035
15282
|
this.onClose = onClose;
|
|
15036
15283
|
this.onError = onError;
|
|
15037
15284
|
}
|
|
15038
|
-
connect() {
|
|
15285
|
+
async connect() {
|
|
15286
|
+
const ticket = await this.connectionTicket();
|
|
15287
|
+
return this.openSocket(ticket);
|
|
15288
|
+
}
|
|
15289
|
+
/**
|
|
15290
|
+
* 연결 시도 한 번에 쓸 1회용 티켓(core/ws-ticket.ts). 서버가 티켓을 모르거나(구버전) 잠시 못 주면 null 이고 예전처럼
|
|
15291
|
+
* 자격증명을 URL 에 싣는다. 발급이 자격증명을 거부하면 {@link GameError} 로 던진다.
|
|
15292
|
+
*/
|
|
15293
|
+
async connectionTicket() {
|
|
15294
|
+
const { accessToken, publicKey, appId } = this.config;
|
|
15295
|
+
if (!accessToken && !publicKey || !appId) return null;
|
|
15296
|
+
const baseUrl = this.config.gameServerUrl || "wss://game.connectbase.world";
|
|
15297
|
+
try {
|
|
15298
|
+
return await fetchWsTicket(
|
|
15299
|
+
ticketUrl(baseUrl, `/v1/game/${encodeURIComponent(appId)}/ws-ticket`),
|
|
15300
|
+
"game.ws",
|
|
15301
|
+
accessToken ? { kind: "access_token", value: accessToken } : { kind: "public_key", value: publicKey }
|
|
15302
|
+
);
|
|
15303
|
+
} catch (e) {
|
|
15304
|
+
if (e instanceof WsTicketError) {
|
|
15305
|
+
throw new GameError({ code: e.code, message: e.message });
|
|
15306
|
+
}
|
|
15307
|
+
throw e;
|
|
15308
|
+
}
|
|
15309
|
+
}
|
|
15310
|
+
openSocket(ticket) {
|
|
15039
15311
|
return new Promise((resolve, reject) => {
|
|
15040
|
-
const url = this.buildUrl();
|
|
15041
15312
|
try {
|
|
15042
|
-
this.ws = new WebSocket(
|
|
15313
|
+
this.ws = new WebSocket(this.buildUrl(ticket));
|
|
15043
15314
|
this.ws.binaryType = "arraybuffer";
|
|
15044
15315
|
} catch (error) {
|
|
15045
15316
|
reject(error);
|
|
@@ -15069,16 +15340,20 @@ var WebSocketTransport = class {
|
|
|
15069
15340
|
this.ws.addEventListener("message", onMessage);
|
|
15070
15341
|
});
|
|
15071
15342
|
}
|
|
15072
|
-
buildUrl() {
|
|
15343
|
+
buildUrl(ticket = null) {
|
|
15073
15344
|
const baseUrl = this.config.gameServerUrl || "wss://game.connectbase.world";
|
|
15074
15345
|
const wsUrl = baseUrl.replace(/^http/, "ws");
|
|
15075
15346
|
const params = new URLSearchParams();
|
|
15076
15347
|
params.set("client_id", requireClientId(this.config.clientId));
|
|
15077
|
-
if (
|
|
15078
|
-
params.set("
|
|
15079
|
-
}
|
|
15080
|
-
|
|
15081
|
-
|
|
15348
|
+
if (ticket) {
|
|
15349
|
+
params.set("ticket", ticket);
|
|
15350
|
+
} else {
|
|
15351
|
+
if (this.config.publicKey) {
|
|
15352
|
+
params.set("public_key", this.config.publicKey);
|
|
15353
|
+
}
|
|
15354
|
+
if (this.config.accessToken) {
|
|
15355
|
+
params.set("token", this.config.accessToken);
|
|
15356
|
+
}
|
|
15082
15357
|
}
|
|
15083
15358
|
if (!this.config.appId) {
|
|
15084
15359
|
throw new Error(
|
|
@@ -15758,6 +16033,7 @@ var index_default = ConnectBase;
|
|
|
15758
16033
|
RolesAPI,
|
|
15759
16034
|
SessionManager,
|
|
15760
16035
|
VideoProcessingError,
|
|
16036
|
+
WsTicketError,
|
|
15761
16037
|
detectInAppBrowser,
|
|
15762
16038
|
escapeToExternalBrowser,
|
|
15763
16039
|
isWebTransportSupported,
|