@yyyu-star/arcade-kit 0.0.0-stage → 0.10.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +249 -0
  2. package/LICENSE +21 -0
  3. package/README.md +440 -2
  4. package/dist/auth.d.ts +66 -0
  5. package/dist/auth.d.ts.map +1 -0
  6. package/dist/auth.js +86 -0
  7. package/dist/auth.js.map +1 -0
  8. package/dist/client.d.ts +194 -0
  9. package/dist/client.d.ts.map +1 -0
  10. package/dist/client.js +379 -0
  11. package/dist/client.js.map +1 -0
  12. package/dist/fun-art.d.ts +41 -0
  13. package/dist/fun-art.d.ts.map +1 -0
  14. package/dist/fun-art.js +141 -0
  15. package/dist/fun-art.js.map +1 -0
  16. package/dist/fun.d.ts +87 -0
  17. package/dist/fun.d.ts.map +1 -0
  18. package/dist/fun.js +512 -0
  19. package/dist/fun.js.map +1 -0
  20. package/dist/game.d.ts +160 -0
  21. package/dist/game.d.ts.map +1 -0
  22. package/dist/game.js +41 -0
  23. package/dist/game.js.map +1 -0
  24. package/dist/guest.d.ts +60 -0
  25. package/dist/guest.d.ts.map +1 -0
  26. package/dist/guest.js +87 -0
  27. package/dist/guest.js.map +1 -0
  28. package/dist/internal.d.ts +33 -0
  29. package/dist/internal.d.ts.map +1 -0
  30. package/dist/internal.js +53 -0
  31. package/dist/internal.js.map +1 -0
  32. package/dist/meter.d.ts +102 -0
  33. package/dist/meter.d.ts.map +1 -0
  34. package/dist/meter.js +207 -0
  35. package/dist/meter.js.map +1 -0
  36. package/dist/protocol.d.ts +227 -0
  37. package/dist/protocol.d.ts.map +1 -0
  38. package/dist/protocol.js +65 -0
  39. package/dist/protocol.js.map +1 -0
  40. package/dist/rng.d.ts +40 -0
  41. package/dist/rng.d.ts.map +1 -0
  42. package/dist/rng.js +119 -0
  43. package/dist/rng.js.map +1 -0
  44. package/dist/room.d.ts +352 -0
  45. package/dist/room.d.ts.map +1 -0
  46. package/dist/room.js +1563 -0
  47. package/dist/room.js.map +1 -0
  48. package/dist/testing.d.ts +125 -0
  49. package/dist/testing.d.ts.map +1 -0
  50. package/dist/testing.js +272 -0
  51. package/dist/testing.js.map +1 -0
  52. package/dist/worker.d.ts +89 -0
  53. package/dist/worker.d.ts.map +1 -0
  54. package/dist/worker.js +177 -0
  55. package/dist/worker.js.map +1 -0
  56. package/package.json +107 -4
  57. package/src/auth.ts +100 -0
  58. package/src/client.ts +438 -0
  59. package/src/fun-art.ts +184 -0
  60. package/src/fun.ts +626 -0
  61. package/src/game.ts +200 -0
  62. package/src/guest.ts +122 -0
  63. package/src/internal.ts +64 -0
  64. package/src/meter.ts +255 -0
  65. package/src/protocol.ts +243 -0
  66. package/src/rng.ts +131 -0
  67. package/src/room.ts +1677 -0
  68. package/src/testing.ts +300 -0
  69. package/src/worker.ts +229 -0
package/dist/auth.js ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * 进房认人(游戏 Worker 里用):WebSocket 升级请求带着平台的登录 cookie,游戏 Worker 经服务绑定 ARCADE 问一次平台 GET /api/me,
3
+ * 和游戏登录闸门一个做法(docs/platform-backend.md 15.8):
4
+ * - 请求里没有平台的会话 cookie(SESSION_COOKIE)就直接算"没登录",不问平台:没登录的脚本刷进房地址,不占平台的计量(0.1.1);
5
+ * - 只转发 Cookie 和 cf-connecting-ip 两个头,3 秒超时;
6
+ * - 200 且 user 不为 null 才认,拿 user.id 和昵称交给房间;200 且 user 为 null 是"没登录";
7
+ * - 其他状态码、不是 JSON、缺字段、超时、绑定不存在都是"平台暂时不可用":同样拒绝(fail-closed),但和"没登录"分开,
8
+ * 进房时回 1013(稍后再试,客户端会重连),不回 4401(不重连),平台抖一下不至于把正在打牌的人踢走;
9
+ * - 地址按请求自己的 origin 拼(`<请求的 origin>/api/me`,0.9.0):游戏和平台挂在同一个站点下,平台按主机名认是不是本站;
10
+ * kit 里不写死站点地址,测试或特殊部署可以用 meUrl 覆盖;
11
+ * - 平台续期或清掉失效会话时回的 Set-Cookie 原样带回来(setCookie),游戏的登录闸门、自己的接口在放行和"没登录"时转给浏览器,
12
+ * 只玩游戏、不回平台页的人 cookie 也能续上(15.8)。进房用不上,不转。
13
+ *
14
+ * 0.10.0 起有独立模式(guest.ts):游戏不带平台单独部署时认游客,按部署方式选哪种认法的是 identify;whoIs 本身不变,只问平台。
15
+ *
16
+ * 0.3.1 起作为 `arcade-kit/auth` 导出:游戏自己的接口用同一份,不再各抄一份;登录闸门 0.8.0 起直接用 `arcade-kit/worker` 的 `loginGate`。
17
+ *
18
+ * 第一版不做 Ed25519 入场票(5.3):好友房都是熟人小范围开房,进房时多问一次 /api/me(算平台 1 次调用,带 cookie 时读 D1 一两行)
19
+ * 换来不用管签名密钥、不用改平台;等开房要走平台登记和配额、或者进房量大到这一次调用显得贵了,再换成入场票,房间这边只是换一种认人的方式。
20
+ */
21
+ import { cleanNickname, guestIdentity, isStandalone, mintGuestCookie, readGuestCookie } from './guest.js';
22
+ import { hasSessionCookie } from './internal.js';
23
+ /** 平台"我是谁"接口的路径;完整地址按请求的 origin 拼(meUrlOf) */
24
+ export const ME_PATH = '/api/me';
25
+ /** 这个请求所在站点的 /api/me 地址 */
26
+ export function meUrlOf(request) {
27
+ return new URL(ME_PATH, request.url).href;
28
+ }
29
+ const UNAVAILABLE = { error: 'unavailable' };
30
+ /** 认人。认不出来时分"没登录"(anonymous)和"平台暂时不可用"(unavailable),两种调用方都要拒绝。 */
31
+ export async function whoIs(request, arcade, opts = {}) {
32
+ if (!hasSessionCookie(request.headers.get('cookie')))
33
+ return { error: 'anonymous', setCookie: [] };
34
+ if (!arcade)
35
+ return UNAVAILABLE;
36
+ try {
37
+ const res = await arcade.fetch(opts.meUrl ?? meUrlOf(request), {
38
+ headers: {
39
+ cookie: request.headers.get('cookie') ?? '',
40
+ 'cf-connecting-ip': request.headers.get('cf-connecting-ip') ?? '',
41
+ },
42
+ signal: AbortSignal.timeout(opts.timeoutMs ?? 3_000),
43
+ });
44
+ if (res.status !== 200)
45
+ return UNAVAILABLE;
46
+ const body = (await res.json());
47
+ if (!body || typeof body !== 'object' || !('user' in body))
48
+ return UNAVAILABLE;
49
+ const setCookie = res.headers.getSetCookie();
50
+ const u = body.user;
51
+ if (u === null)
52
+ return { error: 'anonymous', setCookie };
53
+ if (!u || typeof u.id !== 'string' || !u.id || typeof u.displayName !== 'string')
54
+ return UNAVAILABLE;
55
+ return { user: { userId: u.id, name: u.displayName }, setCookie };
56
+ }
57
+ catch {
58
+ return UNAVAILABLE;
59
+ }
60
+ }
61
+ /**
62
+ * 按部署方式认人(0.10.0):
63
+ * - 平台模式(有 ARCADE 绑定,或者 GATE=login):就是 whoIs,游客 cookie 一律不看;GATE=login 却没绑 ARCADE 时 whoIs 回 unavailable(fail-closed);
64
+ * - 独立模式(guest.ts 的 isStandalone):认游客 cookie;没有时 mint 为 true 就现发一个,否则算 anonymous。
65
+ */
66
+ export async function identify(request, env, opts = {}) {
67
+ if (!isStandalone(env))
68
+ return whoIs(request, env.ARCADE, opts.meUrl ? { meUrl: opts.meUrl } : {});
69
+ const nickname = opts.nickname == null ? null : cleanNickname(opts.nickname, opts.guest?.reserved);
70
+ const have = readGuestCookie(request, opts.guest);
71
+ if (have)
72
+ return { user: await guestIdentity(have, nickname), setCookie: [] };
73
+ if (!opts.mint)
74
+ return { error: 'anonymous', setCookie: [] };
75
+ const fresh = mintGuestCookie(request, opts.guest);
76
+ return { user: await guestIdentity(fresh.value, nickname), setCookie: [fresh.setCookie] };
77
+ }
78
+ /**
79
+ * WebSocket 不受同源策略管,别的网站的页面也能往这里连(浏览器会带上能带的 cookie):升级请求的 Origin 必须是本站。
80
+ * 没带 Origin 的(不是浏览器)也拒绝。
81
+ * 本站默认就是请求自己的 origin(0.9.0:kit 不写死站点地址);部署时想更严,可以传 siteOrigin(比如由 SITE_ORIGIN 变量注入)
82
+ */
83
+ export function sameOrigin(request, siteOrigin) {
84
+ return request.headers.get('origin') === (siteOrigin ?? new URL(request.url).origin);
85
+ }
86
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,194 @@
1
+ /**
2
+ * 浏览器端连接器(docs/platform-backend.md 4.5):
3
+ * - 断线后按指数退避加随机抖动重连;房间每次都回全量快照(welcome),客户端整桌重画,不做增量修补;
4
+ * - 页面从后台切回来(visibilitychange、pageshow)时立刻检查连接:iOS Safari、微信切后台会挂起连接;
5
+ * - 收到 1008、1009 或 4000 段关闭码时不重连(违规、房间关了、被别处顶掉、版本太旧、没登录 4401、别的网站 4403);
6
+ * - 平台暂时不可用(1013)照常按退避重连;
7
+ * - 连着 maxFailures 次(默认 20 次,约 4 分钟)都没进得了房间(没收到 welcome)就停下,状态变成 failed,由界面提示"连不上"、让用户点 retry()。
8
+ * 退避和失败次数都在收到 welcome 时才清零,不在 onopen:被拒的连接也会先 open 再关,按 onopen 清零就会一直快速重连;
9
+ * - 下行消息按 seq 去重,旧的丢掉;上行操作带 moveId 和 baseSeq(做决定时看到的 seq),没收到 ack 的在重连后原样重发:
10
+ * 房间认得出重复的 moveId;牌局在那之后变过的,房间回 stale,不会把旧操作套到新的一手上;
11
+ * - 心跳发纯文本 "ping",房间由运行时自动回 "pong",不唤醒房间。
12
+ *
13
+ * 浏览器里的计时器(重连、心跳)可以用 setTimeout,"只用一个闹钟"是房间(Durable Object)那边的规矩。
14
+ */
15
+ import { type CreateError, type DownMsg, PROTOCOL_VERSION } from './protocol.js';
16
+ export type ClientStatus = 'connecting' | 'open' | 'reconnecting' | 'closed' | 'failed';
17
+ export interface RoomClientOptions<V> {
18
+ /** 房间的 WebSocket 地址(游戏 Worker 的路径,如 wss://<站点>/games/<id>/rooms/123456/ws) */
19
+ url: string;
20
+ /** 页面的版本号,房间据此判断要不要让旧页面刷新 */
21
+ clientVersion?: number;
22
+ onMessage(msg: DownMsg<V>): void;
23
+ /**
24
+ * 连接状态变化。closeCode:closed、failed 时是让它停下的那个关闭码;reconnecting 时是刚断开的那条连接的关闭码
25
+ * (1006 网络断了、1013 平台暂时认不了人……,0.1.1 起才带);connecting、open 时没有。
26
+ * reason:closed 时房间给的关闭原因(0.2.0);4000 带 CLOSE_REASON.quota 是今天联机额度满了、房间收场,其他时候一般不用看
27
+ */
28
+ onStatus?(status: ClientStatus, closeCode?: number, reason?: string): void;
29
+ /**
30
+ * 有人发了快捷短语(0.6.0,见 RoomGame.emotes):seat 是房间的座位号,id 是短语编号。不传就丢掉。
31
+ * to(0.7.0):冲着哪个座位发的,没指定时是 undefined;bot(0.10.0):电脑座位发的(GameRoom.botEmote)是 true,一般不用区分
32
+ */
33
+ onEmote?(seat: number, id: number, to?: number, bot?: boolean): void;
34
+ /** 下面这些是给测试注入的;浏览器里不用传 */
35
+ WebSocketImpl?: {
36
+ new (url: string): WebSocketLike;
37
+ };
38
+ random?: () => number;
39
+ setTimer?: (fn: () => void, ms: number) => unknown;
40
+ clearTimer?: (t: unknown) => void;
41
+ /** 页面事件来源(document / window),没有就不监听 */
42
+ events?: {
43
+ addEventListener(type: string, fn: () => void): void;
44
+ removeEventListener(type: string, fn: () => void): void;
45
+ };
46
+ isHidden?: () => boolean;
47
+ /** 连着几次没进得了房间(没收到 welcome;被拒的连接也会先 open 再关)就停下,默认 20 */
48
+ maxFailures?: number;
49
+ }
50
+ export interface WebSocketLike {
51
+ readyState: number;
52
+ onopen: ((ev: unknown) => void) | null;
53
+ onmessage: ((ev: {
54
+ data: unknown;
55
+ }) => void) | null;
56
+ onclose: ((ev: {
57
+ code: number;
58
+ reason?: string;
59
+ }) => void) | null;
60
+ onerror: ((ev: unknown) => void) | null;
61
+ send(data: string): void;
62
+ close(code?: number, reason?: string): void;
63
+ }
64
+ /** 第 n 次重连(从 0 起)的等待:指数退避,上限 15 秒,乘一个 [0.5, 1) 的随机系数(抖动,免得一桌人同时重连)。 */
65
+ export declare function backoffMs(attempt: number, random?: () => number): number;
66
+ export declare class RoomClient<A = unknown, V = unknown> {
67
+ private ws;
68
+ private status;
69
+ private attempt;
70
+ /** 连着几次没进得了房间(收到 welcome 清零) */
71
+ private failures;
72
+ /** 这条连接收到过 welcome */
73
+ private welcomed;
74
+ private lastSeq;
75
+ private retryTimer;
76
+ private beatTimer;
77
+ /** 没收到 ack 的操作:重连后按原 moveId 重发 */
78
+ private pending;
79
+ private moveCounter;
80
+ /** 快捷短语的本地令牌桶(0.7.0):节奏按房间 welcome 里给的,没收到前按默认 1 秒 1 个、攒 3 个 */
81
+ private emoteRate;
82
+ private emoteTokens;
83
+ private emoteAt;
84
+ /** 太快时排着的(只留最后 EMOTE_QUEUE 个),到下一个令牌时发 */
85
+ private emoteQueue;
86
+ private emoteTimer;
87
+ private readonly opts;
88
+ private readonly onWake;
89
+ constructor(opts: RoomClientOptions<V>);
90
+ /** 发一个操作意图;返回 moveId。没连上时先存着,连上后发。 */
91
+ act(action: A): string;
92
+ sit(seat: number): void;
93
+ leave(): void;
94
+ /** 房主开局;opts 是开局选项(0.3.0,游戏校验),不带就和以前一样 */
95
+ start(opts?: unknown): void;
96
+ /**
97
+ * 快捷短语(0.6.0):发游戏短语表里的编号;to(0.7.0)是冲着哪个座位(扔道具、点名嘲讽),不带就是对全桌。
98
+ * 0.7.0 起本地按房间的节奏(welcome.emote)排队:太快的排到下一个令牌,只留最后 2 个,不提示用户。没连上就丢掉,不存着重发(过时的短语没意义);
99
+ * 房间转发回来的 { t: 'emote', seat, id } 经 onEmote 交给页面(不进 onMessage),发的人自己也会收到
100
+ */
101
+ emote(id: number, to?: number): void;
102
+ /** 按令牌发排着的短语;没令牌就约到下一个令牌的时候。用户这边永远不提示"太快了" */
103
+ private flushEmotes;
104
+ /** 主动离开:不再重连。 */
105
+ close(): void;
106
+ /** 停下(failed)以后用户点"重试":从头再连。 */
107
+ retry(): void;
108
+ get state(): ClientStatus;
109
+ private connect;
110
+ /** 带上这次的关闭码:游戏据此区分"网络断了"(1006)和"平台有点忙"(1013)。 */
111
+ private scheduleRetry;
112
+ /** 切回前台:没连着就别等退避,马上连;连着就发个心跳探一下(挂起的连接会因此报错断开,再走重连)。 */
113
+ private wake;
114
+ private heartbeat;
115
+ private clearTimers;
116
+ private trySend;
117
+ private setStatus;
118
+ }
119
+ export { PROTOCOL_VERSION };
120
+ /** 开房的结果:成功回房间码;失败回 acceptCreate 的错误码,网络断了是 offline。has_room 带那个房间的码 */
121
+ export type CreateResult = {
122
+ code: string;
123
+ } | {
124
+ error: CreateError | 'offline';
125
+ code?: string;
126
+ };
127
+ /**
128
+ * 开房:POST 到游戏 Worker 的开房路由(页面在 `/games/<id>/` 下,传相对路径 `'rooms'` 就对),请求体是 `{ opts }`,
129
+ * 游戏 Worker 从里面取出开局选项交给 acceptCreate(worker.ts 的 `CreateOptions.options`)。带 `X-Arcade: 1`(5.2 的 CSRF 规矩)。
130
+ * 回话不是 JSON(网关的错误页之类)当 unavailable;401 一律当 login
131
+ */
132
+ export declare function createRoom(url: string | URL, opts: unknown, fetchImpl?: typeof fetch): Promise<CreateResult>;
133
+ /**
134
+ * 开房失败、和房间断开时给玩家看的话(docs/platform-backend.md 15.10 的口径:大白话、说清能做什么)。只写各游戏都通用的说法,
135
+ * 游戏按 key 换掉个别措辞(比如"这一手""这一局"、"桌上的筹码结回钱包"),没换的用默认:
136
+ * - 开房失败:key 是 createRoom 的错误码(login、has_room、daily_limit、busy、bad_options、forbidden、unavailable、offline),不认识的按 unavailable;
137
+ * - 断开:key 是 CLOSE 里的名字(roomClosed、noRoom、replaced……),另有 quota(4000 带 quota 原因:今天额度快满、房间收场)和 other(没列出的关闭码)。
138
+ */
139
+ export declare const ROOM_TEXT: {
140
+ readonly login: '登录过期了,刷新一下页面';
141
+ readonly forbidden: '请从游戏大厅打开';
142
+ readonly has_room: '你同时开着的房间太多了,先离开几个再开';
143
+ readonly daily_limit: '今天开房的次数用完了,明天再来';
144
+ readonly busy: '今天联机的人太多了,明天再来,单机照常玩';
145
+ readonly bad_options: '开房的设置不对,刷新一下再开';
146
+ readonly unavailable: '暂时开不了房,稍后再试';
147
+ readonly offline: '网络断了,连上再试';
148
+ readonly roomClosed: '房间关了';
149
+ readonly quota: '今天联机的人太多了,房间先散了,单机照常玩';
150
+ readonly replaced: '你在别的页面打开了这个房间,这里先断开';
151
+ readonly noRoom: '没有这个房间:码不对,或者房间已经关了';
152
+ readonly tooOld: '页面有新版本了,刷新一下';
153
+ readonly full: '这个房间看的人太多了,进不去';
154
+ readonly tooManyJoins: '你在这个房间进出、操作太多次了,先歇一会儿';
155
+ readonly userQuota: '你在这个房间进出、操作太多次了,先歇一会儿';
156
+ readonly unauthorized: '登录过期了,刷新一下页面';
157
+ readonly other: '和房间断开了';
158
+ };
159
+ export type RoomTextKey = keyof typeof ROOM_TEXT;
160
+ export type RoomTexts = Partial<Record<RoomTextKey, string>>;
161
+ /** 开房失败给玩家看的话:错误码 → 文案,游戏的 overrides 优先 */
162
+ export declare function createText(error: string, overrides?: RoomTexts): string;
163
+ /** 和房间断开时给玩家看的话:关闭码(和 4000 带的原因)→ 文案,游戏的 overrides 优先。1013(稍后再试)客户端会自己重连,也给一句 other */
164
+ export declare function closeText(code: number, reason?: string, overrides?: RoomTexts): string;
165
+ /**
166
+ * 页面所在站点有没有平台:
167
+ * - `platform: true`:同域 `/api/me` 回 200 且是 `{ user }` 的 JSON。user 是平台的 `/api/me` 原样(没登录为 null);
168
+ * - `platform: false`:404、405、410,或者 2xx 却不是那个 JSON(独立部署时这个地址落到游戏自己的 Worker 或静态资源上)。
169
+ * 游戏应当藏起登录、云存档、排行榜、钱包,"回大厅"改成回游戏首页;
170
+ * - `platform: 'unknown'`:5xx、429、断网、超时(默认 2.5 秒)。平台可能只是抖了一下:先按有平台处理但别报错,过一会儿再试,
171
+ * 或者干脆当成没有——游戏自己定。
172
+ */
173
+ export type PlatformInfo = {
174
+ platform: true;
175
+ user: unknown;
176
+ } | {
177
+ platform: false;
178
+ } | {
179
+ platform: 'unknown';
180
+ };
181
+ export interface DetectPlatformOptions {
182
+ /** 默认 `/api/me`(站点根下,和游戏挂在哪无关) */
183
+ url?: string | URL;
184
+ timeoutMs?: number;
185
+ fetchImpl?: typeof fetch;
186
+ }
187
+ export declare function detectPlatform(opts?: DetectPlatformOptions): Promise<PlatformInfo>;
188
+ /**
189
+ * 独立模式下给开房、进房的地址带上昵称(`?name=`,0.10.0):`createRoom(withNickname('rooms', name), opts)`、
190
+ * `new RoomClient({ url: withNickname(wsUrl, name), … })`。服务端会严格清洗(guest.ts 的 cleanNickname),不合格就用默认的"游客1234";
191
+ * 有平台时服务端不看它,昵称以平台为准。name 空的原样返回
192
+ */
193
+ export declare function withNickname(url: string, name?: string | null): string;
194
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}