@lootlog/sdk 0.1.1 → 0.3.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/activity.d.ts +8 -2
- package/dist/activity.d.ts.map +1 -1
- package/dist/activity.js +2 -2
- package/dist/activity.js.map +1 -1
- package/dist/battlelog.d.ts +280 -5
- package/dist/battlelog.d.ts.map +1 -1
- package/dist/battlelog.js +11 -6
- package/dist/battlelog.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/main.d.ts +1457 -918
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +400 -284
- package/dist/main.js.map +1 -1
- package/dist/{mutators-Da_vMUva.d.ts → mutators-BaZDDfwh.d.ts} +2 -2
- package/dist/{mutators-Da_vMUva.d.ts.map → mutators-BaZDDfwh.d.ts.map} +1 -1
- package/dist/{mutators-CaBTxpi-.js → mutators-Dj5qBH3y.js} +2 -2
- package/dist/{mutators-CaBTxpi-.js.map → mutators-Dj5qBH3y.js.map} +1 -1
- package/dist/realtime.d.ts +430 -21
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +799 -587
- package/dist/realtime.js.map +1 -1
- package/dist/search.d.ts +247 -5
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +115 -2
- package/dist/search.js.map +1 -1
- package/dist/{transport-CIfewyM7.js → transport-Cn1V6y-o.js} +46 -8
- package/dist/transport-Cn1V6y-o.js.map +1 -0
- package/dist/{transport-BHbtsYo_.d.ts → transport-CoTymLxF.d.ts} +3 -1
- package/dist/transport-CoTymLxF.d.ts.map +1 -0
- package/openapi/activity.json +15 -1
- package/openapi/battlelog.json +807 -40
- package/openapi/main.json +5752 -4060
- package/openapi/search.json +721 -53
- package/package.json +5 -6
- package/dist/transport-BHbtsYo_.d.ts.map +0 -1
- package/dist/transport-CIfewyM7.js.map +0 -1
package/dist/realtime.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Result, Schema, SchemaTransformation } from "effect";
|
|
1
|
+
import { Predicate, Result, Schema, SchemaTransformation } from "effect";
|
|
2
2
|
import * as Schema$1 from "effect/Schema";
|
|
3
3
|
import { TaggedError } from "effect/Schema";
|
|
4
|
-
import
|
|
4
|
+
import * as SchemaTransformation$1 from "effect/SchemaTransformation";
|
|
5
5
|
//#region ../schema/src/npc-type.ts
|
|
6
6
|
const NpcTypeEnum = {
|
|
7
7
|
COMMON: "COMMON",
|
|
@@ -12,6 +12,7 @@ const NpcTypeEnum = {
|
|
|
12
12
|
EVENT_HERO: "EVENT_HERO",
|
|
13
13
|
COLOSSUS: "COLOSSUS",
|
|
14
14
|
TITAN: "TITAN",
|
|
15
|
+
HERO_TRACKER: "HERO_TRACKER",
|
|
15
16
|
NPC: "NPC"
|
|
16
17
|
};
|
|
17
18
|
const NpcTypeSchema = Schema.Literals([
|
|
@@ -23,6 +24,7 @@ const NpcTypeSchema = Schema.Literals([
|
|
|
23
24
|
NpcTypeEnum.EVENT_HERO,
|
|
24
25
|
NpcTypeEnum.COLOSSUS,
|
|
25
26
|
NpcTypeEnum.TITAN,
|
|
27
|
+
NpcTypeEnum.HERO_TRACKER,
|
|
26
28
|
NpcTypeEnum.NPC
|
|
27
29
|
]);
|
|
28
30
|
//#endregion
|
|
@@ -139,7 +141,7 @@ const NPC_PERMISSIONS = {
|
|
|
139
141
|
Object.values(NPC_PERMISSIONS).flat();
|
|
140
142
|
new Set(NPC_PERMISSIONS.loots);
|
|
141
143
|
Object.values(Permission).sort();
|
|
142
|
-
|
|
144
|
+
Schema.is(NpcTypeSchema);
|
|
143
145
|
//#endregion
|
|
144
146
|
//#region ../schema/src/item-rarity.ts
|
|
145
147
|
const ItemRarityEnum = {
|
|
@@ -161,21 +163,11 @@ const LootSourceEnum = {
|
|
|
161
163
|
DIALOG: "DIALOG",
|
|
162
164
|
FIGHT: "FIGHT"
|
|
163
165
|
};
|
|
164
|
-
Schema.Literals([
|
|
166
|
+
const LootSourceSchema = Schema.Literals([
|
|
165
167
|
LootSourceEnum.LOOTBOX,
|
|
166
168
|
LootSourceEnum.DIALOG,
|
|
167
169
|
LootSourceEnum.FIGHT
|
|
168
170
|
]);
|
|
169
|
-
const LootShareSourceEnum = {
|
|
170
|
-
NONE: "NONE",
|
|
171
|
-
ITEM_OWNER: "ITEM_OWNER",
|
|
172
|
-
CHAT_MESSAGE: "CHAT_MESSAGE"
|
|
173
|
-
};
|
|
174
|
-
Schema.Literals([
|
|
175
|
-
LootShareSourceEnum.NONE,
|
|
176
|
-
LootShareSourceEnum.ITEM_OWNER,
|
|
177
|
-
LootShareSourceEnum.CHAT_MESSAGE
|
|
178
|
-
]);
|
|
179
171
|
const ProfessionEnum = {
|
|
180
172
|
WARRIOR: "WARRIOR",
|
|
181
173
|
PALADIN: "PALADIN",
|
|
@@ -192,89 +184,56 @@ const ProfessionSchema = Schema.Literals([
|
|
|
192
184
|
ProfessionEnum.BLADE_DANCER,
|
|
193
185
|
ProfessionEnum.TRACKER
|
|
194
186
|
]);
|
|
195
|
-
const ItemTypeEnum = {
|
|
196
|
-
ONE_HAND_WEAPON: "ONE_HAND_WEAPON",
|
|
197
|
-
TWO_HAND_WEAPON: "TWO_HAND_WEAPON",
|
|
198
|
-
ONE_AND_HALF_HAND_WEAPON: "ONE_AND_HALF_HAND_WEAPON",
|
|
199
|
-
DISTANCE_WEAPON: "DISTANCE_WEAPON",
|
|
200
|
-
HELP_WEAPON: "HELP_WEAPON",
|
|
201
|
-
WAND_WEAPON: "WAND_WEAPON",
|
|
202
|
-
ORB_WEAPON: "ORB_WEAPON",
|
|
203
|
-
ARMOR: "ARMOR",
|
|
204
|
-
HELMET: "HELMET",
|
|
205
|
-
BOOTS: "BOOTS",
|
|
206
|
-
GLOVES: "GLOVES",
|
|
207
|
-
RING: "RING",
|
|
208
|
-
NECKLACE: "NECKLACE",
|
|
209
|
-
SHIELD: "SHIELD",
|
|
210
|
-
NEUTRAL: "NEUTRAL",
|
|
211
|
-
CONSUME: "CONSUME",
|
|
212
|
-
GOLD: "GOLD",
|
|
213
|
-
KEYS: "KEYS",
|
|
214
|
-
QUEST: "QUEST",
|
|
215
|
-
RENEWABLE: "RENEWABLE",
|
|
216
|
-
ARROWS: "ARROWS",
|
|
217
|
-
TALISMAN: "TALISMAN",
|
|
218
|
-
BOOK: "BOOK",
|
|
219
|
-
BAG: "BAG",
|
|
220
|
-
BLESS: "BLESS",
|
|
221
|
-
UPGRADE: "UPGRADE",
|
|
222
|
-
RECIPE: "RECIPE",
|
|
223
|
-
COINAGE: "COINAGE",
|
|
224
|
-
QUIVER: "QUIVER",
|
|
225
|
-
OUTFITS: "OUTFITS",
|
|
226
|
-
PETS: "PETS",
|
|
227
|
-
TELEPORTS: "TELEPORTS"
|
|
228
|
-
};
|
|
229
|
-
Schema.Literals([
|
|
230
|
-
ItemTypeEnum.ONE_HAND_WEAPON,
|
|
231
|
-
ItemTypeEnum.TWO_HAND_WEAPON,
|
|
232
|
-
ItemTypeEnum.ONE_AND_HALF_HAND_WEAPON,
|
|
233
|
-
ItemTypeEnum.DISTANCE_WEAPON,
|
|
234
|
-
ItemTypeEnum.HELP_WEAPON,
|
|
235
|
-
ItemTypeEnum.WAND_WEAPON,
|
|
236
|
-
ItemTypeEnum.ORB_WEAPON,
|
|
237
|
-
ItemTypeEnum.ARMOR,
|
|
238
|
-
ItemTypeEnum.HELMET,
|
|
239
|
-
ItemTypeEnum.BOOTS,
|
|
240
|
-
ItemTypeEnum.GLOVES,
|
|
241
|
-
ItemTypeEnum.RING,
|
|
242
|
-
ItemTypeEnum.NECKLACE,
|
|
243
|
-
ItemTypeEnum.SHIELD,
|
|
244
|
-
ItemTypeEnum.NEUTRAL,
|
|
245
|
-
ItemTypeEnum.CONSUME,
|
|
246
|
-
ItemTypeEnum.GOLD,
|
|
247
|
-
ItemTypeEnum.KEYS,
|
|
248
|
-
ItemTypeEnum.QUEST,
|
|
249
|
-
ItemTypeEnum.RENEWABLE,
|
|
250
|
-
ItemTypeEnum.ARROWS,
|
|
251
|
-
ItemTypeEnum.TALISMAN,
|
|
252
|
-
ItemTypeEnum.BOOK,
|
|
253
|
-
ItemTypeEnum.BAG,
|
|
254
|
-
ItemTypeEnum.BLESS,
|
|
255
|
-
ItemTypeEnum.UPGRADE,
|
|
256
|
-
ItemTypeEnum.RECIPE,
|
|
257
|
-
ItemTypeEnum.COINAGE,
|
|
258
|
-
ItemTypeEnum.QUIVER,
|
|
259
|
-
ItemTypeEnum.OUTFITS,
|
|
260
|
-
ItemTypeEnum.PETS,
|
|
261
|
-
ItemTypeEnum.TELEPORTS
|
|
262
|
-
]);
|
|
263
187
|
//#endregion
|
|
264
188
|
//#region ../schema/src/http-scalars.ts
|
|
265
189
|
const FiniteNumber = Schema$1.Number.check(Schema$1.isFinite().annotate({ expected: "a finite number" }));
|
|
266
|
-
const dateTimeString = (pattern) => Schema$1.String.annotate({ format: "date-time" }).check(Schema$1.isPattern(new RegExp(pattern)).annotate({ expected: `a string matching the RegExp ${pattern}` }));
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
190
|
+
const dateTimeString = (pattern) => Schema$1.String.annotate({ format: "date-time" }).check(Schema$1.isPattern(new RegExp(pattern, "u")).annotate({ expected: `a string matching the RegExp ${pattern}` }));
|
|
191
|
+
const dateTimePattern = (timeZonePattern) => `^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:${timeZonePattern}))$`;
|
|
192
|
+
/** Strict ISO-8601 UTC date-time pattern shared by HTTP and domain schemas. */
|
|
193
|
+
const DATE_TIME_STRING_PATTERN = dateTimePattern("Z");
|
|
194
|
+
const DateTimeString = dateTimeString(DATE_TIME_STRING_PATTERN);
|
|
195
|
+
dateTimeString(dateTimePattern("Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)"));
|
|
196
|
+
const NonEmptyString = Schema$1.String.check(Schema$1.isMinLength(1).annotate({ expected: "a value with a length of at least 1" }));
|
|
270
197
|
Schema$1.Json.annotate({ expected: "JSON value" });
|
|
271
|
-
Schema$1.Number.check(Schema$1.isInt().annotate({ expected: "an integer" })).check(Schema$1.isGreaterThanOrEqualTo(
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
198
|
+
const boundedInteger = (minimum, maximum) => Schema$1.Number.check(Schema$1.isInt().annotate({ expected: "an integer" })).check(Schema$1.isGreaterThanOrEqualTo(minimum).annotate({ expected: `a value greater than or equal to ${minimum}` })).check(Schema$1.isLessThanOrEqualTo(maximum).annotate({ expected: `a value less than or equal to ${maximum}` }));
|
|
199
|
+
boundedInteger(-9007199254740991, 9007199254740991);
|
|
200
|
+
const PositiveSafeInteger = boundedInteger(1, 9007199254740991);
|
|
201
|
+
const NonNegativeSafeInteger = boundedInteger(0, 9007199254740991);
|
|
202
|
+
const LevelFilter = boundedInteger(0, 500);
|
|
203
|
+
boundedInteger(1, 100);
|
|
204
|
+
/** Rejects a level filter whose lower bound exceeds its upper bound. */
|
|
205
|
+
const levelRangeCheck = (minKey, maxKey) => Schema$1.makeFilter((data) => {
|
|
206
|
+
const min = data[minKey];
|
|
207
|
+
const max = data[maxKey];
|
|
208
|
+
return min === void 0 || max === void 0 || min <= max ? void 0 : {
|
|
209
|
+
path: [minKey],
|
|
210
|
+
issue: `${minKey} must be <= ${maxKey}`
|
|
211
|
+
};
|
|
212
|
+
});
|
|
213
|
+
Schema$1.optionalKey(LevelFilter), Schema$1.optionalKey(LevelFilter), levelRangeCheck("npcLevelMin", "npcLevelMax");
|
|
214
|
+
Schema$1.Array(NpcTypeSchema);
|
|
215
|
+
Schema$1.Literals([
|
|
216
|
+
"24h",
|
|
217
|
+
"3d",
|
|
218
|
+
"7d",
|
|
219
|
+
"14d",
|
|
220
|
+
"30d",
|
|
221
|
+
"90d",
|
|
222
|
+
"180d",
|
|
223
|
+
"all"
|
|
224
|
+
]).annotate({ default: "7d" });
|
|
225
|
+
Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(64)).pipe(Schema$1.decode(SchemaTransformation$1.trim()), Schema$1.decode(SchemaTransformation$1.toLowerCase())).check(Schema$1.isMinLength(1).annotate({ expected: "a non-empty world" }));
|
|
226
|
+
Schema$1.String.check(Schema$1.isMaxLength(100)).pipe(Schema$1.decode(SchemaTransformation$1.trim()));
|
|
227
|
+
Schema$1.String.check(Schema$1.isMinLength(1), Schema$1.isMaxLength(512));
|
|
276
228
|
//#endregion
|
|
277
229
|
//#region ../protocol/src/loot-summary.ts
|
|
230
|
+
const MapPlayersSnapshot = Schema.Array(Schema.Struct({
|
|
231
|
+
accountId: PositiveSafeInteger,
|
|
232
|
+
characterId: PositiveSafeInteger,
|
|
233
|
+
name: NonEmptyString,
|
|
234
|
+
prof: Schema.NullOr(ProfessionSchema),
|
|
235
|
+
icon: Schema.NullOr(Schema.String)
|
|
236
|
+
})).check(Schema.isMinLength(1));
|
|
278
237
|
const LootItemResponse = Schema.Struct({
|
|
279
238
|
id: FiniteNumber,
|
|
280
239
|
hid: Schema.String,
|
|
@@ -314,6 +273,37 @@ const LootSummary = Schema.Struct({
|
|
|
314
273
|
lootShare: LootShareResponse,
|
|
315
274
|
location: Schema.String
|
|
316
275
|
});
|
|
276
|
+
/** Same shape as the HTTP `MemberSummary`. */
|
|
277
|
+
const LootMember = Schema.Struct({
|
|
278
|
+
id: Schema.Number,
|
|
279
|
+
name: Schema.String,
|
|
280
|
+
avatar: Schema.NullOr(Schema.String),
|
|
281
|
+
userId: Schema.String
|
|
282
|
+
});
|
|
283
|
+
const LootSubmission = Schema.Struct({
|
|
284
|
+
guildId: Schema.String,
|
|
285
|
+
memberId: Schema.Number,
|
|
286
|
+
lootId: Schema.Number,
|
|
287
|
+
member: LootMember
|
|
288
|
+
});
|
|
289
|
+
/** One Organization's view of a loot, as its HTTP list and detail responses encode it. */
|
|
290
|
+
const LootSnapshot = Schema.Struct({
|
|
291
|
+
id: Schema.Number,
|
|
292
|
+
uniqueId: Schema.String,
|
|
293
|
+
world: Schema.String,
|
|
294
|
+
source: LootSourceSchema,
|
|
295
|
+
location: Schema.String,
|
|
296
|
+
items: Schema.Array(LootItemResponse),
|
|
297
|
+
players: Schema.Array(LootPlayerResponse),
|
|
298
|
+
mapPlayersSnapshot: Schema.NullOr(MapPlayersSnapshot),
|
|
299
|
+
npcs: Schema.Array(LootNpcResponse),
|
|
300
|
+
lootShare: LootShareResponse,
|
|
301
|
+
createdAt: Schema.String,
|
|
302
|
+
updatedAt: Schema.String,
|
|
303
|
+
submissions: Schema.optionalKey(Schema.Array(LootSubmission)),
|
|
304
|
+
commentsCount: Schema.Number,
|
|
305
|
+
likes: Schema.optionalKey(Schema.Array(LootMember))
|
|
306
|
+
});
|
|
317
307
|
//#endregion
|
|
318
308
|
//#region ../protocol/src/feed.ts
|
|
319
309
|
const FeedNpc = Schema.Struct({
|
|
@@ -360,9 +350,204 @@ const UserFeedItem = Schema.Union([Schema.Struct({
|
|
|
360
350
|
})]);
|
|
361
351
|
//#endregion
|
|
362
352
|
//#region ../schema/src/primitives.ts
|
|
363
|
-
const NonEmptyString = Schema.NonEmptyString;
|
|
364
353
|
const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
|
|
365
|
-
|
|
354
|
+
const isoDateTimePattern = new RegExp(DATE_TIME_STRING_PATTERN, "u");
|
|
355
|
+
Schema.String.annotate({ format: "date-time" }).pipe(Schema.check(Schema.isPattern(isoDateTimePattern)), Schema.decodeTo(Schema.Date, SchemaTransformation.dateFromString));
|
|
356
|
+
//#endregion
|
|
357
|
+
//#region ../schema/src/party-ready-room.ts
|
|
358
|
+
const PARTY_READY_ROOM_STATUSES = ["ACTIVE", "CANCELLED"];
|
|
359
|
+
const PARTY_READY_ROOM_PARTY_PRESENCE_STATES = ["OUTSIDE", "IN_PARTY"];
|
|
360
|
+
const PartyReadyRoomClanSchema = Schema.Struct({
|
|
361
|
+
id: Schema.optionalKey(Schema.Number),
|
|
362
|
+
name: Schema.optionalKey(Schema.String)
|
|
363
|
+
});
|
|
364
|
+
const PartyReadyRoomCharacterSchema = Schema.Struct({
|
|
365
|
+
accountId: Schema.String,
|
|
366
|
+
characterId: Schema.String,
|
|
367
|
+
icon: Schema.String,
|
|
368
|
+
lvl: Schema.Number,
|
|
369
|
+
nick: Schema.String,
|
|
370
|
+
prof: Schema.String,
|
|
371
|
+
clan: Schema.optionalKey(PartyReadyRoomClanSchema)
|
|
372
|
+
});
|
|
373
|
+
const PartyReadyRoomParticipantSchema = Schema.Struct({
|
|
374
|
+
participantId: Schema.String,
|
|
375
|
+
discordId: Schema.String,
|
|
376
|
+
character: PartyReadyRoomCharacterSchema,
|
|
377
|
+
partyPresence: Schema.Literals(PARTY_READY_ROOM_PARTY_PRESENCE_STATES),
|
|
378
|
+
createdAt: Schema.String,
|
|
379
|
+
updatedAt: Schema.String
|
|
380
|
+
});
|
|
381
|
+
const PartyGatheringNpcSchema = Schema.Struct({
|
|
382
|
+
prof: Schema.optionalKey(Schema.String),
|
|
383
|
+
icon: Schema.optionalKey(Schema.String),
|
|
384
|
+
name: Schema.String,
|
|
385
|
+
location: Schema.String,
|
|
386
|
+
lvl: Schema.Number,
|
|
387
|
+
type: Schema.String,
|
|
388
|
+
x: Schema.optionalKey(Schema.Number),
|
|
389
|
+
y: Schema.optionalKey(Schema.Number)
|
|
390
|
+
});
|
|
391
|
+
const PartyGatheringCharacterSchema = Schema.Struct({
|
|
392
|
+
characterId: NonEmptyString.check(Schema.isMaxLength(255)),
|
|
393
|
+
nick: Schema.String.check(Schema.isMaxLength(255)),
|
|
394
|
+
icon: Schema.String.check(Schema.isMaxLength(2048)),
|
|
395
|
+
lvl: FiniteNumber,
|
|
396
|
+
prof: Schema.String.check(Schema.isMaxLength(100))
|
|
397
|
+
});
|
|
398
|
+
const PartyGatheringPartyMemberSchema = Schema.Struct({
|
|
399
|
+
characterId: PartyGatheringCharacterSchema.fields.characterId,
|
|
400
|
+
nick: Schema.optionalKey(PartyGatheringCharacterSchema.fields.nick),
|
|
401
|
+
icon: Schema.optionalKey(PartyGatheringCharacterSchema.fields.icon),
|
|
402
|
+
lvl: Schema.optionalKey(PartyGatheringCharacterSchema.fields.lvl),
|
|
403
|
+
prof: Schema.optionalKey(PartyGatheringCharacterSchema.fields.prof)
|
|
404
|
+
});
|
|
405
|
+
const PartyGatheringPartyStateSchema = Schema.Union([Schema.Struct({ status: Schema.Literal("UNKNOWN") }), Schema.Struct({
|
|
406
|
+
status: Schema.Literal("OBSERVED"),
|
|
407
|
+
observedAt: DateTimeString,
|
|
408
|
+
members: Schema.Array(PartyGatheringPartyMemberSchema).check(Schema.isMaxLength(20))
|
|
409
|
+
})]);
|
|
410
|
+
const PartyGatheringVolunteerSchema = Schema.Struct({
|
|
411
|
+
...PartyGatheringCharacterSchema.fields,
|
|
412
|
+
partyPresence: Schema.Literals(PARTY_READY_ROOM_PARTY_PRESENCE_STATES)
|
|
413
|
+
});
|
|
414
|
+
const PartyGatheringSummarySchema = Schema.Struct({
|
|
415
|
+
notificationId: Schema.String,
|
|
416
|
+
organizerName: Schema.String,
|
|
417
|
+
organizerDiscordId: Schema.optionalKey(Schema.String),
|
|
418
|
+
organizerLvl: Schema.optionalKey(FiniteNumber),
|
|
419
|
+
organizerProf: Schema.optionalKey(Schema.String),
|
|
420
|
+
applicantCount: NonNegativeSafeInteger,
|
|
421
|
+
inPartyCount: NonNegativeSafeInteger,
|
|
422
|
+
partyMemberCount: Schema.optionalKey(NonNegativeSafeInteger),
|
|
423
|
+
revision: Schema.optionalKey(PositiveSafeInteger),
|
|
424
|
+
volunteers: Schema.optionalKey(Schema.Array(PartyGatheringVolunteerSchema)),
|
|
425
|
+
partyState: Schema.optionalKey(PartyGatheringPartyStateSchema),
|
|
426
|
+
guildIds: Schema.Array(Schema.String),
|
|
427
|
+
world: Schema.String,
|
|
428
|
+
description: Schema.optionalKey(Schema.String),
|
|
429
|
+
minLvl: Schema.optionalKey(FiniteNumber),
|
|
430
|
+
maxLvl: Schema.optionalKey(FiniteNumber),
|
|
431
|
+
npc: Schema.optionalKey(Schema.Struct({
|
|
432
|
+
...PartyGatheringNpcSchema.fields,
|
|
433
|
+
type: Schema.optionalKey(Schema.String)
|
|
434
|
+
})),
|
|
435
|
+
createdAt: DateTimeString,
|
|
436
|
+
expiresAt: DateTimeString
|
|
437
|
+
});
|
|
438
|
+
const PartyGatheringClientUpdateSchema = Schema.Union([Schema.Struct({
|
|
439
|
+
type: Schema.Literal("UPSERT"),
|
|
440
|
+
gathering: PartyGatheringSummarySchema
|
|
441
|
+
}), Schema.Struct({
|
|
442
|
+
type: Schema.Literal("REMOVE"),
|
|
443
|
+
notificationId: Schema.String,
|
|
444
|
+
revision: PositiveSafeInteger
|
|
445
|
+
})]);
|
|
446
|
+
Schema.Struct({
|
|
447
|
+
guildId: Schema.String,
|
|
448
|
+
guildIds: Schema.Array(Schema.String),
|
|
449
|
+
organizerDiscordId: Schema.String,
|
|
450
|
+
npc: Schema.optionalKey(PartyGatheringNpcSchema),
|
|
451
|
+
world: Schema.String,
|
|
452
|
+
notificationId: Schema.String,
|
|
453
|
+
revision: PositiveSafeInteger,
|
|
454
|
+
update: PartyGatheringClientUpdateSchema
|
|
455
|
+
});
|
|
456
|
+
const PartyReadyRoomAggregateSchema = Schema.Struct({
|
|
457
|
+
schemaVersion: Schema.Literal(3),
|
|
458
|
+
npc: Schema.optionalKey(PartyGatheringNpcSchema),
|
|
459
|
+
notificationId: Schema.String,
|
|
460
|
+
organizerDiscordId: Schema.String,
|
|
461
|
+
organizerCharacter: PartyReadyRoomCharacterSchema,
|
|
462
|
+
guildIds: Schema.mutable(Schema.Array(Schema.String)),
|
|
463
|
+
world: Schema.String,
|
|
464
|
+
description: Schema.optionalKey(Schema.String),
|
|
465
|
+
minLvl: Schema.optionalKey(Schema.Number),
|
|
466
|
+
maxLvl: Schema.optionalKey(Schema.Number),
|
|
467
|
+
partyMemberCount: Schema.optionalKey(NonNegativeSafeInteger),
|
|
468
|
+
partyState: Schema.optionalKey(PartyGatheringPartyStateSchema),
|
|
469
|
+
status: Schema.Literals(PARTY_READY_ROOM_STATUSES),
|
|
470
|
+
revision: Schema.Number,
|
|
471
|
+
createdAt: Schema.String,
|
|
472
|
+
updatedAt: Schema.String,
|
|
473
|
+
expiresAt: Schema.String,
|
|
474
|
+
participants: Schema.Record(Schema.String, PartyReadyRoomParticipantSchema)
|
|
475
|
+
});
|
|
476
|
+
const activeProjectionFields = {
|
|
477
|
+
volunteers: Schema.optionalKey(Schema.Array(PartyGatheringVolunteerSchema)),
|
|
478
|
+
...PartyReadyRoomAggregateSchema.fields,
|
|
479
|
+
participants: Schema.Record(Schema.String, Schema.mutableKey(PartyReadyRoomParticipantSchema)),
|
|
480
|
+
status: Schema.Literal("ACTIVE")
|
|
481
|
+
};
|
|
482
|
+
const PartyReadyRoomOrganizerProjectionSchema = Schema.Struct({
|
|
483
|
+
...activeProjectionFields,
|
|
484
|
+
viewer: Schema.Literal("ORGANIZER"),
|
|
485
|
+
ownedParticipantIds: Schema.mutable(Schema.Array(Schema.String))
|
|
486
|
+
});
|
|
487
|
+
const PartyReadyRoomParticipantProjectionSchema = Schema.Struct({
|
|
488
|
+
...activeProjectionFields,
|
|
489
|
+
viewer: Schema.Literal("PARTICIPANT")
|
|
490
|
+
});
|
|
491
|
+
const PartyReadyRoomProjectionSchema = Schema.Union([PartyReadyRoomOrganizerProjectionSchema, PartyReadyRoomParticipantProjectionSchema]);
|
|
492
|
+
Schema.decodeUnknownSync(PartyReadyRoomProjectionSchema);
|
|
493
|
+
const PartyReadyRoomUpsertUpdateSchema = Schema.Struct({
|
|
494
|
+
schemaVersion: Schema.Literal(3),
|
|
495
|
+
type: Schema.Literal("UPSERT"),
|
|
496
|
+
projection: PartyReadyRoomProjectionSchema
|
|
497
|
+
});
|
|
498
|
+
const PartyReadyRoomRemoveUpdateSchema = Schema.Struct({
|
|
499
|
+
schemaVersion: Schema.Literal(3),
|
|
500
|
+
type: Schema.Literal("REMOVE"),
|
|
501
|
+
notificationId: Schema.String,
|
|
502
|
+
revision: Schema.Number
|
|
503
|
+
});
|
|
504
|
+
const PartyReadyRoomClientUpdateSchema = Schema.Union([PartyReadyRoomUpsertUpdateSchema, PartyReadyRoomRemoveUpdateSchema]);
|
|
505
|
+
Schema.decodeUnknownSync(PartyReadyRoomClientUpdateSchema);
|
|
506
|
+
const GlobalChatMessageText = Schema.String.check(Schema.isTrimmed(), Schema.isMinLength(1), Schema.isMaxLength(128));
|
|
507
|
+
/**
|
|
508
|
+
* A Margonem world with its own global chat channel. Wherever a channel is
|
|
509
|
+
* named, an absent world means the channel every world shares.
|
|
510
|
+
*/
|
|
511
|
+
const GlobalChatWorld = Schema.NonEmptyString.check(Schema.isMaxLength(64));
|
|
512
|
+
/**
|
|
513
|
+
* A global chat message as every Member sees it. It crosses Organization
|
|
514
|
+
* boundaries, so it names the sender only by their Discord display name and
|
|
515
|
+
* never carries a User, Discord, Organization or character identifier.
|
|
516
|
+
*
|
|
517
|
+
* `isAdmin` and the worlds are optional only for messages published before
|
|
518
|
+
* channels existed; the API sets `isAdmin` on every message it stores.
|
|
519
|
+
*/
|
|
520
|
+
const GlobalChatMessageSchema = Schema.Struct({
|
|
521
|
+
id: Schema.NonEmptyString,
|
|
522
|
+
displayName: Schema.NonEmptyString,
|
|
523
|
+
message: GlobalChatMessageText,
|
|
524
|
+
timestamp: DateTimeString,
|
|
525
|
+
/** The channel's world; absent in the channel every world shares. */
|
|
526
|
+
world: Schema.optionalKey(GlobalChatWorld),
|
|
527
|
+
/** In the shared channel only: the world the sender wrote from. */
|
|
528
|
+
originWorld: Schema.optionalKey(GlobalChatWorld),
|
|
529
|
+
isAdmin: Schema.optionalKey(Schema.Boolean)
|
|
530
|
+
});
|
|
531
|
+
Schema.Union([Schema.Struct({
|
|
532
|
+
type: Schema.Literal("deleted"),
|
|
533
|
+
world: Schema.optionalKey(GlobalChatWorld),
|
|
534
|
+
id: Schema.NonEmptyString
|
|
535
|
+
}), Schema.Struct({
|
|
536
|
+
type: Schema.Literal("pinned"),
|
|
537
|
+
world: Schema.optionalKey(GlobalChatWorld),
|
|
538
|
+
message: Schema.NullOr(GlobalChatMessageSchema)
|
|
539
|
+
})]);
|
|
540
|
+
/** Live counts the gateway sends to a channel's subscribers. */
|
|
541
|
+
const GlobalChatStatsSchema = Schema.Struct({
|
|
542
|
+
world: Schema.optionalKey(GlobalChatWorld),
|
|
543
|
+
/**
|
|
544
|
+
* Unique Users with a live Game client across every Organization: on the
|
|
545
|
+
* channel's world, or on any world in the shared channel.
|
|
546
|
+
*/
|
|
547
|
+
online: NonNegativeInt,
|
|
548
|
+
/** Unique Users following this channel. */
|
|
549
|
+
listeners: NonNegativeInt
|
|
550
|
+
});
|
|
366
551
|
//#endregion
|
|
367
552
|
//#region ../schema/src/air-tag.ts
|
|
368
553
|
const AIR_TAG_RELATIONS = [
|
|
@@ -386,6 +571,11 @@ const Coordinate = Schema.Int.check(Schema.isBetween({
|
|
|
386
571
|
minimum: 0,
|
|
387
572
|
maximum: AIR_TAG_MAX_COORDINATE
|
|
388
573
|
}));
|
|
574
|
+
const Level = Schema.Int.check(Schema.isBetween({
|
|
575
|
+
minimum: 0,
|
|
576
|
+
maximum: 1e4
|
|
577
|
+
}));
|
|
578
|
+
const Profession = Schema.NonEmptyString.check(Schema.isMaxLength(1));
|
|
389
579
|
const AirTagRelationSchema = Schema.Literals(AIR_TAG_RELATIONS);
|
|
390
580
|
const AirTagClanSchema = Schema.Struct({
|
|
391
581
|
id: SafeNatural,
|
|
@@ -397,24 +587,22 @@ const AirTagObservationSchema = Schema.Struct({
|
|
|
397
587
|
clan: Schema.optionalKey(AirTagClanSchema),
|
|
398
588
|
relation: AirTagRelationSchema,
|
|
399
589
|
x: Coordinate,
|
|
400
|
-
y: Coordinate
|
|
590
|
+
y: Coordinate,
|
|
591
|
+
lvl: Schema.optionalKey(Level),
|
|
592
|
+
prof: Schema.optionalKey(Profession),
|
|
593
|
+
stasis: Schema.optionalKey(Schema.Boolean)
|
|
594
|
+
});
|
|
595
|
+
const AirTagDepartureSchema = Schema.Struct({
|
|
596
|
+
targetId: ShortString,
|
|
597
|
+
reason: Schema.Literals(["left-map", "out-of-sight"])
|
|
401
598
|
});
|
|
402
599
|
const AirTagObservationBatchSchema = Schema.Struct({
|
|
403
600
|
expectedMapId: Coordinate,
|
|
404
|
-
observations: Schema.Array(AirTagObservationSchema)
|
|
405
|
-
|
|
406
|
-
Schema.Struct({
|
|
407
|
-
requestId: ShortString,
|
|
408
|
-
enabled: Schema.Boolean,
|
|
409
|
-
expectedMapId: Schema.optionalKey(Coordinate)
|
|
601
|
+
observations: Schema.Array(AirTagObservationSchema),
|
|
602
|
+
departures: Schema.optionalKey(Schema.Array(AirTagDepartureSchema))
|
|
410
603
|
});
|
|
411
604
|
const AirTagTargetSchema = Schema.Struct({
|
|
412
|
-
|
|
413
|
-
nickname: ShortString,
|
|
414
|
-
clan: Schema.optionalKey(AirTagClanSchema),
|
|
415
|
-
relation: AirTagRelationSchema,
|
|
416
|
-
x: Coordinate,
|
|
417
|
-
y: Coordinate,
|
|
605
|
+
...AirTagObservationSchema.fields,
|
|
418
606
|
observedAt: SafeNatural,
|
|
419
607
|
enemyObservedAt: Schema.optionalKey(SafeNatural),
|
|
420
608
|
clanEnemyObservedAt: Schema.optionalKey(SafeNatural)
|
|
@@ -429,23 +617,220 @@ const AirTagScopeIdentityFields = {
|
|
|
429
617
|
};
|
|
430
618
|
const AirTagScopeSnapshotSchema = Schema.Struct({
|
|
431
619
|
...AirTagScopeIdentityFields,
|
|
432
|
-
targets: Schema.Array(AirTagTargetSchema)
|
|
620
|
+
targets: Schema.Array(AirTagTargetSchema),
|
|
621
|
+
serverTime: Schema.optionalKey(SafeNatural)
|
|
433
622
|
});
|
|
434
623
|
const AirTagUpdateEventSchema = Schema.Struct({
|
|
435
624
|
...AirTagScopeIdentityFields,
|
|
436
625
|
target: AirTagTargetSchema
|
|
437
626
|
});
|
|
438
|
-
Schema.
|
|
627
|
+
const AirTagScopeUpdateEventSchema = Schema.Struct({
|
|
628
|
+
...AirTagScopeIdentityFields,
|
|
629
|
+
targets: Schema.Array(AirTagTargetSchema),
|
|
630
|
+
removedTargetIds: Schema.Array(ShortString)
|
|
631
|
+
});
|
|
632
|
+
const AirTagMapThreatEventSchema = Schema.Struct({
|
|
633
|
+
guildId: GuildId,
|
|
634
|
+
world: ShortString,
|
|
635
|
+
mapId: Coordinate,
|
|
636
|
+
mapName: Schema.NonEmptyString.check(Schema.isMaxLength(128)),
|
|
637
|
+
revision: SafeNatural,
|
|
638
|
+
enemies: Schema.Array(Schema.Struct({
|
|
639
|
+
targetId: AirTagObservationSchema.fields.targetId,
|
|
640
|
+
nickname: AirTagObservationSchema.fields.nickname,
|
|
641
|
+
clan: AirTagObservationSchema.fields.clan,
|
|
642
|
+
lvl: AirTagObservationSchema.fields.lvl,
|
|
643
|
+
prof: AirTagObservationSchema.fields.prof,
|
|
644
|
+
stasis: AirTagObservationSchema.fields.stasis,
|
|
645
|
+
ageMs: SafeNatural
|
|
646
|
+
}))
|
|
647
|
+
});
|
|
439
648
|
Schema.is(AirTagObservationSchema);
|
|
440
|
-
Schema.is(
|
|
649
|
+
Schema.is(AirTagRelationSchema);
|
|
441
650
|
Schema.is(AirTagScopeSnapshotSchema);
|
|
442
651
|
Schema.is(AirTagUpdateEventSchema);
|
|
652
|
+
Schema.is(AirTagScopeUpdateEventSchema);
|
|
653
|
+
Schema.is(AirTagDepartureSchema);
|
|
654
|
+
//#endregion
|
|
655
|
+
//#region ../schema/src/battle-ping.ts
|
|
656
|
+
/** Pings placed on an enemy warrior. `attack` is the team's shared target. */
|
|
657
|
+
const BATTLE_ENEMY_PING_TYPES = [
|
|
658
|
+
"attack",
|
|
659
|
+
"caution",
|
|
660
|
+
"spare"
|
|
661
|
+
];
|
|
662
|
+
/** Requests placed on an allied warrior (or on the sender's own warrior). */
|
|
663
|
+
const BATTLE_REQUEST_PING_TYPES = [
|
|
664
|
+
"attack-speed-aura",
|
|
665
|
+
"atmo",
|
|
666
|
+
"cleanse",
|
|
667
|
+
"emanating-arrow",
|
|
668
|
+
"fury",
|
|
669
|
+
"heal",
|
|
670
|
+
"healing-wave",
|
|
671
|
+
"need-heal",
|
|
672
|
+
"poison",
|
|
673
|
+
"protection-aura",
|
|
674
|
+
"rime",
|
|
675
|
+
"stigma",
|
|
676
|
+
"taunt"
|
|
677
|
+
];
|
|
678
|
+
/**
|
|
679
|
+
* Calls to the whole battle team, sent from the sender's own warrior. Only
|
|
680
|
+
* sockets that negotiated `lootlog.battle-ping.team.v1` send or receive them.
|
|
681
|
+
*/
|
|
682
|
+
const BATTLE_TEAM_PING_TYPES = ["quick-fight"];
|
|
683
|
+
const BATTLE_PING_TYPES = [
|
|
684
|
+
...BATTLE_ENEMY_PING_TYPES,
|
|
685
|
+
...BATTLE_REQUEST_PING_TYPES,
|
|
686
|
+
...BATTLE_TEAM_PING_TYPES
|
|
687
|
+
];
|
|
688
|
+
/** A Margonem party holds at most ten characters, so nine other recipients. */
|
|
689
|
+
const MAX_BATTLE_PING_RECIPIENTS = 9;
|
|
690
|
+
/** Margonem battle warrior ids: positive for characters, negative for NPCs. */
|
|
691
|
+
const MAX_BATTLE_WARRIOR_ID = 2147483647;
|
|
692
|
+
const BattlePingTypeSchema = Schema.Literals(BATTLE_PING_TYPES);
|
|
693
|
+
const BattleWarriorIdSchema = Schema.Int.check(Schema.isBetween({
|
|
694
|
+
minimum: -2147483647,
|
|
695
|
+
maximum: MAX_BATTLE_WARRIOR_ID
|
|
696
|
+
}), Schema.makeFilter((id) => id !== 0 || "warrior id must not be 0"));
|
|
697
|
+
const CharacterIdSchema$1 = Schema.String.check(Schema.isPattern(/^[1-9]\d{0,9}$/u));
|
|
698
|
+
Schema.is(BattlePingTypeSchema);
|
|
699
|
+
Schema.is(Schema.Literals(BATTLE_ENEMY_PING_TYPES));
|
|
700
|
+
Schema.is(Schema.Literals(BATTLE_TEAM_PING_TYPES));
|
|
701
|
+
const BattlePingSendPayloadSchema = Schema.Struct({
|
|
702
|
+
expectedMapId: NonNegativeInt,
|
|
703
|
+
type: BattlePingTypeSchema,
|
|
704
|
+
warriorId: BattleWarriorIdSchema,
|
|
705
|
+
recipientCharacterIds: Schema.Array(CharacterIdSchema$1).check(Schema.isMinLength(1), Schema.isMaxLength(MAX_BATTLE_PING_RECIPIENTS))
|
|
706
|
+
});
|
|
707
|
+
const BattlePingEventSchema = Schema.Struct({
|
|
708
|
+
pingId: Schema.NonEmptyString,
|
|
709
|
+
world: Schema.NonEmptyString,
|
|
710
|
+
mapId: NonNegativeInt,
|
|
711
|
+
type: BattlePingTypeSchema,
|
|
712
|
+
warriorId: BattleWarriorIdSchema,
|
|
713
|
+
sender: Schema.Struct({
|
|
714
|
+
characterId: Schema.NonEmptyString,
|
|
715
|
+
name: Schema.NonEmptyString
|
|
716
|
+
}),
|
|
717
|
+
createdAt: NonNegativeInt
|
|
718
|
+
});
|
|
719
|
+
//#endregion
|
|
720
|
+
//#region ../schema/src/loot-split.ts
|
|
721
|
+
/**
|
|
722
|
+
* A participant's choice for one item of a group loot split, as Margonem's
|
|
723
|
+
* `loot.states` reports it: 0 "nie chcę", 1 "chcę", 2 "bardzo chcę".
|
|
724
|
+
*/
|
|
725
|
+
const LOOT_SPLIT_CHOICES = [
|
|
726
|
+
0,
|
|
727
|
+
1,
|
|
728
|
+
2
|
|
729
|
+
];
|
|
730
|
+
/** A Margonem party holds at most ten characters, so nine other recipients. */
|
|
731
|
+
const MAX_LOOT_SPLIT_RECIPIENTS = 9;
|
|
732
|
+
/** Bounds a frame; one group loot holds far fewer items. */
|
|
733
|
+
const MAX_LOOT_SPLIT_ITEMS = 64;
|
|
734
|
+
const ITEM_ID_PATTERN = /^[1-9]\d{0,17}$/u;
|
|
735
|
+
const ItemIdSchema = Schema.String.check(Schema.isPattern(ITEM_ID_PATTERN));
|
|
736
|
+
const CharacterIdSchema = Schema.String.check(Schema.isPattern(/^[1-9]\d{0,9}$/u));
|
|
737
|
+
const RecipientCharacterIdsSchema = Schema.Array(CharacterIdSchema).check(Schema.isMinLength(1), Schema.isMaxLength(MAX_LOOT_SPLIT_RECIPIENTS));
|
|
738
|
+
const SenderSchema = Schema.Struct({
|
|
739
|
+
characterId: Schema.NonEmptyString,
|
|
740
|
+
name: Schema.NonEmptyString
|
|
741
|
+
});
|
|
742
|
+
const LootSplitChoicesSchema = Schema.Record(Schema.String, Schema.Literals(LOOT_SPLIT_CHOICES)).check(Schema.makeFilter((choices) => {
|
|
743
|
+
const ids = Object.keys(choices);
|
|
744
|
+
return ids.length <= MAX_LOOT_SPLIT_ITEMS && ids.every((id) => ITEM_ID_PATTERN.test(id)) || "choices must map at most 64 item ids";
|
|
745
|
+
}));
|
|
746
|
+
const LootSplitSendPayloadSchema = Schema.Struct({
|
|
747
|
+
splitId: ItemIdSchema,
|
|
748
|
+
choices: LootSplitChoicesSchema,
|
|
749
|
+
accepted: Schema.Boolean,
|
|
750
|
+
recipientCharacterIds: RecipientCharacterIdsSchema
|
|
751
|
+
});
|
|
752
|
+
const LootSplitEventSchema = Schema.Struct({
|
|
753
|
+
splitId: ItemIdSchema,
|
|
754
|
+
world: Schema.NonEmptyString,
|
|
755
|
+
sender: SenderSchema,
|
|
756
|
+
choices: LootSplitChoicesSchema,
|
|
757
|
+
accepted: Schema.Boolean,
|
|
758
|
+
sentAt: NonNegativeInt
|
|
759
|
+
});
|
|
760
|
+
const LootSplitAutoAcceptPayloadSchema = Schema.Struct({ recipientCharacterIds: RecipientCharacterIdsSchema });
|
|
761
|
+
const LootSplitAutoAcceptedEventSchema = Schema.Struct({
|
|
762
|
+
world: Schema.NonEmptyString,
|
|
763
|
+
sender: SenderSchema,
|
|
764
|
+
sentAt: NonNegativeInt
|
|
765
|
+
});
|
|
766
|
+
const LootSplitAckSchema = Schema.Union([Schema.Struct({ status: Schema.Literal("accepted") }), Schema.Struct({
|
|
767
|
+
status: Schema.Literal("rejected"),
|
|
768
|
+
code: Schema.Literals([
|
|
769
|
+
"forbidden",
|
|
770
|
+
"invalid-context",
|
|
771
|
+
"invalid-payload",
|
|
772
|
+
"rate-limited",
|
|
773
|
+
"temporarily-unavailable"
|
|
774
|
+
]),
|
|
775
|
+
retryAfterMs: Schema.optionalKey(NonNegativeInt)
|
|
776
|
+
})]);
|
|
777
|
+
/** The fields organization NPC visibility needs, keyed by the NPC's runtime id. */
|
|
778
|
+
const NpcPresenceNpcSchema = Schema.Struct({
|
|
779
|
+
id: NonNegativeInt,
|
|
780
|
+
lvl: NonNegativeInt,
|
|
781
|
+
wt: NonNegativeInt,
|
|
782
|
+
prof: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(8))),
|
|
783
|
+
type: Schema.optionalKey(NonNegativeInt)
|
|
784
|
+
});
|
|
785
|
+
/** Every NPC a character currently sees on its map; each report replaces the previous one. */
|
|
786
|
+
const NpcPresenceReportSchema = Schema.Struct({
|
|
787
|
+
world: Schema.NonEmptyString.check(Schema.isMaxLength(64)),
|
|
788
|
+
organizationIds: Schema.Array(Schema.NonEmptyString).check(Schema.isMaxLength(20)),
|
|
789
|
+
npcs: Schema.Array(NpcPresenceNpcSchema).check(Schema.isMaxLength(50))
|
|
790
|
+
});
|
|
791
|
+
/** Sent only when an NPC gains its first reporter or loses its last one. */
|
|
792
|
+
const NpcPresenceEventSchema = Schema.Struct({
|
|
793
|
+
organizationId: Schema.NonEmptyString,
|
|
794
|
+
world: Schema.NonEmptyString,
|
|
795
|
+
/** Orders the updates of one Organization and world across gateway replicas. */
|
|
796
|
+
revision: NonNegativeInt,
|
|
797
|
+
npc: NpcPresenceNpcSchema,
|
|
798
|
+
standing: Schema.Boolean,
|
|
799
|
+
/** Gateway time the NPC was first reported. */
|
|
800
|
+
since: NonNegativeInt
|
|
801
|
+
});
|
|
802
|
+
const NpcPresenceSnapshotSchema = Schema.Struct({
|
|
803
|
+
revision: NonNegativeInt,
|
|
804
|
+
npcs: Schema.Array(Schema.Struct({
|
|
805
|
+
npc: NpcPresenceNpcSchema,
|
|
806
|
+
since: NonNegativeInt
|
|
807
|
+
}))
|
|
808
|
+
});
|
|
809
|
+
const NpcPresenceRejectCode = Schema.Literals([
|
|
810
|
+
"forbidden",
|
|
811
|
+
"invalid-context",
|
|
812
|
+
"invalid-payload",
|
|
813
|
+
"rate-limited",
|
|
814
|
+
"temporarily-unavailable"
|
|
815
|
+
]);
|
|
816
|
+
const NpcPresenceReportAckSchema = Schema.Union([Schema.Struct({ status: Schema.Literal("accepted") }), Schema.Struct({
|
|
817
|
+
status: Schema.Literal("rejected"),
|
|
818
|
+
code: NpcPresenceRejectCode,
|
|
819
|
+
retryAfterMs: Schema.optional(NonNegativeInt)
|
|
820
|
+
})]);
|
|
821
|
+
//#endregion
|
|
822
|
+
//#region ../schema/src/map-ping.ts
|
|
443
823
|
const MapPingTypeSchema = Schema.Literals([
|
|
444
824
|
"attention",
|
|
445
825
|
"enemy",
|
|
446
826
|
"regroup",
|
|
447
827
|
"avoid"
|
|
448
828
|
]);
|
|
829
|
+
/** Margonem NPC and character ids are positive 32-bit integers. */
|
|
830
|
+
const MapPingCharacterIdSchema = Schema.Int.check(Schema.isBetween({
|
|
831
|
+
minimum: 1,
|
|
832
|
+
maximum: 2147483647
|
|
833
|
+
}));
|
|
449
834
|
const MapPingRejectCodeSchema = Schema.Literals([
|
|
450
835
|
"forbidden",
|
|
451
836
|
"invalid-context",
|
|
@@ -454,11 +839,16 @@ const MapPingRejectCodeSchema = Schema.Literals([
|
|
|
454
839
|
"temporarily-unavailable"
|
|
455
840
|
]);
|
|
456
841
|
Schema.is(MapPingTypeSchema);
|
|
457
|
-
const
|
|
458
|
-
expectedMapId: NonNegativeInt,
|
|
842
|
+
const mapPingFields = {
|
|
459
843
|
type: MapPingTypeSchema,
|
|
460
844
|
x: NonNegativeInt,
|
|
461
|
-
y: NonNegativeInt
|
|
845
|
+
y: NonNegativeInt,
|
|
846
|
+
npcId: Schema.optionalKey(MapPingCharacterIdSchema),
|
|
847
|
+
playerId: Schema.optionalKey(MapPingCharacterIdSchema)
|
|
848
|
+
};
|
|
849
|
+
const MapPingSendPayloadSchema = Schema.Struct({
|
|
850
|
+
expectedMapId: NonNegativeInt,
|
|
851
|
+
...mapPingFields
|
|
462
852
|
});
|
|
463
853
|
const MapPingAckSchema = Schema.Union([Schema.Struct({
|
|
464
854
|
status: Schema.Literal("accepted"),
|
|
@@ -472,9 +862,7 @@ const MapPingEventSchema = Schema.Struct({
|
|
|
472
862
|
pingId: Schema.NonEmptyString,
|
|
473
863
|
world: Schema.NonEmptyString,
|
|
474
864
|
mapId: NonNegativeInt,
|
|
475
|
-
|
|
476
|
-
x: NonNegativeInt,
|
|
477
|
-
y: NonNegativeInt,
|
|
865
|
+
...mapPingFields,
|
|
478
866
|
sender: Schema.Struct({
|
|
479
867
|
characterId: Schema.NonEmptyString,
|
|
480
868
|
name: Schema.NonEmptyString
|
|
@@ -482,302 +870,24 @@ const MapPingEventSchema = Schema.Struct({
|
|
|
482
870
|
createdAt: NonNegativeInt
|
|
483
871
|
});
|
|
484
872
|
//#endregion
|
|
485
|
-
//#region ../
|
|
486
|
-
const discordPermissionFields = {
|
|
487
|
-
hasRequiredPermissions: Schema.Boolean,
|
|
488
|
-
requiredPermissions: Schema.Array(Schema.String),
|
|
489
|
-
grantedPermissions: Schema.Array(Schema.String),
|
|
490
|
-
missingPermissions: Schema.Array(Schema.String)
|
|
491
|
-
};
|
|
492
|
-
const DiscordGuildSyncStatus = Schema.Literals([
|
|
493
|
-
"SYNCED",
|
|
494
|
-
"SYNCING",
|
|
495
|
-
"FAILED",
|
|
496
|
-
"STALE",
|
|
497
|
-
"NOT_FOUND"
|
|
498
|
-
]);
|
|
499
|
-
//#endregion
|
|
500
|
-
//#region ../protocol/src/rabbit/topology.ts
|
|
501
|
-
const RabbitExchange = {
|
|
502
|
-
DEFAULT: "default",
|
|
503
|
-
DEAD_LETTER: "dlx",
|
|
504
|
-
RETRY: "retry"
|
|
505
|
-
};
|
|
506
|
-
const RabbitExchangeName = Schema.Literals([
|
|
507
|
-
RabbitExchange.DEFAULT,
|
|
508
|
-
RabbitExchange.DEAD_LETTER,
|
|
509
|
-
RabbitExchange.RETRY
|
|
510
|
-
]);
|
|
511
|
-
const RabbitRoutingKey = {
|
|
512
|
-
USERS_ONLINE_CHECKPOINT_V1: "users.online.checkpoint.v1",
|
|
513
|
-
USERS_ONLINE_CHECKPOINT_V1_RETRY: "users.online.checkpoint.v1.retry",
|
|
514
|
-
USERS_ONLINE_CHECKPOINT_V1_DLQ: "users.online.checkpoint.v1.dlq",
|
|
515
|
-
ACTIVITY_LOG_CREATE: "activity.log.create",
|
|
516
|
-
ACTIVITY_LOG_CREATE_DLQ: "activity.log.create.dlq",
|
|
517
|
-
ACTIVITY_LOG_CREATE_RETRY: "activity.log.create.retry",
|
|
518
|
-
DISCORD_GUILD_CHANNEL_DELETED: "discord.guild.channel.deleted",
|
|
519
|
-
DISCORD_GUILD_CHANNEL_UPSERTED: "discord.guild.channel.upserted",
|
|
520
|
-
DISCORD_GUILD_CHANNELS_SYNC_FAILED: "discord.guild.channels.sync.failed",
|
|
521
|
-
DISCORD_GUILD_CHANNELS_SYNCED: "discord.guild.channels.synced",
|
|
522
|
-
DISCORD_GUILD_SYNC_STATE_UPDATED: "discord.guild.sync-state.updated",
|
|
523
|
-
EVENT_HERO_KILLED: "event.hero.killed",
|
|
524
|
-
EVENT_MAP_STATUS_UPDATE: "event.map-status.update",
|
|
525
|
-
EVENT_RANKING_UPDATE: "event.ranking.update",
|
|
526
|
-
EVENT_RESPAWN_WINDOW_CLOSED: "event.respawn-window.closed",
|
|
527
|
-
EVENT_RESPAWN_WINDOW_OPENED: "event.respawn-window.opened",
|
|
528
|
-
GUILDS_CLEAR_MESSAGES: "guilds.clear.messages",
|
|
529
|
-
GUILDS_CREATE: "guilds.create",
|
|
530
|
-
GUILDS_CREATE_DLQ: "guilds.create.dlq",
|
|
531
|
-
GUILDS_CREATE_RETRY: "guilds.create.retry",
|
|
532
|
-
GUILDS_CREATE_ROLE: "guilds.create.role",
|
|
533
|
-
GUILDS_CREATE_ROLE_DLQ: "guilds.create.role.dlq",
|
|
534
|
-
GUILDS_CREATE_ROLE_RETRY: "guilds.create.role.retry",
|
|
535
|
-
GUILDS_DELETE: "guilds.delete",
|
|
536
|
-
GUILDS_DELETE_DLQ: "guilds.delete.dlq",
|
|
537
|
-
GUILDS_DELETE_MESSAGE: "guilds.delete.message",
|
|
538
|
-
GUILDS_DELETE_RETRY: "guilds.delete.retry",
|
|
539
|
-
GUILDS_DELETE_ROLE: "guilds.delete.role",
|
|
540
|
-
GUILDS_DELETE_ROLE_DLQ: "guilds.delete.role.dlq",
|
|
541
|
-
GUILDS_DELETE_ROLE_RETRY: "guilds.delete.role.retry",
|
|
542
|
-
GUILDS_INITIALIZE: "guilds.initialize",
|
|
543
|
-
GUILDS_INITIALIZE_BOT: "guilds.initialize-bot",
|
|
544
|
-
GUILDS_INITIALIZE_DLQ: "guilds.initialize.dlq",
|
|
545
|
-
GUILDS_INITIALIZE_RETRY: "guilds.initialize.retry",
|
|
546
|
-
GUILDS_KILLS_ACCEPTED_V1: "guilds.kills.accepted.v1",
|
|
547
|
-
GUILDS_KILLS_ACCEPTED_V1_RETRY: "guilds.kills.accepted.v1.retry",
|
|
548
|
-
GUILDS_KILLS_ACCEPTED_V1_DLQ: "guilds.kills.accepted.v1.dlq",
|
|
549
|
-
GUILDS_LOOTS_CREATE: "guilds.loots.create",
|
|
550
|
-
GUILDS_LOOTS_CREATE_DLQ: "guilds.loots.create.dlq",
|
|
551
|
-
GUILDS_LOOTS_CREATE_RETRY: "guilds.loots.create.retry",
|
|
552
|
-
GUILDS_LOOTS_DELETE: "guilds.loots.delete",
|
|
553
|
-
GUILDS_LOOTS_SHARE_UPDATE: "guilds.loots.share.update",
|
|
554
|
-
GUILDS_LOOTS_SHARE_UPDATE_DLQ: "guilds.loots.share.update.dlq",
|
|
555
|
-
GUILDS_LOOTS_SHARE_UPDATE_RETRY: "guilds.loots.share.update.retry",
|
|
556
|
-
GUILDS_LOOTS_UPDATE: "guilds.loots.update",
|
|
557
|
-
GUILDS_MEMBERS_ADD: "guilds.members.add",
|
|
558
|
-
GUILDS_MEMBERS_ADD_DLQ: "guilds.members.add.dlq",
|
|
559
|
-
GUILDS_MEMBERS_ADD_RETRY: "guilds.members.add.retry",
|
|
560
|
-
GUILDS_MEMBERS_ADD_ROLE: "guilds.members.add.role",
|
|
561
|
-
GUILDS_MEMBERS_ADD_ROLE_DLQ: "guilds.members.add.role.dlq",
|
|
562
|
-
GUILDS_MEMBERS_ADD_ROLE_RETRY: "guilds.members.add.role.retry",
|
|
563
|
-
GUILDS_MEMBERS_REFRESH_JOB_UPDATE: "guilds.members.refresh.job.update",
|
|
564
|
-
GUILDS_MEMBERS_REMOVE: "guilds.members.remove",
|
|
565
|
-
GUILDS_MEMBERS_REMOVE_DLQ: "guilds.members.remove.dlq",
|
|
566
|
-
GUILDS_MEMBERS_REMOVE_RETRY: "guilds.members.remove.retry",
|
|
567
|
-
GUILDS_MEMBERS_REMOVE_ROLE: "guilds.members.remove.role",
|
|
568
|
-
GUILDS_MEMBERS_REMOVE_ROLE_DLQ: "guilds.members.remove.role.dlq",
|
|
569
|
-
GUILDS_MEMBERS_REMOVE_ROLE_RETRY: "guilds.members.remove.role.retry",
|
|
570
|
-
GUILDS_MEMBERS_UPDATE: "guilds.members.update",
|
|
571
|
-
GUILDS_MEMBERS_UPDATE_DLQ: "guilds.members.update.dlq",
|
|
572
|
-
GUILDS_MEMBERS_UPDATE_RETRY: "guilds.members.update.retry",
|
|
573
|
-
GUILDS_NOTIFICATIONS_SEND: "guilds.notifications.send",
|
|
574
|
-
GUILDS_NOTIFICATIONS_SEND_DLQ: "guilds.notifications.send.dlq",
|
|
575
|
-
GUILDS_NOTIFICATIONS_SEND_RETRY: "guilds.notifications.send.retry",
|
|
576
|
-
GUILDS_NOTIFICATIONS_VOLUNTEER: "guilds.notifications.volunteer",
|
|
577
|
-
GUILDS_NOTIFICATIONS_VOLUNTEER_DLQ: "guilds.notifications.volunteer.dlq",
|
|
578
|
-
GUILDS_NOTIFICATIONS_VOLUNTEER_RETRY: "guilds.notifications.volunteer.retry",
|
|
579
|
-
GUILDS_PARTY_GATHERING: "guilds.party-gathering",
|
|
580
|
-
GUILDS_PARTY_GATHERING_CANCEL: "guilds.party-gathering.cancel",
|
|
581
|
-
GUILDS_PARTY_GATHERING_DLQ: "guilds.party-gathering.dlq",
|
|
582
|
-
GUILDS_PARTY_GATHERING_RETRY: "guilds.party-gathering.retry",
|
|
583
|
-
GUILDS_RESERVATIONS_CHANGED_V2: "guilds.reservations.v2.changed",
|
|
584
|
-
GUILDS_RESERVATIONS_CHANGED_V2_DLQ: "guilds.reservations.v2.changed.dlq",
|
|
585
|
-
GUILDS_RESERVATIONS_CHANGED_V2_RETRY: "guilds.reservations.v2.changed.retry",
|
|
586
|
-
GUILDS_RESERVATIONS_CREATE: "guilds.reservations.create",
|
|
587
|
-
GUILDS_RESERVATIONS_CREATE_DLQ: "guilds.reservations.create.dlq",
|
|
588
|
-
GUILDS_RESERVATIONS_CREATE_RETRY: "guilds.reservations.create.retry",
|
|
589
|
-
GUILDS_RESERVATIONS_DELETE: "guilds.reservations.delete",
|
|
590
|
-
GUILDS_RESERVATIONS_DELETE_DLQ: "guilds.reservations.delete.dlq",
|
|
591
|
-
GUILDS_RESERVATIONS_DELETE_RETRY: "guilds.reservations.delete.retry",
|
|
592
|
-
GUILDS_SEND_MESSAGE: "guilds.send.message",
|
|
593
|
-
GUILDS_SEND_MESSAGE_DLQ: "guilds.send.message.dlq",
|
|
594
|
-
GUILDS_SEND_MESSAGE_RETRY: "guilds.send.message.retry",
|
|
595
|
-
GUILDS_SYNC: "guilds.sync",
|
|
596
|
-
GUILDS_SYNC_TRIGGER: "guilds.sync.trigger",
|
|
597
|
-
GUILDS_TIMERS_DELETE: "guilds.timers.delete",
|
|
598
|
-
GUILDS_TIMERS_DELETE_DLQ: "guilds.timers.delete.dlq",
|
|
599
|
-
GUILDS_TIMERS_DELETE_RETRY: "guilds.timers.delete.retry",
|
|
600
|
-
GUILDS_TIMERS_UPDATE: "guilds.timers.update",
|
|
601
|
-
GUILDS_TIMERS_UPDATE_DLQ: "guilds.timers.update.dlq",
|
|
602
|
-
GUILDS_TIMERS_UPDATE_RETRY: "guilds.timers.update.retry",
|
|
603
|
-
GUILDS_UPDATE: "guilds.update",
|
|
604
|
-
GUILDS_UPDATE_DLQ: "guilds.update.dlq",
|
|
605
|
-
GUILDS_UPDATE_MESSAGE: "guilds.update.message",
|
|
606
|
-
GUILDS_UPDATE_RETRY: "guilds.update.retry",
|
|
607
|
-
GUILDS_UPDATE_ROLE: "guilds.update.role",
|
|
608
|
-
GUILDS_UPDATE_ROLE_DLQ: "guilds.update.role.dlq",
|
|
609
|
-
GUILDS_UPDATE_ROLE_RETRY: "guilds.update.role.retry",
|
|
610
|
-
NOTIFICATIONS_DELIVERY_RESULT: "notifications.delivery.result",
|
|
611
|
-
NOTIFICATIONS_DISCORD_SEND: "notifications.discord.send",
|
|
612
|
-
NOTIFICATIONS_LOOT_CREATED: "notifications.loot.created",
|
|
613
|
-
NOTIFICATIONS_TIMER_DELETED: "notifications.timer.deleted",
|
|
614
|
-
NOTIFICATIONS_TIMER_UPDATED: "notifications.timer.updated",
|
|
615
|
-
GAME_CHARACTER_OFFLINE: "game.character.offline",
|
|
616
|
-
PRESENCE_CHECK_REQUEST: "presence.check.request",
|
|
617
|
-
PRESENCE_COVERAGE_CHECK: "presence.coverage.check",
|
|
618
|
-
SEARCH_ITEMS_INDEX: "search.items.index",
|
|
619
|
-
SEARCH_NPCS_INDEX: "search.npcs.index",
|
|
620
|
-
SEARCH_PLAYERS_INDEX: "search.players.index",
|
|
621
|
-
USERS_PARTY_READY_ROOM_UPDATED: "users.party-ready-room.updated",
|
|
622
|
-
USERS_PARTY_READY_ROOM_UPDATED_DLQ: "users.party-ready-room.updated.dlq",
|
|
623
|
-
USERS_PARTY_READY_ROOM_UPDATED_RETRY: "users.party-ready-room.updated.retry"
|
|
624
|
-
};
|
|
625
|
-
const RabbitRoutingKeyName = Schema.Literals(Object.values(RabbitRoutingKey));
|
|
626
|
-
Schema.Struct({
|
|
627
|
-
name: RabbitExchangeName,
|
|
628
|
-
type: Schema.Literal("topic"),
|
|
629
|
-
durable: Schema.Boolean
|
|
630
|
-
});
|
|
631
|
-
Schema.Struct({
|
|
632
|
-
name: Schema.String,
|
|
633
|
-
exchange: RabbitExchangeName,
|
|
634
|
-
routingKey: RabbitRoutingKeyName,
|
|
635
|
-
durable: Schema.Boolean,
|
|
636
|
-
messageTtl: Schema.optional(Schema.Int),
|
|
637
|
-
singleActiveConsumer: Schema.optional(Schema.Boolean),
|
|
638
|
-
deadLetterExchange: Schema.optional(RabbitExchangeName),
|
|
639
|
-
deadLetterRoutingKey: Schema.optional(RabbitRoutingKeyName)
|
|
640
|
-
});
|
|
641
|
-
RabbitExchange.DEFAULT, RabbitExchange.RETRY, RabbitExchange.DEAD_LETTER;
|
|
642
|
-
Schema.Struct({
|
|
643
|
-
userId: NonEmptyString,
|
|
644
|
-
discordId: NonEmptyString,
|
|
645
|
-
world: NonEmptyString,
|
|
646
|
-
characterId: NonEmptyString,
|
|
647
|
-
organizationIds: Schema.Array(NonEmptyString),
|
|
648
|
-
disconnectedAt: NonNegativeInt
|
|
649
|
-
});
|
|
650
|
-
const NullableString = Schema.NullOr(Schema.String);
|
|
651
|
-
const NullableNumber = Schema.NullOr(Schema.Number);
|
|
652
|
-
const GuildRole = Schema.Struct({
|
|
653
|
-
id: NonEmptyString,
|
|
654
|
-
name: NonEmptyString,
|
|
655
|
-
color: Schema.Number,
|
|
656
|
-
admin: Schema.Boolean,
|
|
657
|
-
position: Schema.Number
|
|
658
|
-
});
|
|
659
|
-
Schema.Struct({
|
|
660
|
-
guildId: NonEmptyString,
|
|
661
|
-
name: NonEmptyString,
|
|
662
|
-
icon: NullableString,
|
|
663
|
-
ownerId: NonEmptyString,
|
|
664
|
-
roles: Schema.Array(GuildRole)
|
|
665
|
-
});
|
|
666
|
-
Schema.Struct({
|
|
667
|
-
guildId: NonEmptyString,
|
|
668
|
-
name: NonEmptyString,
|
|
669
|
-
icon: NullableString,
|
|
670
|
-
ownerId: NonEmptyString
|
|
671
|
-
});
|
|
672
|
-
Schema.Struct({ guildId: NonEmptyString });
|
|
673
|
-
Schema.Struct({
|
|
674
|
-
guildId: NonEmptyString,
|
|
675
|
-
...GuildRole.fields
|
|
676
|
-
});
|
|
677
|
-
Schema.Struct({
|
|
678
|
-
guildId: NonEmptyString,
|
|
679
|
-
id: NonEmptyString
|
|
680
|
-
});
|
|
681
|
-
Schema.Struct({
|
|
682
|
-
guildId: NonEmptyString,
|
|
683
|
-
discordId: NonEmptyString,
|
|
684
|
-
userId: NonEmptyString
|
|
685
|
-
});
|
|
686
|
-
Schema.Struct({
|
|
687
|
-
guildId: NonEmptyString,
|
|
688
|
-
mapName: NonEmptyString
|
|
689
|
-
});
|
|
690
|
-
Schema.Struct({
|
|
691
|
-
guildId: NonEmptyString,
|
|
692
|
-
mapName: NonEmptyString,
|
|
693
|
-
discordId: NonEmptyString,
|
|
694
|
-
hasPlayer: Schema.Boolean,
|
|
695
|
-
isAfk: Schema.optional(Schema.Boolean)
|
|
696
|
-
});
|
|
697
|
-
const DiscordGuildChannel = Schema.Struct({
|
|
698
|
-
guildId: NonEmptyString,
|
|
699
|
-
channelId: NonEmptyString,
|
|
700
|
-
name: NonEmptyString,
|
|
701
|
-
channelType: NonEmptyString,
|
|
702
|
-
parentId: NullableString,
|
|
703
|
-
position: Schema.Number,
|
|
704
|
-
active: Schema.Boolean,
|
|
705
|
-
canView: Schema.Boolean,
|
|
706
|
-
canSend: Schema.Boolean,
|
|
707
|
-
...discordPermissionFields,
|
|
708
|
-
lastSyncedAt: NonEmptyString
|
|
709
|
-
});
|
|
710
|
-
const DiscordGuildSyncState = Schema.Struct({
|
|
711
|
-
guildId: NonEmptyString,
|
|
712
|
-
status: DiscordGuildSyncStatus,
|
|
713
|
-
...discordPermissionFields,
|
|
714
|
-
channelCount: NonNegativeInt,
|
|
715
|
-
selectableChannelCount: NonNegativeInt,
|
|
716
|
-
lastAttemptAt: NullableString,
|
|
717
|
-
lastSuccessAt: NullableString,
|
|
718
|
-
lastError: NullableString,
|
|
719
|
-
updatedAt: NonEmptyString
|
|
720
|
-
});
|
|
721
|
-
Schema.Struct({
|
|
722
|
-
guildId: NonEmptyString,
|
|
723
|
-
channels: Schema.Array(DiscordGuildChannel),
|
|
724
|
-
syncState: DiscordGuildSyncState
|
|
725
|
-
});
|
|
726
|
-
Schema.Struct({
|
|
727
|
-
guildId: NonEmptyString,
|
|
728
|
-
channel: DiscordGuildChannel,
|
|
729
|
-
syncState: DiscordGuildSyncState
|
|
730
|
-
});
|
|
731
|
-
Schema.Struct({
|
|
732
|
-
guildId: NonEmptyString,
|
|
733
|
-
status: DiscordGuildSyncStatus,
|
|
734
|
-
lastAttemptAt: NonEmptyString,
|
|
735
|
-
lastError: NonEmptyString
|
|
736
|
-
});
|
|
737
|
-
Schema.Struct({
|
|
738
|
-
guildId: NonEmptyString,
|
|
739
|
-
syncState: DiscordGuildSyncState
|
|
740
|
-
});
|
|
741
|
-
Schema.Union([Schema.Struct({ guildId: NonEmptyString }), Schema.Struct({ organizationId: NonEmptyString })]);
|
|
742
|
-
Schema.Struct({
|
|
743
|
-
recipientDiscordId: NonEmptyString,
|
|
744
|
-
eligibleGuildIds: Schema.Array(NonEmptyString),
|
|
745
|
-
update: Schema.Unknown
|
|
746
|
-
});
|
|
747
|
-
Schema.Struct({
|
|
748
|
-
notificationId: NonEmptyString,
|
|
749
|
-
targetDiscordId: NonEmptyString,
|
|
750
|
-
volunteerDiscordId: NonEmptyString,
|
|
751
|
-
world: NonEmptyString,
|
|
752
|
-
character: Schema.Unknown
|
|
753
|
-
});
|
|
873
|
+
//#region ../protocol/src/loot-events.ts
|
|
754
874
|
const GuildLootEventNpc = Schema.Struct({
|
|
755
|
-
lvl: Schema.optional(
|
|
756
|
-
prof: Schema.optional(
|
|
875
|
+
lvl: Schema.optional(Schema.NullOr(Schema.Number)),
|
|
876
|
+
prof: Schema.optional(Schema.NullOr(Schema.String)),
|
|
757
877
|
type: Schema.optional(Schema.NullOr(Schema.Union([Schema.Number, Schema.String]))),
|
|
758
878
|
wt: Schema.optional(Schema.NullOr(Schema.Union([Schema.Number, Schema.String])))
|
|
759
879
|
});
|
|
760
|
-
Schema.Struct({
|
|
761
|
-
sourceNpcs: Schema.optional(Schema.Array(Schema.Struct({
|
|
762
|
-
level: NonNegativeInt,
|
|
763
|
-
type: NpcTypeSchema
|
|
764
|
-
}))),
|
|
765
|
-
feedEntry: Schema.optional(UserFeedItem),
|
|
766
|
-
version: Schema.Literal(1),
|
|
767
|
-
guildId: NonEmptyString,
|
|
768
|
-
world: NonEmptyString,
|
|
769
|
-
npc: Schema.Struct({
|
|
770
|
-
type: NpcTypeSchema,
|
|
771
|
-
lvl: NonNegativeInt
|
|
772
|
-
})
|
|
773
|
-
});
|
|
774
880
|
const GuildLootCreatedEventV2 = Schema.Struct({
|
|
775
881
|
feedEntry: Schema.optional(UserFeedItem),
|
|
776
882
|
version: Schema.Literal(2),
|
|
777
|
-
guildId: NonEmptyString,
|
|
883
|
+
guildId: Schema.NonEmptyString,
|
|
778
884
|
lootId: NonNegativeInt,
|
|
779
885
|
npcs: Schema.Array(GuildLootEventNpc)
|
|
780
886
|
});
|
|
887
|
+
const GuildLootCreatedMessageV2 = Schema.Struct({
|
|
888
|
+
...GuildLootCreatedEventV2.fields,
|
|
889
|
+
loot: Schema.optional(LootSnapshot)
|
|
890
|
+
});
|
|
781
891
|
const GuildLootShareUpdatedEventV2 = Schema.Struct({
|
|
782
892
|
...GuildLootCreatedEventV2.fields,
|
|
783
893
|
lootShare: Schema.Record(Schema.String, Schema.Array(Schema.String))
|
|
@@ -790,105 +900,29 @@ const ReservationChangedEventV2 = Schema.Struct({
|
|
|
790
900
|
"deleted",
|
|
791
901
|
"sharing-changed"
|
|
792
902
|
]),
|
|
793
|
-
sourceGuildId: NonEmptyString,
|
|
794
|
-
audienceGuildIds: Schema.Array(NonEmptyString),
|
|
903
|
+
sourceGuildId: Schema.NonEmptyString,
|
|
904
|
+
audienceGuildIds: Schema.Array(Schema.NonEmptyString),
|
|
795
905
|
reservationId: Schema.NullOr(NonNegativeInt),
|
|
796
906
|
spotId: Schema.NullOr(Schema.String)
|
|
797
907
|
});
|
|
798
|
-
const
|
|
799
|
-
|
|
800
|
-
eventId: NonEmptyString
|
|
801
|
-
});
|
|
802
|
-
Schema.Struct({
|
|
803
|
-
...EventScope.fields,
|
|
804
|
-
heroNpcLvl: Schema.optional(Schema.NullOr(NonNegativeInt)),
|
|
805
|
-
mapId: NonEmptyString,
|
|
806
|
-
reason: Schema.optional(Schema.String)
|
|
807
|
-
});
|
|
808
|
-
Schema.Struct({
|
|
809
|
-
...EventScope.fields,
|
|
810
|
-
heroNpcLvl: Schema.optional(Schema.NullOr(NonNegativeInt)),
|
|
811
|
-
heroId: Schema.optional(NonEmptyString)
|
|
812
|
-
});
|
|
813
|
-
Schema.Struct({
|
|
814
|
-
...EventScope.fields,
|
|
815
|
-
heroNpcLvl: Schema.optional(Schema.NullOr(NonNegativeInt)),
|
|
816
|
-
heroId: NonEmptyString
|
|
817
|
-
});
|
|
818
|
-
Schema.Struct({
|
|
819
|
-
userId: NonEmptyString,
|
|
820
|
-
guildId: Schema.optional(NonEmptyString),
|
|
821
|
-
action: NonEmptyString,
|
|
822
|
-
entityType: Schema.optional(NonEmptyString),
|
|
823
|
-
entityId: Schema.optional(Schema.Union([Schema.String, Schema.Number])),
|
|
824
|
-
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown))
|
|
825
|
-
});
|
|
826
|
-
Schema.Struct({
|
|
827
|
-
guildId: NonEmptyString,
|
|
828
|
-
world: NonEmptyString,
|
|
829
|
-
npcId: NonNegativeInt,
|
|
830
|
-
timerKey: NonEmptyString,
|
|
831
|
-
minSpawnTime: NonEmptyString,
|
|
832
|
-
maxSpawnTime: NonEmptyString,
|
|
833
|
-
npc: Schema.optional(Schema.NullOr(Schema.Struct({ name: Schema.optional(Schema.String) })))
|
|
834
|
-
});
|
|
835
|
-
Schema.Struct({
|
|
836
|
-
guildId: NonEmptyString,
|
|
837
|
-
world: NonEmptyString,
|
|
838
|
-
timerKey: NonEmptyString,
|
|
839
|
-
npcId: Schema.optional(NonNegativeInt)
|
|
840
|
-
});
|
|
841
|
-
Schema.Struct({
|
|
842
|
-
version: Schema.Literal(2),
|
|
843
|
-
lootId: NonNegativeInt,
|
|
844
|
-
world: NonEmptyString,
|
|
845
|
-
guildIds: Schema.Array(NonEmptyString),
|
|
846
|
-
itemIds: Schema.Array(NonNegativeInt),
|
|
847
|
-
itemNames: Schema.Array(Schema.String),
|
|
848
|
-
npcs: Schema.Array(Schema.Struct({
|
|
849
|
-
type: NullableString,
|
|
850
|
-
lvl: NullableNumber
|
|
851
|
-
}))
|
|
852
|
-
});
|
|
853
|
-
Schema.Struct({
|
|
854
|
-
notificationJobId: NonEmptyString,
|
|
855
|
-
success: Schema.Boolean,
|
|
856
|
-
retryable: Schema.Boolean,
|
|
857
|
-
providerMessageId: Schema.optional(NullableString),
|
|
858
|
-
errorCode: Schema.optional(NullableString),
|
|
859
|
-
errorMessage: Schema.optional(NullableString),
|
|
860
|
-
deliveredAt: NonEmptyString
|
|
861
|
-
});
|
|
862
|
-
Schema.Struct({
|
|
863
|
-
guildId: NonEmptyString,
|
|
864
|
-
channelId: NonEmptyString,
|
|
865
|
-
syncState: DiscordGuildSyncState
|
|
866
|
-
});
|
|
867
|
-
/** Cumulative confirmed game presence. A gap starts a new segment; retries never extend it. */
|
|
868
|
-
const UserOnlineCheckpointV1 = Schema.Struct({
|
|
869
|
-
version: Schema.Literal(1),
|
|
870
|
-
type: Schema.Literal("checkpoint"),
|
|
871
|
-
userId: NonEmptyString,
|
|
872
|
-
sessionId: NonEmptyString,
|
|
873
|
-
segmentId: NonEmptyString,
|
|
874
|
-
world: Schema.optional(NonEmptyString),
|
|
875
|
-
startedAt: DateTimeWithOffsetString,
|
|
876
|
-
endedAt: DateTimeWithOffsetString,
|
|
877
|
-
observedAt: DateTimeWithOffsetString
|
|
878
|
-
}).check(Schema.makeFilter((event) => Date.parse(event.startedAt) <= Date.parse(event.endedAt) && Date.parse(event.endedAt) <= Date.parse(event.observedAt), { expected: "ordered confirmed interval timestamps" }));
|
|
879
|
-
/** Published only after the durable publisher has drained its pending checkpoints. */
|
|
880
|
-
const UserOnlineHealthV1 = Schema.Struct({
|
|
881
|
-
version: Schema.Literal(1),
|
|
882
|
-
type: Schema.Literal("collector"),
|
|
883
|
-
observedAt: DateTimeWithOffsetString,
|
|
884
|
-
status: Schema.Literals(["healthy", "degraded"])
|
|
885
|
-
});
|
|
886
|
-
Schema.Union([UserOnlineCheckpointV1, UserOnlineHealthV1]);
|
|
887
|
-
RabbitRoutingKey.GAME_CHARACTER_OFFLINE, RabbitRoutingKey.GUILDS_KILLS_ACCEPTED_V1, RabbitRoutingKey.USERS_ONLINE_CHECKPOINT_V1, RabbitRoutingKey.ACTIVITY_LOG_CREATE, RabbitRoutingKey.DISCORD_GUILD_CHANNEL_DELETED, RabbitRoutingKey.DISCORD_GUILD_CHANNEL_UPSERTED, RabbitRoutingKey.DISCORD_GUILD_CHANNELS_SYNC_FAILED, RabbitRoutingKey.DISCORD_GUILD_CHANNELS_SYNCED, RabbitRoutingKey.DISCORD_GUILD_SYNC_STATE_UPDATED, RabbitRoutingKey.EVENT_HERO_KILLED, RabbitRoutingKey.EVENT_MAP_STATUS_UPDATE, RabbitRoutingKey.EVENT_RANKING_UPDATE, RabbitRoutingKey.EVENT_RESPAWN_WINDOW_CLOSED, RabbitRoutingKey.EVENT_RESPAWN_WINDOW_OPENED, RabbitRoutingKey.GUILDS_LOOTS_CREATE, RabbitRoutingKey.GUILDS_LOOTS_SHARE_UPDATE, RabbitRoutingKey.GUILDS_CREATE, RabbitRoutingKey.GUILDS_DELETE, RabbitRoutingKey.GUILDS_UPDATE, RabbitRoutingKey.GUILDS_CREATE_ROLE, RabbitRoutingKey.GUILDS_DELETE_ROLE, RabbitRoutingKey.GUILDS_UPDATE_ROLE, RabbitRoutingKey.GUILDS_MEMBERS_ADD, RabbitRoutingKey.GUILDS_MEMBERS_ADD_ROLE, RabbitRoutingKey.GUILDS_MEMBERS_REMOVE, RabbitRoutingKey.GUILDS_MEMBERS_REMOVE_ROLE, RabbitRoutingKey.GUILDS_MEMBERS_UPDATE, RabbitRoutingKey.GUILDS_TIMERS_UPDATE, RabbitRoutingKey.GUILDS_TIMERS_DELETE, RabbitRoutingKey.GUILDS_RESERVATIONS_CREATE, RabbitRoutingKey.GUILDS_RESERVATIONS_DELETE, RabbitRoutingKey.GUILDS_SEND_MESSAGE, RabbitRoutingKey.GUILDS_UPDATE_MESSAGE, RabbitRoutingKey.GUILDS_DELETE_MESSAGE, RabbitRoutingKey.GUILDS_CLEAR_MESSAGES, RabbitRoutingKey.GUILDS_NOTIFICATIONS_SEND, RabbitRoutingKey.GUILDS_NOTIFICATIONS_VOLUNTEER, RabbitRoutingKey.GUILDS_PARTY_GATHERING, RabbitRoutingKey.GUILDS_PARTY_GATHERING_CANCEL, RabbitRoutingKey.GUILDS_MEMBERS_REFRESH_JOB_UPDATE, RabbitRoutingKey.GUILDS_RESERVATIONS_CHANGED_V2, RabbitRoutingKey.NOTIFICATIONS_DELIVERY_RESULT, RabbitRoutingKey.NOTIFICATIONS_LOOT_CREATED, RabbitRoutingKey.NOTIFICATIONS_TIMER_DELETED, RabbitRoutingKey.NOTIFICATIONS_TIMER_UPDATED, RabbitRoutingKey.PRESENCE_CHECK_REQUEST, RabbitRoutingKey.PRESENCE_COVERAGE_CHECK, RabbitRoutingKey.USERS_PARTY_READY_ROOM_UPDATED;
|
|
888
|
-
const REALTIME_SUBPROTOCOL = "lootlog.realtime.v1";
|
|
908
|
+
const { feedEntry: _feedEntry, ...lootCreatedFields } = GuildLootCreatedMessageV2.fields;
|
|
909
|
+
const LootSnapshotEvent = Schema.Struct(lootCreatedFields);
|
|
889
910
|
const REALTIME_JSON_SUBPROTOCOL = "lootlog.realtime.json.v1";
|
|
911
|
+
const REALTIME_CLIENT_CLOSE_CODES = {
|
|
912
|
+
heartbeatTimeout: 4001,
|
|
913
|
+
heartbeatRejected: 4002,
|
|
914
|
+
heartbeatUnavailable: 4003,
|
|
915
|
+
malformedFrame: 4007,
|
|
916
|
+
sessionJoinFailed: 4008
|
|
917
|
+
};
|
|
918
|
+
const REALTIME_SERVER_CLOSE_CODES = {
|
|
919
|
+
restarting: 1012,
|
|
920
|
+
tryAgainLater: 1013
|
|
921
|
+
};
|
|
922
|
+
const REALTIME_SERVER_CLOSE_RECONNECT_WINDOW_MS = 3e4;
|
|
890
923
|
const PRESENCE_HEARTBEAT_INTERVAL_MS = 25e3;
|
|
891
|
-
const
|
|
924
|
+
const PRESENCE_EXPIRY_MS = 6e4;
|
|
925
|
+
const RequestId = Schema.NonEmptyString;
|
|
892
926
|
const Revision = NonNegativeInt;
|
|
893
927
|
const Timestamp = NonNegativeInt;
|
|
894
928
|
const RealtimeLogicalTopic = Schema.Literals([
|
|
@@ -903,13 +937,15 @@ const RealtimeLogicalTopic = Schema.Literals([
|
|
|
903
937
|
"event.coordination",
|
|
904
938
|
"map.air-tags",
|
|
905
939
|
"map.pings",
|
|
906
|
-
"party.ready-room"
|
|
940
|
+
"party.ready-room",
|
|
941
|
+
"global.chat",
|
|
942
|
+
"global.chat.world"
|
|
907
943
|
]);
|
|
908
944
|
const SubscriptionScope = Schema.Struct({
|
|
909
945
|
topic: RealtimeLogicalTopic,
|
|
910
|
-
organizationId: Schema.optional(NonEmptyString),
|
|
911
|
-
eventId: Schema.optional(NonEmptyString),
|
|
912
|
-
world: Schema.optional(NonEmptyString),
|
|
946
|
+
organizationId: Schema.optional(Schema.NonEmptyString),
|
|
947
|
+
eventId: Schema.optional(Schema.NonEmptyString),
|
|
948
|
+
world: Schema.optional(Schema.NonEmptyString),
|
|
913
949
|
mapId: Schema.optional(NonNegativeInt)
|
|
914
950
|
});
|
|
915
951
|
const PresencePlatform = Schema.Literals(["game", "web-app"]);
|
|
@@ -921,26 +957,26 @@ const PresenceClan = Schema.Struct({
|
|
|
921
957
|
rank: Schema.optional(Schema.Int)
|
|
922
958
|
});
|
|
923
959
|
const PresenceCharacter = Schema.Struct({
|
|
924
|
-
world: NonEmptyString,
|
|
925
|
-
name: NonEmptyString,
|
|
960
|
+
world: Schema.NonEmptyString,
|
|
961
|
+
name: Schema.NonEmptyString,
|
|
926
962
|
lvl: NonNegativeInt,
|
|
927
963
|
icon: Schema.String,
|
|
928
|
-
characterId: NonEmptyString,
|
|
929
|
-
accountId: NonEmptyString,
|
|
964
|
+
characterId: Schema.NonEmptyString,
|
|
965
|
+
accountId: Schema.NonEmptyString,
|
|
930
966
|
prof: Schema.String,
|
|
931
967
|
clan: Schema.optional(PresenceClan)
|
|
932
968
|
});
|
|
933
969
|
const PrecisePresenceLocation = Schema.Struct({
|
|
934
970
|
mapId: Schema.optional(NonNegativeInt),
|
|
935
|
-
map: NonEmptyString,
|
|
971
|
+
map: Schema.NonEmptyString,
|
|
936
972
|
x: Schema.optional(NonNegativeInt),
|
|
937
973
|
y: Schema.optional(NonNegativeInt)
|
|
938
974
|
});
|
|
939
975
|
const BasicPresence = Schema.Struct({
|
|
940
|
-
userId: NonEmptyString,
|
|
941
|
-
discordId: Schema.optional(NonEmptyString),
|
|
942
|
-
sessionId: NonEmptyString,
|
|
943
|
-
organizationIds: Schema.Array(NonEmptyString),
|
|
976
|
+
userId: Schema.NonEmptyString,
|
|
977
|
+
discordId: Schema.optional(Schema.NonEmptyString),
|
|
978
|
+
sessionId: Schema.NonEmptyString,
|
|
979
|
+
organizationIds: Schema.Array(Schema.NonEmptyString),
|
|
944
980
|
platform: PresencePlatform,
|
|
945
981
|
status: PresenceStatus,
|
|
946
982
|
confidence: PresenceConfidence,
|
|
@@ -953,29 +989,29 @@ const PresenceWithLocation = Schema.Struct({
|
|
|
953
989
|
location: PrecisePresenceLocation
|
|
954
990
|
});
|
|
955
991
|
const PublishedPresence = Schema.Struct({
|
|
956
|
-
organizationIds: Schema.Array(NonEmptyString),
|
|
992
|
+
organizationIds: Schema.Array(Schema.NonEmptyString),
|
|
957
993
|
isAfk: Schema.optional(Schema.Boolean),
|
|
958
994
|
character: Schema.optional(PresenceCharacter),
|
|
959
995
|
location: Schema.optional(PrecisePresenceLocation),
|
|
960
996
|
clientObservedAt: Schema.optional(Timestamp)
|
|
961
997
|
});
|
|
962
998
|
const PresenceSnapshot = Schema.Struct({
|
|
963
|
-
organizationId: NonEmptyString,
|
|
964
|
-
world: Schema.optional(NonEmptyString),
|
|
999
|
+
organizationId: Schema.NonEmptyString,
|
|
1000
|
+
world: Schema.optional(Schema.NonEmptyString),
|
|
965
1001
|
revision: Revision,
|
|
966
1002
|
presences: Schema.Array(Schema.Union([PresenceWithLocation, BasicPresence]))
|
|
967
1003
|
});
|
|
968
1004
|
const PresenceDelta = Schema.Struct({
|
|
969
|
-
organizationId: NonEmptyString,
|
|
1005
|
+
organizationId: Schema.NonEmptyString,
|
|
970
1006
|
revision: Revision,
|
|
971
1007
|
changes: Schema.Array(Schema.Union([Schema.Struct({
|
|
972
1008
|
action: Schema.Literal("upsert"),
|
|
973
1009
|
presence: Schema.Union([PresenceWithLocation, BasicPresence])
|
|
974
1010
|
}), Schema.Struct({
|
|
975
1011
|
action: Schema.Literal("remove"),
|
|
976
|
-
userId: NonEmptyString,
|
|
977
|
-
discordId: Schema.optional(NonEmptyString),
|
|
978
|
-
sessionId: NonEmptyString
|
|
1012
|
+
userId: Schema.NonEmptyString,
|
|
1013
|
+
discordId: Schema.optional(Schema.NonEmptyString),
|
|
1014
|
+
sessionId: Schema.NonEmptyString
|
|
979
1015
|
})]))
|
|
980
1016
|
});
|
|
981
1017
|
const command = (type, data) => Schema.Struct({
|
|
@@ -985,25 +1021,40 @@ const command = (type, data) => Schema.Struct({
|
|
|
985
1021
|
data
|
|
986
1022
|
});
|
|
987
1023
|
const SessionJoinCommand = command("session.join", Schema.Struct({
|
|
988
|
-
world: Schema.optional(NonEmptyString),
|
|
1024
|
+
world: Schema.optional(Schema.NonEmptyString),
|
|
989
1025
|
character: Schema.optional(PresenceCharacter),
|
|
990
1026
|
margonemAccountProof: Schema.optional(Schema.Unknown)
|
|
991
1027
|
}));
|
|
992
|
-
const HeartbeatCommand = command("presence.heartbeat", Schema.Struct({ sessionId: NonEmptyString }));
|
|
1028
|
+
const HeartbeatCommand = command("presence.heartbeat", Schema.Struct({ sessionId: Schema.NonEmptyString }));
|
|
993
1029
|
const PresencePublishCommand = command("presence.publish", PublishedPresence);
|
|
1030
|
+
const PingCommand = command("connection.ping", Schema.Struct({}));
|
|
994
1031
|
const PresenceFetchCommand = command("presence.fetch", Schema.Struct({
|
|
995
|
-
organizationId: NonEmptyString,
|
|
996
|
-
world: Schema.optional(NonEmptyString)
|
|
1032
|
+
organizationId: Schema.NonEmptyString,
|
|
1033
|
+
world: Schema.optional(Schema.NonEmptyString),
|
|
1034
|
+
delivery: Schema.optional(Schema.Literal("response"))
|
|
997
1035
|
}));
|
|
998
1036
|
const SubscribeCommand = command("subscription.subscribe", SubscriptionScope);
|
|
999
1037
|
const UnsubscribeCommand = command("subscription.unsubscribe", SubscriptionScope);
|
|
1000
1038
|
const MapPingCommand = command("map-ping.send", MapPingSendPayloadSchema);
|
|
1039
|
+
const BattlePingCommand = command("battle-ping.send", BattlePingSendPayloadSchema);
|
|
1040
|
+
const LootSplitCommand = command("loot-split.send", LootSplitSendPayloadSchema);
|
|
1041
|
+
const LootSplitAutoAcceptCommand = command("loot-split.auto-accept", LootSplitAutoAcceptPayloadSchema);
|
|
1001
1042
|
const AirTagSubscriptionCommand = command("air-tag.subscription", Schema.Struct({
|
|
1002
1043
|
requestId: RequestId,
|
|
1003
1044
|
enabled: Schema.Boolean,
|
|
1004
1045
|
expectedMapId: Schema.optional(NonNegativeInt)
|
|
1005
1046
|
}));
|
|
1006
1047
|
const AirTagObservationCommand = command("air-tag.observation", AirTagObservationBatchSchema);
|
|
1048
|
+
const AirTagMapThreatsFetchCommand = command("air-tag.map-threats.fetch", Schema.Struct({
|
|
1049
|
+
organizationId: Schema.NonEmptyString,
|
|
1050
|
+
world: Schema.NonEmptyString
|
|
1051
|
+
}));
|
|
1052
|
+
const AirTagMapThreatsFetchResponse = Schema.Struct({ threats: Schema.Array(AirTagMapThreatEventSchema) });
|
|
1053
|
+
const NpcPresenceReportCommand = command("npc-presence.report", NpcPresenceReportSchema);
|
|
1054
|
+
const NpcPresenceFetchCommand = command("npc-presence.fetch", Schema.Struct({
|
|
1055
|
+
organizationId: Schema.NonEmptyString,
|
|
1056
|
+
world: Schema.NonEmptyString
|
|
1057
|
+
}));
|
|
1007
1058
|
const AirTagRejectCode = Schema.Literals([
|
|
1008
1059
|
"forbidden",
|
|
1009
1060
|
"invalid-context",
|
|
@@ -1032,17 +1083,24 @@ const AirTagObservationAck = Schema.Union([Schema.Struct({
|
|
|
1032
1083
|
const ClientCommand = Schema.Union([
|
|
1033
1084
|
SessionJoinCommand,
|
|
1034
1085
|
HeartbeatCommand,
|
|
1086
|
+
PingCommand,
|
|
1035
1087
|
PresencePublishCommand,
|
|
1036
1088
|
PresenceFetchCommand,
|
|
1037
1089
|
SubscribeCommand,
|
|
1038
1090
|
UnsubscribeCommand,
|
|
1039
1091
|
MapPingCommand,
|
|
1092
|
+
BattlePingCommand,
|
|
1093
|
+
LootSplitCommand,
|
|
1094
|
+
LootSplitAutoAcceptCommand,
|
|
1040
1095
|
AirTagSubscriptionCommand,
|
|
1041
|
-
AirTagObservationCommand
|
|
1096
|
+
AirTagObservationCommand,
|
|
1097
|
+
AirTagMapThreatsFetchCommand,
|
|
1098
|
+
NpcPresenceReportCommand,
|
|
1099
|
+
NpcPresenceFetchCommand
|
|
1042
1100
|
]);
|
|
1043
1101
|
const RealtimeError = Schema.Struct({
|
|
1044
|
-
code: NonEmptyString,
|
|
1045
|
-
message: NonEmptyString,
|
|
1102
|
+
code: Schema.NonEmptyString,
|
|
1103
|
+
message: Schema.NonEmptyString,
|
|
1046
1104
|
retryable: Schema.Boolean,
|
|
1047
1105
|
retryAfterMs: Schema.optional(NonNegativeInt),
|
|
1048
1106
|
details: Schema.optional(Schema.Record(Schema.String, Schema.Unknown))
|
|
@@ -1065,19 +1123,21 @@ const serverEvent = (type, data) => Schema.Struct({
|
|
|
1065
1123
|
data
|
|
1066
1124
|
});
|
|
1067
1125
|
const OrganizationEvent = Schema.Struct({
|
|
1068
|
-
organizationId: NonEmptyString,
|
|
1126
|
+
organizationId: Schema.NonEmptyString,
|
|
1069
1127
|
payload: Schema.Unknown
|
|
1070
1128
|
});
|
|
1071
1129
|
const ServerEvent = Schema.Union([
|
|
1130
|
+
serverEvent("session.hello", Schema.Struct({ connectionId: Schema.NonEmptyString })),
|
|
1072
1131
|
serverEvent("session.joined", Schema.Struct({
|
|
1073
|
-
connectionId: NonEmptyString,
|
|
1074
|
-
organizationIds: Schema.Array(NonEmptyString),
|
|
1132
|
+
connectionId: Schema.NonEmptyString,
|
|
1133
|
+
organizationIds: Schema.Array(Schema.NonEmptyString),
|
|
1075
1134
|
subscriptionScopes: Schema.Array(SubscriptionScope),
|
|
1076
|
-
accessPolicy: Schema.optional(AccessPolicySnapshot)
|
|
1135
|
+
accessPolicy: Schema.optional(AccessPolicySnapshot),
|
|
1136
|
+
capabilities: Schema.optional(Schema.Array(Schema.NonEmptyString))
|
|
1077
1137
|
})),
|
|
1078
1138
|
serverEvent("permissions.updated", Schema.Struct({
|
|
1079
1139
|
changes: Schema.optional(Schema.Array(AccessPolicyChange)),
|
|
1080
|
-
organizationIds: Schema.Array(NonEmptyString),
|
|
1140
|
+
organizationIds: Schema.Array(Schema.NonEmptyString),
|
|
1081
1141
|
subscriptionScopes: Schema.Array(SubscriptionScope),
|
|
1082
1142
|
accessPolicy: Schema.optional(AccessPolicySnapshot)
|
|
1083
1143
|
})),
|
|
@@ -1087,12 +1147,24 @@ const ServerEvent = Schema.Union([
|
|
|
1087
1147
|
serverEvent("chat.updated", OrganizationEvent),
|
|
1088
1148
|
serverEvent("chat.deleted", OrganizationEvent),
|
|
1089
1149
|
serverEvent("chat.cleared", OrganizationEvent),
|
|
1150
|
+
serverEvent("global-chat.created", GlobalChatMessageSchema),
|
|
1151
|
+
serverEvent("global-chat.deleted", Schema.Struct({
|
|
1152
|
+
world: Schema.optionalKey(GlobalChatWorld),
|
|
1153
|
+
id: Schema.NonEmptyString
|
|
1154
|
+
})),
|
|
1155
|
+
serverEvent("global-chat.pinned", Schema.Struct({
|
|
1156
|
+
world: Schema.optionalKey(GlobalChatWorld),
|
|
1157
|
+
message: Schema.NullOr(GlobalChatMessageSchema)
|
|
1158
|
+
})),
|
|
1159
|
+
serverEvent("global-chat.stats", GlobalChatStatsSchema),
|
|
1090
1160
|
serverEvent("feed.entry", UserFeedItem),
|
|
1091
|
-
serverEvent("kills.changed", Schema.Struct({ guildId: NonEmptyString })),
|
|
1161
|
+
serverEvent("kills.changed", Schema.Struct({ guildId: Schema.NonEmptyString })),
|
|
1092
1162
|
serverEvent("loot.created", GuildLootCreatedEventV2),
|
|
1093
1163
|
serverEvent("loot.share-updated", GuildLootShareUpdatedEventV2),
|
|
1164
|
+
serverEvent("loot.snapshot", LootSnapshotEvent),
|
|
1094
1165
|
serverEvent("timer.created", OrganizationEvent),
|
|
1095
1166
|
serverEvent("timer.deleted", OrganizationEvent),
|
|
1167
|
+
serverEvent("hero-sighting.updated", OrganizationEvent),
|
|
1096
1168
|
serverEvent("reservation.created", OrganizationEvent),
|
|
1097
1169
|
serverEvent("reservation.deleted", OrganizationEvent),
|
|
1098
1170
|
serverEvent("reservation.changed", ReservationChangedEventV2),
|
|
@@ -1100,19 +1172,29 @@ const ServerEvent = Schema.Union([
|
|
|
1100
1172
|
serverEvent("member-refresh.updated", OrganizationEvent),
|
|
1101
1173
|
serverEvent("party-gathering.updated", OrganizationEvent),
|
|
1102
1174
|
serverEvent("party-gathering.cancelled", OrganizationEvent),
|
|
1175
|
+
serverEvent("party-gathering.state-updated", Schema.Struct({
|
|
1176
|
+
organizationId: Schema.NonEmptyString,
|
|
1177
|
+
payload: PartyGatheringClientUpdateSchema
|
|
1178
|
+
})),
|
|
1103
1179
|
serverEvent("party-ready-room.updated", OrganizationEvent),
|
|
1104
1180
|
serverEvent("map-ping.received", MapPingEventSchema),
|
|
1181
|
+
serverEvent("battle-ping.received", BattlePingEventSchema),
|
|
1182
|
+
serverEvent("loot-split.received", LootSplitEventSchema),
|
|
1183
|
+
serverEvent("loot-split.auto-accepted", LootSplitAutoAcceptedEventSchema),
|
|
1105
1184
|
serverEvent("air-tag.updated", AirTagUpdateEventSchema),
|
|
1185
|
+
serverEvent("air-tag.scope-updated", AirTagScopeUpdateEventSchema),
|
|
1186
|
+
serverEvent("air-tag.map-threat-updated", AirTagMapThreatEventSchema),
|
|
1187
|
+
serverEvent("npc-presence.updated", NpcPresenceEventSchema),
|
|
1106
1188
|
serverEvent("event.map-status-updated", OrganizationEvent),
|
|
1107
1189
|
serverEvent("event.hero-killed", OrganizationEvent),
|
|
1108
1190
|
serverEvent("event.ranking-updated", OrganizationEvent),
|
|
1109
1191
|
serverEvent("event.respawn-window-opened", OrganizationEvent),
|
|
1110
1192
|
serverEvent("event.respawn-window-closed", OrganizationEvent),
|
|
1111
1193
|
serverEvent("notification.volunteer", Schema.Struct({
|
|
1112
|
-
notificationId: NonEmptyString,
|
|
1194
|
+
notificationId: Schema.NonEmptyString,
|
|
1113
1195
|
volunteer: Schema.StructWithRest(Schema.Struct({
|
|
1114
|
-
discordId: NonEmptyString,
|
|
1115
|
-
world: NonEmptyString
|
|
1196
|
+
discordId: Schema.NonEmptyString,
|
|
1197
|
+
world: Schema.NonEmptyString
|
|
1116
1198
|
}), [Schema.Record(Schema.String, Schema.Unknown)])
|
|
1117
1199
|
}))
|
|
1118
1200
|
]);
|
|
@@ -1122,9 +1204,8 @@ const RealtimeFrame = Schema.Union([
|
|
|
1122
1204
|
ServerEvent
|
|
1123
1205
|
]);
|
|
1124
1206
|
Schema.decodeUnknownSync(ClientCommand);
|
|
1125
|
-
Schema.decodeUnknownSync(Response);
|
|
1126
1207
|
Schema.decodeUnknownSync(ServerEvent);
|
|
1127
|
-
|
|
1208
|
+
Schema.decodeUnknownSync(RealtimeFrame);
|
|
1128
1209
|
const serverEventTypes = new Set(ServerEvent.members.map((member) => member.fields.type.literal));
|
|
1129
1210
|
const isServerEventFrame = (frame) => "type" in frame && serverEventTypes.has(frame.type);
|
|
1130
1211
|
//#endregion
|
|
@@ -1140,7 +1221,7 @@ const tryEncodeFrame = (frame) => Result.try({
|
|
|
1140
1221
|
const validated = Schema.encodeUnknownSync(RealtimeFrame)(frame);
|
|
1141
1222
|
return {
|
|
1142
1223
|
frame: validated,
|
|
1143
|
-
|
|
1224
|
+
text: JSON.stringify(validated)
|
|
1144
1225
|
};
|
|
1145
1226
|
},
|
|
1146
1227
|
catch: (error) => new RealtimeCodecError({
|
|
@@ -1149,26 +1230,62 @@ const tryEncodeFrame = (frame) => Result.try({
|
|
|
1149
1230
|
cause: error
|
|
1150
1231
|
})
|
|
1151
1232
|
});
|
|
1152
|
-
const tryEncodeRealtimeFrame = (frame) => Result.map(tryEncodeFrame(frame), ({
|
|
1233
|
+
const tryEncodeRealtimeFrame = (frame) => Result.map(tryEncodeFrame(frame), ({ text }) => text);
|
|
1153
1234
|
const encodeRealtimeFrame = (frame) => Result.getOrThrow(tryEncodeRealtimeFrame(frame));
|
|
1154
|
-
const
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1235
|
+
const rejectProtoKey = (key, value) => {
|
|
1236
|
+
if (key === "__proto__") throw new Error("The key __proto__ is not allowed");
|
|
1237
|
+
return value;
|
|
1238
|
+
};
|
|
1239
|
+
/**
|
|
1240
|
+
* Whether a JSON text cannot have a `__proto__` key. A key can spell it only
|
|
1241
|
+
* literally or through a `\u` escape.
|
|
1242
|
+
*/
|
|
1243
|
+
const isWithoutProtoKey = (text) => !text.includes("__proto__") && !text.includes("\\u");
|
|
1244
|
+
/**
|
|
1245
|
+
* JSON-parse a realtime text, rejecting any `__proto__` key. The reviver makes
|
|
1246
|
+
* parsing several times slower, so it runs only when the text may need it.
|
|
1247
|
+
*/
|
|
1248
|
+
const parseRealtimeJson = (text) => isWithoutProtoKey(text) ? JSON.parse(text) : JSON.parse(text, rejectProtoKey);
|
|
1249
|
+
const tryDecode = (input) => Result.try({
|
|
1250
|
+
try: () => Schema.decodeUnknownSync(RealtimeFrame)(input()),
|
|
1159
1251
|
catch: (error) => new RealtimeCodecError({
|
|
1160
1252
|
operation: "decode",
|
|
1161
1253
|
message: getErrorMessage(error),
|
|
1162
1254
|
cause: error
|
|
1163
1255
|
})
|
|
1164
1256
|
});
|
|
1257
|
+
const tryDecodeRealtimeFrame = (text) => tryDecode(() => parseRealtimeJson(text));
|
|
1258
|
+
const decodeRealtimeFrame = (text) => Result.getOrThrow(tryDecodeRealtimeFrame(text));
|
|
1165
1259
|
Schema.decodeUnknownSync(PresenceSnapshot);
|
|
1166
1260
|
Schema.is(MapPingAckSchema);
|
|
1261
|
+
Schema.is(LootSplitAckSchema);
|
|
1167
1262
|
Schema.is(AirTagSubscriptionAck);
|
|
1168
1263
|
Schema.is(AirTagObservationAck);
|
|
1264
|
+
Schema.is(AirTagMapThreatsFetchResponse);
|
|
1265
|
+
Schema.is(NpcPresenceReportAckSchema);
|
|
1266
|
+
Schema.is(NpcPresenceSnapshotSchema);
|
|
1267
|
+
const hasRealtimeCapabilities = Schema.is(Schema.Struct({ capabilities: Schema.Array(Schema.NonEmptyString) }));
|
|
1169
1268
|
Schema.is(Schema.Struct({ presences: Schema.optional(PresenceSnapshot.fields.presences) }));
|
|
1170
1269
|
//#endregion
|
|
1270
|
+
//#region ../client/src/realtime/notify-listeners.ts
|
|
1271
|
+
const reportListenerError = ({ source, error }) => {
|
|
1272
|
+
try {
|
|
1273
|
+
console.warn(`[Realtime] ${source} listener failed:`, error);
|
|
1274
|
+
} catch {}
|
|
1275
|
+
};
|
|
1276
|
+
const notifyListeners = (listeners, notify, source) => {
|
|
1277
|
+
for (const listener of listeners) try {
|
|
1278
|
+
notify(listener);
|
|
1279
|
+
} catch (error) {
|
|
1280
|
+
reportListenerError({
|
|
1281
|
+
source,
|
|
1282
|
+
error
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
//#endregion
|
|
1171
1287
|
//#region ../client/src/realtime/realtime-client.ts
|
|
1288
|
+
var RealtimeRequestTimeout = class extends Error {};
|
|
1172
1289
|
var RealtimeRequestError = class extends Error {
|
|
1173
1290
|
code;
|
|
1174
1291
|
retryable;
|
|
@@ -1193,6 +1310,7 @@ const normalizeUrl = (baseUrl, path) => {
|
|
|
1193
1310
|
};
|
|
1194
1311
|
const nativeWebSocketFactory = (url, protocols) => new WebSocket(url, protocols);
|
|
1195
1312
|
const WEBSOCKET_OPEN = 1;
|
|
1313
|
+
const MIN_HEARTBEAT_RETRY_BUDGET_MS = 2e3;
|
|
1196
1314
|
const scopeKey = (scope) => JSON.stringify([
|
|
1197
1315
|
scope.topic,
|
|
1198
1316
|
scope.organizationId ?? null,
|
|
@@ -1200,12 +1318,7 @@ const scopeKey = (scope) => JSON.stringify([
|
|
|
1200
1318
|
scope.world ?? null,
|
|
1201
1319
|
scope.mapId ?? null
|
|
1202
1320
|
]);
|
|
1203
|
-
const
|
|
1204
|
-
if (data instanceof Uint8Array) return data;
|
|
1205
|
-
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
1206
|
-
if (data instanceof Blob) return new Uint8Array(await data.arrayBuffer());
|
|
1207
|
-
throw new Error("Gateway returned a non-binary realtime frame");
|
|
1208
|
-
};
|
|
1321
|
+
const SERVER_CLOSE_CODES = new Set(Object.values(REALTIME_SERVER_CLOSE_CODES));
|
|
1209
1322
|
const getPresenceSessionId = (data) => {
|
|
1210
1323
|
if (!data || typeof data !== "object" || !("sessionId" in data)) return null;
|
|
1211
1324
|
return typeof data.sessionId === "string" ? data.sessionId : null;
|
|
@@ -1218,30 +1331,34 @@ var RealtimeClient = class {
|
|
|
1218
1331
|
reconnectMaxDelayMs;
|
|
1219
1332
|
random;
|
|
1220
1333
|
webSocketFactory;
|
|
1221
|
-
frameEncoding;
|
|
1222
1334
|
eventListeners = /* @__PURE__ */ new Set();
|
|
1223
1335
|
stateListeners = /* @__PURE__ */ new Set();
|
|
1336
|
+
heartbeatLatencyMs = null;
|
|
1337
|
+
heartbeatLatencyListeners = /* @__PURE__ */ new Set();
|
|
1224
1338
|
pending = /* @__PURE__ */ new Map();
|
|
1225
1339
|
subscriptions = /* @__PURE__ */ new Map();
|
|
1226
1340
|
socket = null;
|
|
1227
1341
|
stateValue = "disconnected";
|
|
1228
1342
|
joinData = null;
|
|
1343
|
+
joinAttempt = null;
|
|
1229
1344
|
reconnectAttempt = 0;
|
|
1345
|
+
reconnectNotBeforeMs = 0;
|
|
1230
1346
|
reconnectTimeout = null;
|
|
1231
1347
|
heartbeatTimeout = null;
|
|
1232
1348
|
presenceSessionId = null;
|
|
1349
|
+
presenceRefreshedAt = 0;
|
|
1350
|
+
pingSupported = false;
|
|
1351
|
+
latencyProbeInFlight = false;
|
|
1233
1352
|
manuallyClosed = false;
|
|
1234
|
-
messageChain = Promise.resolve();
|
|
1235
1353
|
rejoinHandler = null;
|
|
1236
1354
|
constructor(options) {
|
|
1237
1355
|
this.url = normalizeUrl(options.url, options.path ?? "/ws");
|
|
1238
|
-
this.protocols =
|
|
1356
|
+
this.protocols = [.../* @__PURE__ */ new Set([REALTIME_JSON_SUBPROTOCOL, ...options.protocols ?? []])];
|
|
1239
1357
|
this.requestTimeoutMs = options.requestTimeoutMs ?? 2e4;
|
|
1240
1358
|
this.reconnectBaseDelayMs = options.reconnectBaseDelayMs ?? 1e3;
|
|
1241
1359
|
this.reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? 3e4;
|
|
1242
1360
|
this.random = options.random ?? Math.random;
|
|
1243
1361
|
this.webSocketFactory = options.webSocketFactory ?? nativeWebSocketFactory;
|
|
1244
|
-
this.frameEncoding = options.frameEncoding ?? "messagepack";
|
|
1245
1362
|
}
|
|
1246
1363
|
get state() {
|
|
1247
1364
|
return this.stateValue;
|
|
@@ -1259,6 +1376,7 @@ var RealtimeClient = class {
|
|
|
1259
1376
|
this.manuallyClosed = true;
|
|
1260
1377
|
this.clearReconnect();
|
|
1261
1378
|
this.clearHeartbeat();
|
|
1379
|
+
this.pingSupported = false;
|
|
1262
1380
|
this.rejectPending(/* @__PURE__ */ new Error("Realtime client disconnected"));
|
|
1263
1381
|
const activeSocket = this.socket;
|
|
1264
1382
|
this.socket = null;
|
|
@@ -1274,6 +1392,30 @@ var RealtimeClient = class {
|
|
|
1274
1392
|
listener(this.stateValue);
|
|
1275
1393
|
return () => this.stateListeners.delete(listener);
|
|
1276
1394
|
}
|
|
1395
|
+
subscribeHeartbeatLatency(listener) {
|
|
1396
|
+
this.heartbeatLatencyListeners.add(listener);
|
|
1397
|
+
listener(this.heartbeatLatencyMs);
|
|
1398
|
+
return () => this.heartbeatLatencyListeners.delete(listener);
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
* Measures the round trip now instead of at the next heartbeat. A gateway
|
|
1402
|
+
* that does not advertise `connection.ping` keeps the heartbeat measurement.
|
|
1403
|
+
*/
|
|
1404
|
+
probeLatency() {
|
|
1405
|
+
if (!this.pingSupported || this.stateValue !== "ready" || this.latencyProbeInFlight) return;
|
|
1406
|
+
const socket = this.socket;
|
|
1407
|
+
const startedAt = performance.now();
|
|
1408
|
+
this.latencyProbeInFlight = true;
|
|
1409
|
+
this.request("connection.ping", {}).then(() => {
|
|
1410
|
+
if (this.socket === socket) this.setHeartbeatLatency(Math.round(performance.now() - startedAt));
|
|
1411
|
+
}).catch(() => {}).finally(() => {
|
|
1412
|
+
this.latencyProbeInFlight = false;
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
setHeartbeatLatency(latencyMs) {
|
|
1416
|
+
this.heartbeatLatencyMs = latencyMs;
|
|
1417
|
+
notifyListeners(this.heartbeatLatencyListeners, (listener) => listener(latencyMs), "heartbeat latency");
|
|
1418
|
+
}
|
|
1277
1419
|
setReconnectHandler(handler) {
|
|
1278
1420
|
this.rejoinHandler = handler;
|
|
1279
1421
|
}
|
|
@@ -1292,22 +1434,26 @@ var RealtimeClient = class {
|
|
|
1292
1434
|
return this.request("subscription.unsubscribe", scope);
|
|
1293
1435
|
}
|
|
1294
1436
|
request(type, data) {
|
|
1437
|
+
return this.requestWithTimeout(type, data, this.requestTimeoutMs);
|
|
1438
|
+
}
|
|
1439
|
+
requestWithTimeout(type, data, timeoutMs) {
|
|
1295
1440
|
const activeSocket = this.socket;
|
|
1296
1441
|
if (!activeSocket || activeSocket.readyState !== WEBSOCKET_OPEN) return Promise.reject(/* @__PURE__ */ new Error("Realtime connection is not open"));
|
|
1297
1442
|
const requestId = crypto.randomUUID();
|
|
1298
1443
|
return new Promise((resolve, reject) => {
|
|
1299
1444
|
const timeout = setTimeout(() => {
|
|
1300
1445
|
this.pending.delete(requestId);
|
|
1301
|
-
reject(
|
|
1302
|
-
},
|
|
1446
|
+
reject(new RealtimeRequestTimeout(`Realtime request timed out: ${type}`));
|
|
1447
|
+
}, timeoutMs);
|
|
1303
1448
|
this.pending.set(requestId, {
|
|
1304
1449
|
type,
|
|
1450
|
+
startedAt: performance.now(),
|
|
1305
1451
|
resolve,
|
|
1306
1452
|
reject,
|
|
1307
1453
|
timeout
|
|
1308
1454
|
});
|
|
1309
1455
|
try {
|
|
1310
|
-
activeSocket.send(
|
|
1456
|
+
activeSocket.send(encodeRealtimeFrame({
|
|
1311
1457
|
v: 1,
|
|
1312
1458
|
type,
|
|
1313
1459
|
requestId,
|
|
@@ -1323,7 +1469,7 @@ var RealtimeClient = class {
|
|
|
1323
1469
|
send(type, data) {
|
|
1324
1470
|
const activeSocket = this.socket;
|
|
1325
1471
|
if (!activeSocket || activeSocket.readyState !== WEBSOCKET_OPEN) return;
|
|
1326
|
-
activeSocket.send(
|
|
1472
|
+
activeSocket.send(encodeRealtimeFrame({
|
|
1327
1473
|
v: 1,
|
|
1328
1474
|
type,
|
|
1329
1475
|
data
|
|
@@ -1333,44 +1479,79 @@ var RealtimeClient = class {
|
|
|
1333
1479
|
this.setState(state);
|
|
1334
1480
|
if (this.manuallyClosed) return;
|
|
1335
1481
|
const socket = this.webSocketFactory(this.url, this.protocols);
|
|
1336
|
-
socket.binaryType = "arraybuffer";
|
|
1337
1482
|
this.socket = socket;
|
|
1338
1483
|
socket.addEventListener("open", () => {
|
|
1339
1484
|
if (this.socket !== socket) return;
|
|
1340
1485
|
this.setState("connected");
|
|
1341
|
-
if (this.joinData) (this.rejoinHandler ?? (() => this.performJoin().then(() => void 0)))().catch(() => socket.close(
|
|
1486
|
+
if (this.joinData && (this.rejoinHandler || this.stateValue === "connected")) (this.rejoinHandler ?? (() => this.performJoin().then(() => void 0)))().catch(() => socket.close(REALTIME_CLIENT_CLOSE_CODES.sessionJoinFailed, "session rejoin failed"));
|
|
1342
1487
|
});
|
|
1343
1488
|
socket.addEventListener("message", (event) => {
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1489
|
+
try {
|
|
1490
|
+
this.handleMessage(event.data, socket);
|
|
1491
|
+
} catch {
|
|
1492
|
+
socket.close(REALTIME_CLIENT_CLOSE_CODES.malformedFrame, "malformed realtime frame");
|
|
1493
|
+
}
|
|
1348
1494
|
});
|
|
1349
|
-
socket.addEventListener("close", () => {
|
|
1495
|
+
socket.addEventListener("close", (event) => {
|
|
1350
1496
|
if (this.socket !== socket) return;
|
|
1351
1497
|
this.socket = null;
|
|
1498
|
+
if (event.code !== void 0 && SERVER_CLOSE_CODES.has(event.code)) this.reconnectNotBeforeMs = Math.round(this.random() * REALTIME_SERVER_CLOSE_RECONNECT_WINDOW_MS);
|
|
1352
1499
|
this.clearHeartbeat();
|
|
1500
|
+
this.pingSupported = false;
|
|
1353
1501
|
this.rejectPending(/* @__PURE__ */ new Error("Realtime connection closed"));
|
|
1354
1502
|
this.setState("disconnected");
|
|
1355
1503
|
if (!this.manuallyClosed) this.scheduleReconnect();
|
|
1356
1504
|
});
|
|
1357
1505
|
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1506
|
+
performJoin(data = this.joinData) {
|
|
1507
|
+
const socket = this.socket;
|
|
1508
|
+
if (!data || !socket || !this.connected) return Promise.resolve(void 0);
|
|
1509
|
+
const key = JSON.stringify(data);
|
|
1510
|
+
const pending = this.joinAttempt;
|
|
1511
|
+
if (pending?.socket === socket) {
|
|
1512
|
+
if (pending.key === key) return pending.promise;
|
|
1513
|
+
return pending.promise.then(() => {
|
|
1514
|
+
if (this.socket !== socket || !this.connected) throw new Error("Realtime connection changed before queued join");
|
|
1515
|
+
return this.performJoin(data);
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
const promise = this.restoreSession(socket, this.request("session.join", data));
|
|
1519
|
+
this.joinAttempt = {
|
|
1520
|
+
socket,
|
|
1521
|
+
key,
|
|
1522
|
+
promise
|
|
1523
|
+
};
|
|
1360
1524
|
this.setState("joining");
|
|
1525
|
+
return promise;
|
|
1526
|
+
}
|
|
1527
|
+
async restoreSession(socket, joined) {
|
|
1361
1528
|
try {
|
|
1362
|
-
const result = await
|
|
1529
|
+
const result = await joined;
|
|
1530
|
+
if (this.socket !== socket) return result;
|
|
1531
|
+
this.pingSupported = hasRealtimeCapabilities(result) && result.capabilities.includes("connection.ping");
|
|
1363
1532
|
await Promise.all([...this.subscriptions.values()].map((scope) => this.request("subscription.subscribe", scope)));
|
|
1533
|
+
if (this.socket !== socket) return result;
|
|
1364
1534
|
this.reconnectAttempt = 0;
|
|
1365
1535
|
this.setState("ready");
|
|
1366
1536
|
return result;
|
|
1367
1537
|
} catch (error) {
|
|
1368
|
-
if (this.
|
|
1538
|
+
if (this.socket === socket) {
|
|
1539
|
+
if (error instanceof RealtimeRequestError && !error.retryable) {
|
|
1540
|
+
this.manuallyClosed = true;
|
|
1541
|
+
this.clearReconnect();
|
|
1542
|
+
}
|
|
1543
|
+
if (error instanceof RealtimeRequestError && error.retryable) this.reconnectNotBeforeMs = error.retryAfterMs ?? 0;
|
|
1544
|
+
socket.close(REALTIME_CLIENT_CLOSE_CODES.sessionJoinFailed, "session join failed");
|
|
1545
|
+
}
|
|
1369
1546
|
throw error;
|
|
1547
|
+
} finally {
|
|
1548
|
+
if (this.joinAttempt?.socket === socket) this.joinAttempt = null;
|
|
1370
1549
|
}
|
|
1371
1550
|
}
|
|
1372
|
-
|
|
1373
|
-
|
|
1551
|
+
handleMessage(data, socket) {
|
|
1552
|
+
if (this.socket !== socket) return;
|
|
1553
|
+
if (!Predicate.isString(data)) throw new Error("Gateway returned a non-text realtime frame");
|
|
1554
|
+
const frame = decodeRealtimeFrame(data);
|
|
1374
1555
|
if ("status" in frame) {
|
|
1375
1556
|
const pending = this.pending.get(frame.requestId);
|
|
1376
1557
|
if (!pending) return;
|
|
@@ -1379,6 +1560,7 @@ var RealtimeClient = class {
|
|
|
1379
1560
|
if (frame.status === "success") {
|
|
1380
1561
|
if (pending.type === "presence.publish") {
|
|
1381
1562
|
this.presenceSessionId = getPresenceSessionId(frame.data);
|
|
1563
|
+
this.presenceRefreshedAt = pending.startedAt;
|
|
1382
1564
|
this.scheduleHeartbeat();
|
|
1383
1565
|
}
|
|
1384
1566
|
pending.resolve(frame.data);
|
|
@@ -1386,35 +1568,64 @@ var RealtimeClient = class {
|
|
|
1386
1568
|
return;
|
|
1387
1569
|
}
|
|
1388
1570
|
if (!isServerEventFrame(frame)) return;
|
|
1389
|
-
|
|
1390
|
-
}
|
|
1391
|
-
encodeFrame(frame) {
|
|
1392
|
-
return this.frameEncoding === "json" ? JSON.stringify(frame) : encodeRealtimeFrame(frame);
|
|
1393
|
-
}
|
|
1394
|
-
async decodeFrame(data) {
|
|
1395
|
-
if (this.frameEncoding === "json") {
|
|
1396
|
-
if (typeof data !== "string") throw new Error("Gateway returned a non-text realtime frame");
|
|
1397
|
-
return decodeRealtimeFrame(JSON.parse(data));
|
|
1398
|
-
}
|
|
1399
|
-
const decoded = tryDecodeRealtimeFrame(await toBytes(data));
|
|
1400
|
-
if (Result.isFailure(decoded)) throw decoded.failure;
|
|
1401
|
-
return decoded.success;
|
|
1571
|
+
notifyListeners(this.eventListeners, (listener) => listener(frame), `server event ${frame.type}`);
|
|
1402
1572
|
}
|
|
1403
1573
|
scheduleReconnect() {
|
|
1404
1574
|
this.reconnectAttempt += 1;
|
|
1405
1575
|
const exponential = Math.min(this.reconnectMaxDelayMs, this.reconnectBaseDelayMs * 2 ** (this.reconnectAttempt - 1));
|
|
1406
|
-
const jittered = Math.round(exponential * (.5 + this.random()));
|
|
1576
|
+
const jittered = Math.max(Math.round(exponential * (.5 + this.random())), this.reconnectNotBeforeMs);
|
|
1577
|
+
this.reconnectNotBeforeMs = 0;
|
|
1407
1578
|
this.setState("reconnecting");
|
|
1408
1579
|
this.reconnectTimeout = setTimeout(() => this.open("reconnecting"), jittered);
|
|
1409
1580
|
}
|
|
1410
|
-
scheduleHeartbeat() {
|
|
1581
|
+
scheduleHeartbeat(delayMs = PRESENCE_HEARTBEAT_INTERVAL_MS, retryAttempt = 0) {
|
|
1411
1582
|
this.clearHeartbeat();
|
|
1412
|
-
if (!this.presenceSessionId || !this.connected)
|
|
1583
|
+
if (!this.presenceSessionId || !this.connected) {
|
|
1584
|
+
this.setHeartbeatLatency(null);
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1413
1587
|
this.heartbeatTimeout = setTimeout(() => {
|
|
1414
1588
|
const sessionId = this.presenceSessionId;
|
|
1415
1589
|
if (!sessionId) return;
|
|
1416
|
-
|
|
1417
|
-
|
|
1590
|
+
const startedAt = performance.now();
|
|
1591
|
+
const socket = this.socket;
|
|
1592
|
+
const refreshedAt = this.presenceRefreshedAt;
|
|
1593
|
+
const expiry = refreshedAt + PRESENCE_EXPIRY_MS;
|
|
1594
|
+
const remaining = expiry - startedAt;
|
|
1595
|
+
if (remaining <= 0 || retryAttempt > 0 && remaining < MIN_HEARTBEAT_RETRY_BUDGET_MS) {
|
|
1596
|
+
this.setHeartbeatLatency(null);
|
|
1597
|
+
socket?.close(REALTIME_CLIENT_CLOSE_CODES.heartbeatUnavailable, "heartbeat unavailable");
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
const isStaleHeartbeat = () => this.socket !== socket || this.presenceSessionId !== sessionId || this.presenceRefreshedAt !== refreshedAt;
|
|
1601
|
+
this.requestWithTimeout("presence.heartbeat", { sessionId }, Math.min(this.requestTimeoutMs, remaining)).then(() => {
|
|
1602
|
+
if (isStaleHeartbeat()) return;
|
|
1603
|
+
this.presenceRefreshedAt = startedAt;
|
|
1604
|
+
this.setHeartbeatLatency(Math.round(performance.now() - startedAt));
|
|
1605
|
+
this.scheduleHeartbeat();
|
|
1606
|
+
}).catch((error) => {
|
|
1607
|
+
if (isStaleHeartbeat()) return;
|
|
1608
|
+
this.setHeartbeatLatency(null);
|
|
1609
|
+
if (error instanceof RealtimeRequestError && error.retryable) {
|
|
1610
|
+
const retryDelay = Math.max(Math.round(Math.min(5e3, 1e3 * 2 ** retryAttempt) * (.5 + this.random())), error.retryAfterMs ?? 0);
|
|
1611
|
+
if (expiry - performance.now() - retryDelay >= MIN_HEARTBEAT_RETRY_BUDGET_MS) {
|
|
1612
|
+
this.scheduleHeartbeat(retryDelay, retryAttempt + 1);
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
socket?.close(REALTIME_CLIENT_CLOSE_CODES.heartbeatUnavailable, "heartbeat unavailable");
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
if (error instanceof RealtimeRequestTimeout) {
|
|
1619
|
+
socket?.close(REALTIME_CLIENT_CLOSE_CODES.heartbeatTimeout, "heartbeat timeout");
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
if (error instanceof RealtimeRequestError) {
|
|
1623
|
+
socket?.close(REALTIME_CLIENT_CLOSE_CODES.heartbeatRejected, "heartbeat rejected");
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
socket?.close();
|
|
1627
|
+
});
|
|
1628
|
+
}, delayMs);
|
|
1418
1629
|
}
|
|
1419
1630
|
rejectPending(error) {
|
|
1420
1631
|
for (const pending of this.pending.values()) {
|
|
@@ -1426,7 +1637,8 @@ var RealtimeClient = class {
|
|
|
1426
1637
|
setState(state) {
|
|
1427
1638
|
if (this.stateValue === state) return;
|
|
1428
1639
|
this.stateValue = state;
|
|
1429
|
-
|
|
1640
|
+
if (state === "disconnected") this.setHeartbeatLatency(null);
|
|
1641
|
+
notifyListeners(this.stateListeners, (listener) => listener(state), `connection state ${state}`);
|
|
1430
1642
|
}
|
|
1431
1643
|
clearReconnect() {
|
|
1432
1644
|
if (this.reconnectTimeout) clearTimeout(this.reconnectTimeout);
|
|
@@ -1438,6 +1650,6 @@ var RealtimeClient = class {
|
|
|
1438
1650
|
}
|
|
1439
1651
|
};
|
|
1440
1652
|
//#endregion
|
|
1441
|
-
export { REALTIME_JSON_SUBPROTOCOL,
|
|
1653
|
+
export { REALTIME_JSON_SUBPROTOCOL, RealtimeClient, RealtimeRequestError };
|
|
1442
1654
|
|
|
1443
1655
|
//# sourceMappingURL=realtime.js.map
|