vairified 0.1.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/LICENSE +21 -0
- package/README.md +399 -0
- package/dist/index.cjs +1042 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1015 -0
- package/dist/index.d.ts +1015 -0
- package/dist/index.js +994 -0
- package/dist/index.js.map +1 -0
- package/package.json +71 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1042 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AuthenticationError: () => AuthenticationError,
|
|
24
|
+
DEFAULT_SCOPES: () => DEFAULT_SCOPES,
|
|
25
|
+
Match: () => Match,
|
|
26
|
+
MatchResult: () => MatchResult,
|
|
27
|
+
Member: () => Member,
|
|
28
|
+
NotFoundError: () => NotFoundError,
|
|
29
|
+
OAuthError: () => OAuthError,
|
|
30
|
+
Player: () => Player,
|
|
31
|
+
RateLimitError: () => RateLimitError,
|
|
32
|
+
RatingSplit: () => RatingSplit,
|
|
33
|
+
RatingSplits: () => RatingSplits,
|
|
34
|
+
RatingUpdate: () => RatingUpdate,
|
|
35
|
+
SCOPES: () => SCOPES,
|
|
36
|
+
SearchResults: () => SearchResults,
|
|
37
|
+
Vairified: () => Vairified,
|
|
38
|
+
VairifiedError: () => VairifiedError,
|
|
39
|
+
ValidationError: () => ValidationError,
|
|
40
|
+
describeScope: () => describeScope,
|
|
41
|
+
describeScopes: () => describeScopes,
|
|
42
|
+
generateState: () => generateState,
|
|
43
|
+
getAuthorizationUrl: () => getAuthorizationUrl,
|
|
44
|
+
validateScope: () => validateScope
|
|
45
|
+
});
|
|
46
|
+
module.exports = __toCommonJS(index_exports);
|
|
47
|
+
|
|
48
|
+
// src/errors.ts
|
|
49
|
+
var VairifiedError = class extends Error {
|
|
50
|
+
/** HTTP status code */
|
|
51
|
+
statusCode;
|
|
52
|
+
/** Response body */
|
|
53
|
+
response;
|
|
54
|
+
constructor(message, statusCode, response) {
|
|
55
|
+
super(message);
|
|
56
|
+
this.name = "VairifiedError";
|
|
57
|
+
this.statusCode = statusCode;
|
|
58
|
+
this.response = response;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var RateLimitError = class extends VairifiedError {
|
|
62
|
+
/** Seconds to wait before retrying */
|
|
63
|
+
retryAfter;
|
|
64
|
+
constructor(message = "Rate limit exceeded", retryAfter, response) {
|
|
65
|
+
super(message, 429, response);
|
|
66
|
+
this.name = "RateLimitError";
|
|
67
|
+
this.retryAfter = retryAfter;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var AuthenticationError = class extends VairifiedError {
|
|
71
|
+
constructor(message = "Invalid API key", response) {
|
|
72
|
+
super(message, 401, response);
|
|
73
|
+
this.name = "AuthenticationError";
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
var NotFoundError = class extends VairifiedError {
|
|
77
|
+
constructor(message = "Resource not found", response) {
|
|
78
|
+
super(message, 404, response);
|
|
79
|
+
this.name = "NotFoundError";
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
var ValidationError = class extends VairifiedError {
|
|
83
|
+
constructor(message = "Validation error", response) {
|
|
84
|
+
super(message, 400, response);
|
|
85
|
+
this.name = "ValidationError";
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
var OAuthError = class extends VairifiedError {
|
|
89
|
+
/** OAuth error code (e.g., 'invalid_grant', 'expired_token') */
|
|
90
|
+
errorCode;
|
|
91
|
+
constructor(message = "OAuth error", errorCode, response) {
|
|
92
|
+
super(message, void 0, response);
|
|
93
|
+
this.name = "OAuthError";
|
|
94
|
+
this.errorCode = errorCode;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// src/models.ts
|
|
99
|
+
var RatingSplit = class {
|
|
100
|
+
/** The rating value */
|
|
101
|
+
rating;
|
|
102
|
+
/** Abbreviation (e.g., "VG", "50+") */
|
|
103
|
+
abbr;
|
|
104
|
+
/** Date of last match in this category */
|
|
105
|
+
datePlayed;
|
|
106
|
+
constructor(data) {
|
|
107
|
+
if (typeof data === "number") {
|
|
108
|
+
this.rating = data;
|
|
109
|
+
this.abbr = "";
|
|
110
|
+
} else {
|
|
111
|
+
const ratingVal = data.rating;
|
|
112
|
+
this.rating = typeof ratingVal === "string" ? Number.parseFloat(ratingVal) || 0 : ratingVal;
|
|
113
|
+
this.abbr = data.abbr;
|
|
114
|
+
this.datePlayed = data.date_played;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
var RatingSplits = class {
|
|
119
|
+
/** Map of category names to rating splits */
|
|
120
|
+
splits;
|
|
121
|
+
constructor(data) {
|
|
122
|
+
this.splits = /* @__PURE__ */ new Map();
|
|
123
|
+
if (data) {
|
|
124
|
+
for (const [key, value] of Object.entries(data)) {
|
|
125
|
+
this.splits.set(key, new RatingSplit(value));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Get rating for a category */
|
|
130
|
+
get(category) {
|
|
131
|
+
return this.splits.get(category)?.rating;
|
|
132
|
+
}
|
|
133
|
+
/** Open division rating */
|
|
134
|
+
get open() {
|
|
135
|
+
return this.get("open") ?? this.get("VO");
|
|
136
|
+
}
|
|
137
|
+
/** Gender-specific rating (same gender doubles) */
|
|
138
|
+
get gender() {
|
|
139
|
+
return this.get("gender") ?? this.get("VG");
|
|
140
|
+
}
|
|
141
|
+
/** Mixed doubles rating */
|
|
142
|
+
get mixed() {
|
|
143
|
+
return this.get("mixed") ?? this.get("VM");
|
|
144
|
+
}
|
|
145
|
+
/** Recreational rating */
|
|
146
|
+
get recreational() {
|
|
147
|
+
return this.get("recreational") ?? this.get("R");
|
|
148
|
+
}
|
|
149
|
+
/** Singles rating */
|
|
150
|
+
get singles() {
|
|
151
|
+
return this.get("singles") ?? this.get("S");
|
|
152
|
+
}
|
|
153
|
+
/** Best available verified rating */
|
|
154
|
+
get best() {
|
|
155
|
+
const ratings = Array.from(this.splits.values()).map((s) => s.rating).filter((r) => r > 0);
|
|
156
|
+
return ratings.length > 0 ? Math.max(...ratings) : void 0;
|
|
157
|
+
}
|
|
158
|
+
/** Convert to plain object */
|
|
159
|
+
toJSON() {
|
|
160
|
+
const result = {};
|
|
161
|
+
for (const [key, split] of this.splits) {
|
|
162
|
+
result[key] = { rating: split.rating, abbr: split.abbr };
|
|
163
|
+
}
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
function isSearchData(data) {
|
|
168
|
+
return "displayName" in data;
|
|
169
|
+
}
|
|
170
|
+
var Player = class {
|
|
171
|
+
/** External player ID (vair_mem_xxx format) */
|
|
172
|
+
id;
|
|
173
|
+
/** Display name (First Name + Last Initial from search) */
|
|
174
|
+
displayName;
|
|
175
|
+
/** First name (only from connected member) */
|
|
176
|
+
firstName;
|
|
177
|
+
/** Last name (only from connected member) */
|
|
178
|
+
lastName;
|
|
179
|
+
/** Primary/overall rating (2.0-8.0) */
|
|
180
|
+
rating;
|
|
181
|
+
/** Whether player is verified */
|
|
182
|
+
isVairified;
|
|
183
|
+
/** Whether player has connected to your app */
|
|
184
|
+
isConnected;
|
|
185
|
+
/** Ratings by category (only from connected member) */
|
|
186
|
+
ratingSplits;
|
|
187
|
+
/** City */
|
|
188
|
+
city;
|
|
189
|
+
/** State code */
|
|
190
|
+
state;
|
|
191
|
+
/** Country code */
|
|
192
|
+
country;
|
|
193
|
+
_client;
|
|
194
|
+
constructor(data, client) {
|
|
195
|
+
if (isSearchData(data)) {
|
|
196
|
+
this.id = data.id;
|
|
197
|
+
this.displayName = data.displayName;
|
|
198
|
+
this.rating = data.rating ?? 0;
|
|
199
|
+
this.isVairified = data.isVairified ?? false;
|
|
200
|
+
this.isConnected = data.isConnected ?? false;
|
|
201
|
+
this.ratingSplits = new RatingSplits();
|
|
202
|
+
} else {
|
|
203
|
+
this.id = data.id;
|
|
204
|
+
this.firstName = data.firstName ?? "";
|
|
205
|
+
this.lastName = data.lastName ?? "";
|
|
206
|
+
this.rating = data.rating ?? 0;
|
|
207
|
+
this.isVairified = data.isVairified ?? false;
|
|
208
|
+
this.isConnected = true;
|
|
209
|
+
this.ratingSplits = new RatingSplits(data.ratingSplits);
|
|
210
|
+
}
|
|
211
|
+
this.city = data.city;
|
|
212
|
+
this.state = data.state;
|
|
213
|
+
this.country = data.country;
|
|
214
|
+
this._client = client;
|
|
215
|
+
}
|
|
216
|
+
/** Full name (or display name if full name not available) */
|
|
217
|
+
get name() {
|
|
218
|
+
if (this.firstName && this.lastName) {
|
|
219
|
+
return `${this.firstName} ${this.lastName}`.trim();
|
|
220
|
+
}
|
|
221
|
+
return this.displayName ?? "";
|
|
222
|
+
}
|
|
223
|
+
/** Best verified rating */
|
|
224
|
+
get verifiedRating() {
|
|
225
|
+
return this.ratingSplits.best;
|
|
226
|
+
}
|
|
227
|
+
toString() {
|
|
228
|
+
const verified = this.isVairified ? " \u2713" : "";
|
|
229
|
+
return `${this.name} (${this.rating.toFixed(2)})${verified}`;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
var Member = class extends Player {
|
|
233
|
+
/** Email address (only if profile:email scope granted) */
|
|
234
|
+
email;
|
|
235
|
+
/** Scopes the player granted to your app */
|
|
236
|
+
grantedScopes;
|
|
237
|
+
constructor(data, client) {
|
|
238
|
+
super(data, client);
|
|
239
|
+
this.email = data.email;
|
|
240
|
+
this.grantedScopes = data.grantedScopes ?? [];
|
|
241
|
+
}
|
|
242
|
+
/** Check if the player has granted a specific scope */
|
|
243
|
+
hasScope(scope) {
|
|
244
|
+
return this.grantedScopes.includes(scope);
|
|
245
|
+
}
|
|
246
|
+
/** Refresh member data from API */
|
|
247
|
+
async refresh() {
|
|
248
|
+
if (!this._client) {
|
|
249
|
+
throw new Error("Member not connected to client");
|
|
250
|
+
}
|
|
251
|
+
const updated = await this._client.getMember(this.id);
|
|
252
|
+
Object.assign(this, updated);
|
|
253
|
+
return this;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
function generateId() {
|
|
257
|
+
return `SDK-${Math.random().toString(36).substring(2, 14)}`;
|
|
258
|
+
}
|
|
259
|
+
var Match = class {
|
|
260
|
+
/** Event/tournament name */
|
|
261
|
+
event;
|
|
262
|
+
/** Bracket/division name */
|
|
263
|
+
bracket;
|
|
264
|
+
/** Match date */
|
|
265
|
+
date;
|
|
266
|
+
/** Team 1 player IDs */
|
|
267
|
+
team1;
|
|
268
|
+
/** Team 2 player IDs */
|
|
269
|
+
team2;
|
|
270
|
+
/** Game scores */
|
|
271
|
+
scores;
|
|
272
|
+
/** Match type */
|
|
273
|
+
matchType;
|
|
274
|
+
/** Match source */
|
|
275
|
+
source;
|
|
276
|
+
/** Location */
|
|
277
|
+
location;
|
|
278
|
+
/** Unique identifier */
|
|
279
|
+
identifier;
|
|
280
|
+
/** Match ID (set after submission) */
|
|
281
|
+
id;
|
|
282
|
+
constructor(data) {
|
|
283
|
+
this.event = data.event;
|
|
284
|
+
this.bracket = data.bracket;
|
|
285
|
+
this.date = data.date instanceof Date ? data.date : new Date(data.date);
|
|
286
|
+
this.team1 = data.team1;
|
|
287
|
+
this.team2 = data.team2;
|
|
288
|
+
this.scores = data.scores;
|
|
289
|
+
this.matchType = data.matchType ?? "SIDEOUT";
|
|
290
|
+
this.source = data.source ?? "PARTNER";
|
|
291
|
+
this.location = data.location;
|
|
292
|
+
this.identifier = data.identifier ?? generateId();
|
|
293
|
+
}
|
|
294
|
+
/** Match format: SINGLES or DOUBLES */
|
|
295
|
+
get format() {
|
|
296
|
+
return this.team1.length === 1 ? "SINGLES" : "DOUBLES";
|
|
297
|
+
}
|
|
298
|
+
/** Team that won (1 or 2). Returns 0 if tie. */
|
|
299
|
+
get winner() {
|
|
300
|
+
let t1Wins = 0;
|
|
301
|
+
let t2Wins = 0;
|
|
302
|
+
for (const [s1, s2] of this.scores) {
|
|
303
|
+
if (s1 > s2) t1Wins++;
|
|
304
|
+
else if (s2 > s1) t2Wins++;
|
|
305
|
+
}
|
|
306
|
+
if (t1Wins > t2Wins) return 1;
|
|
307
|
+
if (t2Wins > t1Wins) return 2;
|
|
308
|
+
return 0;
|
|
309
|
+
}
|
|
310
|
+
/** Score summary like "11-9, 11-7" */
|
|
311
|
+
get scoreSummary() {
|
|
312
|
+
return this.scores.map(([s1, s2]) => `${s1}-${s2}`).join(", ");
|
|
313
|
+
}
|
|
314
|
+
/** Convert to API request format */
|
|
315
|
+
toJSON() {
|
|
316
|
+
const player1A = this.team1[0];
|
|
317
|
+
const player1B = this.team2[0];
|
|
318
|
+
if (!player1A || !player1B) {
|
|
319
|
+
throw new Error("Match must have at least one player per team");
|
|
320
|
+
}
|
|
321
|
+
const teamA = { player1: player1A };
|
|
322
|
+
const teamB = { player1: player1B };
|
|
323
|
+
if (this.team1[1]) teamA.player2 = this.team1[1];
|
|
324
|
+
if (this.team2[1]) teamB.player2 = this.team2[1];
|
|
325
|
+
const gameKeys = ["game1", "game2", "game3", "game4", "game5"];
|
|
326
|
+
for (let i = 0; i < Math.min(this.scores.length, 5); i++) {
|
|
327
|
+
const score = this.scores[i];
|
|
328
|
+
const key = gameKeys[i];
|
|
329
|
+
if (score && key) {
|
|
330
|
+
teamA[key] = score[0];
|
|
331
|
+
teamB[key] = score[1];
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
identifier: this.identifier,
|
|
336
|
+
bracket: this.bracket,
|
|
337
|
+
event: this.event,
|
|
338
|
+
format: this.format,
|
|
339
|
+
matchDate: this.date.toISOString(),
|
|
340
|
+
matchSource: this.source,
|
|
341
|
+
matchType: this.matchType,
|
|
342
|
+
location: this.location,
|
|
343
|
+
teamA,
|
|
344
|
+
teamB
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
var MatchResult = class {
|
|
349
|
+
/** Whether submission succeeded */
|
|
350
|
+
success;
|
|
351
|
+
/** Number of matches processed */
|
|
352
|
+
numMatches;
|
|
353
|
+
/** Number of games recorded */
|
|
354
|
+
numGames;
|
|
355
|
+
/** Whether this was a dry-run (validation only) */
|
|
356
|
+
dryRun;
|
|
357
|
+
/** Human-readable result message */
|
|
358
|
+
message;
|
|
359
|
+
/** List of validation/processing errors */
|
|
360
|
+
errors;
|
|
361
|
+
constructor(data) {
|
|
362
|
+
this.success = data.success;
|
|
363
|
+
this.numMatches = data.numMatches;
|
|
364
|
+
this.numGames = data.numGames;
|
|
365
|
+
this.dryRun = data.dryRun ?? false;
|
|
366
|
+
this.message = data.message;
|
|
367
|
+
this.errors = data.errors ?? [];
|
|
368
|
+
}
|
|
369
|
+
/** Alias for dryRun */
|
|
370
|
+
get isDryRun() {
|
|
371
|
+
return this.dryRun;
|
|
372
|
+
}
|
|
373
|
+
/** Returns true if submission succeeded without errors */
|
|
374
|
+
get ok() {
|
|
375
|
+
return this.success && this.errors.length === 0;
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
var RatingUpdate = class {
|
|
379
|
+
/** External player ID (vair_mem_xxx format) */
|
|
380
|
+
id;
|
|
381
|
+
/** Member name */
|
|
382
|
+
memberName;
|
|
383
|
+
/** Previous rating */
|
|
384
|
+
previousRating;
|
|
385
|
+
/** New rating */
|
|
386
|
+
newRating;
|
|
387
|
+
/** When the change occurred */
|
|
388
|
+
changedAt;
|
|
389
|
+
/** Updated rating splits */
|
|
390
|
+
ratingSplits;
|
|
391
|
+
_client;
|
|
392
|
+
constructor(data, client) {
|
|
393
|
+
this.id = data.id;
|
|
394
|
+
this.memberName = data.memberName;
|
|
395
|
+
this.previousRating = data.previousRating ?? 0;
|
|
396
|
+
this.newRating = data.newRating ?? 0;
|
|
397
|
+
this.changedAt = data.changedAt ? new Date(data.changedAt) : /* @__PURE__ */ new Date();
|
|
398
|
+
this.ratingSplits = new RatingSplits(data.ratingSplits);
|
|
399
|
+
this._client = client;
|
|
400
|
+
}
|
|
401
|
+
/** Amount of rating change */
|
|
402
|
+
get change() {
|
|
403
|
+
return this.newRating - this.previousRating;
|
|
404
|
+
}
|
|
405
|
+
/** Whether rating improved */
|
|
406
|
+
get improved() {
|
|
407
|
+
return this.change > 0;
|
|
408
|
+
}
|
|
409
|
+
/** Fetch the member associated with this update */
|
|
410
|
+
async getMember() {
|
|
411
|
+
if (!this._client) {
|
|
412
|
+
throw new Error("Update not connected to client");
|
|
413
|
+
}
|
|
414
|
+
return this._client.getMember(this.id);
|
|
415
|
+
}
|
|
416
|
+
toString() {
|
|
417
|
+
const direction = this.improved ? "\u2191" : "\u2193";
|
|
418
|
+
const name = this.memberName ? ` (${this.memberName})` : "";
|
|
419
|
+
return `${this.id}${name}: ${this.previousRating.toFixed(2)} ${direction} ${this.newRating.toFixed(2)}`;
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
var SearchResults = class {
|
|
423
|
+
/** List of players */
|
|
424
|
+
players;
|
|
425
|
+
/** Total matching players */
|
|
426
|
+
total;
|
|
427
|
+
/** Current page */
|
|
428
|
+
page;
|
|
429
|
+
/** Results per page */
|
|
430
|
+
limit;
|
|
431
|
+
_client;
|
|
432
|
+
_filters;
|
|
433
|
+
constructor(data, client, filters = {}) {
|
|
434
|
+
this.players = data.players.map((p) => new Player(p, client));
|
|
435
|
+
this.total = data.total;
|
|
436
|
+
this.page = data.page;
|
|
437
|
+
this.limit = data.limit;
|
|
438
|
+
this._client = client;
|
|
439
|
+
this._filters = filters;
|
|
440
|
+
}
|
|
441
|
+
/** Whether more results are available */
|
|
442
|
+
get hasMore() {
|
|
443
|
+
return this.page * this.limit < this.total;
|
|
444
|
+
}
|
|
445
|
+
/** Total number of pages */
|
|
446
|
+
get pages() {
|
|
447
|
+
return this.limit > 0 ? Math.ceil(this.total / this.limit) : 0;
|
|
448
|
+
}
|
|
449
|
+
/** Number of players in current page */
|
|
450
|
+
get length() {
|
|
451
|
+
return this.players.length;
|
|
452
|
+
}
|
|
453
|
+
/** Get player by index */
|
|
454
|
+
at(index) {
|
|
455
|
+
return this.players[index];
|
|
456
|
+
}
|
|
457
|
+
/** Iterate over players */
|
|
458
|
+
[Symbol.iterator]() {
|
|
459
|
+
return this.players[Symbol.iterator]();
|
|
460
|
+
}
|
|
461
|
+
/** Fetch next page of results */
|
|
462
|
+
async nextPage() {
|
|
463
|
+
if (!this._client) {
|
|
464
|
+
throw new Error("Results not connected to client");
|
|
465
|
+
}
|
|
466
|
+
if (!this.hasMore) {
|
|
467
|
+
throw new Error("No more pages");
|
|
468
|
+
}
|
|
469
|
+
return this._client.search({
|
|
470
|
+
...this._filters,
|
|
471
|
+
page: this.page + 1
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
// src/oauth.ts
|
|
477
|
+
var SCOPES = {
|
|
478
|
+
"profile:read": "Access your name, location, and verification status",
|
|
479
|
+
"profile:email": "Access your email address",
|
|
480
|
+
"rating:read": "View your current rating and rating splits",
|
|
481
|
+
"rating:history": "View your complete rating history",
|
|
482
|
+
"match:submit": "Submit match results on your behalf",
|
|
483
|
+
"webhook:subscribe": "Receive notifications when your rating changes"
|
|
484
|
+
};
|
|
485
|
+
var DEFAULT_SCOPES = ["profile:read", "rating:read"];
|
|
486
|
+
function getAuthorizationUrl(config, scopes = DEFAULT_SCOPES, state) {
|
|
487
|
+
const baseUrl = config.baseUrl || "https://api-next.vairified.com/api/v1";
|
|
488
|
+
const scopeSet = new Set(scopes);
|
|
489
|
+
scopeSet.add("profile:read");
|
|
490
|
+
const scopeList = Array.from(scopeSet);
|
|
491
|
+
const params = new URLSearchParams({
|
|
492
|
+
redirect_uri: config.redirectUri,
|
|
493
|
+
scope: scopeList.join(","),
|
|
494
|
+
response_type: "code"
|
|
495
|
+
});
|
|
496
|
+
if (state) {
|
|
497
|
+
params.set("state", state);
|
|
498
|
+
}
|
|
499
|
+
return `${baseUrl}/partner/oauth/authorize?${params.toString()}`;
|
|
500
|
+
}
|
|
501
|
+
function validateScope(scope) {
|
|
502
|
+
return scope in SCOPES;
|
|
503
|
+
}
|
|
504
|
+
function describeScope(scope) {
|
|
505
|
+
return SCOPES[scope] ?? `Unknown scope: ${scope}`;
|
|
506
|
+
}
|
|
507
|
+
function describeScopes(scopes) {
|
|
508
|
+
return scopes.map((scope) => ({
|
|
509
|
+
scope,
|
|
510
|
+
description: describeScope(scope)
|
|
511
|
+
}));
|
|
512
|
+
}
|
|
513
|
+
function generateState() {
|
|
514
|
+
const array = new Uint8Array(16);
|
|
515
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
516
|
+
crypto.getRandomValues(array);
|
|
517
|
+
} else {
|
|
518
|
+
for (let i = 0; i < array.length; i++) {
|
|
519
|
+
array[i] = Math.floor(Math.random() * 256);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return Array.from(array).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// src/client.ts
|
|
526
|
+
var ENVIRONMENTS = {
|
|
527
|
+
production: "https://api-next.vairified.com/api/v1",
|
|
528
|
+
staging: "https://api-staging.vairified.com/api/v1",
|
|
529
|
+
local: "http://localhost:3001/api/v1"
|
|
530
|
+
};
|
|
531
|
+
var DEFAULT_BASE_URL = ENVIRONMENTS.production;
|
|
532
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
533
|
+
var Vairified = class {
|
|
534
|
+
/** API key */
|
|
535
|
+
apiKey;
|
|
536
|
+
/** Base URL */
|
|
537
|
+
baseUrl;
|
|
538
|
+
/** Environment name */
|
|
539
|
+
env;
|
|
540
|
+
/** Request timeout in ms */
|
|
541
|
+
timeout;
|
|
542
|
+
constructor(options = {}) {
|
|
543
|
+
this.apiKey = options.apiKey || this.getEnvApiKey();
|
|
544
|
+
if (!this.apiKey) {
|
|
545
|
+
throw new Error("API key required. Pass apiKey option or set VAIRIFIED_API_KEY env var.");
|
|
546
|
+
}
|
|
547
|
+
if (options.baseUrl) {
|
|
548
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
549
|
+
this.env = "production";
|
|
550
|
+
} else if (options.env) {
|
|
551
|
+
this.baseUrl = ENVIRONMENTS[options.env];
|
|
552
|
+
this.env = options.env;
|
|
553
|
+
} else {
|
|
554
|
+
const envVar = this.getEnvVar("VAIRIFIED_ENV");
|
|
555
|
+
const defaultEnv = envVar && envVar in ENVIRONMENTS ? envVar : "production";
|
|
556
|
+
this.baseUrl = ENVIRONMENTS[defaultEnv] || DEFAULT_BASE_URL;
|
|
557
|
+
this.env = defaultEnv;
|
|
558
|
+
}
|
|
559
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT;
|
|
560
|
+
}
|
|
561
|
+
getEnvVar(name) {
|
|
562
|
+
if (typeof process !== "undefined" && process.env?.[name]) {
|
|
563
|
+
return process.env[name];
|
|
564
|
+
}
|
|
565
|
+
return "";
|
|
566
|
+
}
|
|
567
|
+
getEnvApiKey() {
|
|
568
|
+
return this.getEnvVar("VAIRIFIED_API_KEY");
|
|
569
|
+
}
|
|
570
|
+
getHeaders() {
|
|
571
|
+
return {
|
|
572
|
+
"X-API-Key": this.apiKey,
|
|
573
|
+
"Content-Type": "application/json",
|
|
574
|
+
Accept: "application/json"
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
async handleError(response) {
|
|
578
|
+
let body;
|
|
579
|
+
let message;
|
|
580
|
+
try {
|
|
581
|
+
body = await response.json();
|
|
582
|
+
message = body.message || response.statusText;
|
|
583
|
+
} catch {
|
|
584
|
+
message = response.statusText;
|
|
585
|
+
}
|
|
586
|
+
const status = response.status;
|
|
587
|
+
if (status === 401) throw new AuthenticationError(message, body);
|
|
588
|
+
if (status === 404) throw new NotFoundError(message, body);
|
|
589
|
+
if (status === 429) {
|
|
590
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
591
|
+
throw new RateLimitError(
|
|
592
|
+
message,
|
|
593
|
+
retryAfter ? Number.parseInt(retryAfter, 10) : void 0,
|
|
594
|
+
body
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
if (status === 400) throw new ValidationError(message, body);
|
|
598
|
+
throw new VairifiedError(message, status, body);
|
|
599
|
+
}
|
|
600
|
+
async request(method, path, options) {
|
|
601
|
+
let url = `${this.baseUrl}${path}`;
|
|
602
|
+
if (options?.params) {
|
|
603
|
+
const searchParams = new URLSearchParams();
|
|
604
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
605
|
+
if (value !== void 0 && value !== null) {
|
|
606
|
+
searchParams.append(key, String(value));
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
const queryString = searchParams.toString();
|
|
610
|
+
if (queryString) url += `?${queryString}`;
|
|
611
|
+
}
|
|
612
|
+
const controller = new AbortController();
|
|
613
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
614
|
+
try {
|
|
615
|
+
const response = await fetch(url, {
|
|
616
|
+
method,
|
|
617
|
+
headers: this.getHeaders(),
|
|
618
|
+
body: options?.body ? JSON.stringify(options.body) : void 0,
|
|
619
|
+
signal: controller.signal
|
|
620
|
+
});
|
|
621
|
+
if (!response.ok) await this.handleError(response);
|
|
622
|
+
return await response.json();
|
|
623
|
+
} finally {
|
|
624
|
+
clearTimeout(timeoutId);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
// ---------------------------------------------------------------------------
|
|
628
|
+
// Member Operations
|
|
629
|
+
// ---------------------------------------------------------------------------
|
|
630
|
+
/**
|
|
631
|
+
* Get a connected member by their external ID.
|
|
632
|
+
*
|
|
633
|
+
* **Requires OAuth Connection**: The player must have connected their
|
|
634
|
+
* account to your application via OAuth before you can access their data.
|
|
635
|
+
*
|
|
636
|
+
* @param playerId - External player ID (vair_mem_xxx format)
|
|
637
|
+
* @returns Member object with profile and rating data
|
|
638
|
+
* @throws NotFoundError if member is not found or invalid ID format
|
|
639
|
+
* @throws ForbiddenError if player has not connected to your app
|
|
640
|
+
*
|
|
641
|
+
* @example
|
|
642
|
+
* ```ts
|
|
643
|
+
* const member = await client.getMember('vair_mem_0ABC123def456GHI789jk');
|
|
644
|
+
* console.log(member.name, member.rating);
|
|
645
|
+
* console.log(member.ratingSplits.open); // Open division rating
|
|
646
|
+
* console.log(member.grantedScopes); // ['profile:read', 'rating:read']
|
|
647
|
+
* ```
|
|
648
|
+
*/
|
|
649
|
+
async getMember(playerId) {
|
|
650
|
+
const data = await this.request("GET", "/partner/member", {
|
|
651
|
+
params: { id: playerId }
|
|
652
|
+
});
|
|
653
|
+
return new Member(data, this);
|
|
654
|
+
}
|
|
655
|
+
// ---------------------------------------------------------------------------
|
|
656
|
+
// Search Operations
|
|
657
|
+
// ---------------------------------------------------------------------------
|
|
658
|
+
/**
|
|
659
|
+
* Search for players.
|
|
660
|
+
*
|
|
661
|
+
* @param filters - Search filters
|
|
662
|
+
* @returns SearchResults with players and pagination
|
|
663
|
+
*
|
|
664
|
+
* @example
|
|
665
|
+
* ```ts
|
|
666
|
+
* const results = await client.search({
|
|
667
|
+
* city: 'Austin',
|
|
668
|
+
* ratingMin: 4.0,
|
|
669
|
+
* vairifiedOnly: true,
|
|
670
|
+
* });
|
|
671
|
+
*
|
|
672
|
+
* for (const player of results) {
|
|
673
|
+
* console.log(player.name, player.rating);
|
|
674
|
+
* }
|
|
675
|
+
*
|
|
676
|
+
* // Pagination
|
|
677
|
+
* if (results.hasMore) {
|
|
678
|
+
* const nextPage = await results.nextPage();
|
|
679
|
+
* }
|
|
680
|
+
* ```
|
|
681
|
+
*/
|
|
682
|
+
async search(filters = {}) {
|
|
683
|
+
const params = {
|
|
684
|
+
limit: filters.limit ?? 20
|
|
685
|
+
};
|
|
686
|
+
if (filters.name) params.member = filters.name;
|
|
687
|
+
if (filters.city) params.city = filters.city;
|
|
688
|
+
if (filters.state) params.state = filters.state;
|
|
689
|
+
if (filters.country) params.country = filters.country;
|
|
690
|
+
if (filters.zipCode) params.zip = filters.zipCode;
|
|
691
|
+
if (filters.ratingMin !== void 0) params.rating1 = filters.ratingMin;
|
|
692
|
+
if (filters.ratingMax !== void 0) params.rating2 = filters.ratingMax;
|
|
693
|
+
if (filters.gender) params.gender = filters.gender;
|
|
694
|
+
if (filters.vairifiedOnly) params.vairified = true;
|
|
695
|
+
if (filters.sortBy) {
|
|
696
|
+
params.sortField = filters.sortBy;
|
|
697
|
+
params.sortDirection = filters.sortOrder ?? "desc";
|
|
698
|
+
}
|
|
699
|
+
if (filters.age !== void 0) {
|
|
700
|
+
params.ageFilterType = "exact";
|
|
701
|
+
params.age1 = filters.age;
|
|
702
|
+
} else if (filters.ageMin !== void 0 && filters.ageMax !== void 0) {
|
|
703
|
+
params.ageFilterType = "range";
|
|
704
|
+
params.age1 = filters.ageMin;
|
|
705
|
+
params.age2 = filters.ageMax;
|
|
706
|
+
} else if (filters.ageMin !== void 0) {
|
|
707
|
+
params.ageFilterType = "above";
|
|
708
|
+
params.age1 = filters.ageMin;
|
|
709
|
+
} else if (filters.ageMax !== void 0) {
|
|
710
|
+
params.ageFilterType = "below";
|
|
711
|
+
params.age1 = filters.ageMax;
|
|
712
|
+
}
|
|
713
|
+
const page = filters.page ?? 1;
|
|
714
|
+
if (page > 1) {
|
|
715
|
+
params.offset = (page - 1) * (filters.limit ?? 20);
|
|
716
|
+
}
|
|
717
|
+
const data = await this.request(
|
|
718
|
+
"GET",
|
|
719
|
+
"/partner/search",
|
|
720
|
+
{
|
|
721
|
+
params
|
|
722
|
+
}
|
|
723
|
+
);
|
|
724
|
+
const normalized = Array.isArray(data) ? { players: data, total: data.length, page, limit: filters.limit ?? 20 } : data;
|
|
725
|
+
return new SearchResults(normalized, this, filters);
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Find a single player by name.
|
|
729
|
+
*
|
|
730
|
+
* @param name - Player name to search for
|
|
731
|
+
* @returns Player if found, undefined otherwise
|
|
732
|
+
*
|
|
733
|
+
* @example
|
|
734
|
+
* ```ts
|
|
735
|
+
* const player = await client.findPlayer('John Smith');
|
|
736
|
+
* if (player) {
|
|
737
|
+
* console.log(player.rating);
|
|
738
|
+
* }
|
|
739
|
+
* ```
|
|
740
|
+
*/
|
|
741
|
+
async findPlayer(name) {
|
|
742
|
+
const results = await this.search({ name, limit: 1 });
|
|
743
|
+
return results.at(0);
|
|
744
|
+
}
|
|
745
|
+
// ---------------------------------------------------------------------------
|
|
746
|
+
// Match Operations
|
|
747
|
+
// ---------------------------------------------------------------------------
|
|
748
|
+
/**
|
|
749
|
+
* Submit a single match.
|
|
750
|
+
*
|
|
751
|
+
* @param match - Match object with teams and scores
|
|
752
|
+
* @returns MatchResult with submission status
|
|
753
|
+
*
|
|
754
|
+
* @example
|
|
755
|
+
* ```ts
|
|
756
|
+
* const match = new Match({
|
|
757
|
+
* event: 'Weekly League',
|
|
758
|
+
* bracket: '4.0 Doubles',
|
|
759
|
+
* date: new Date(),
|
|
760
|
+
* team1: ['p1', 'p2'],
|
|
761
|
+
* team2: ['p3', 'p4'],
|
|
762
|
+
* scores: [[11, 9], [11, 7]],
|
|
763
|
+
* });
|
|
764
|
+
*
|
|
765
|
+
* const result = await client.submitMatch(match);
|
|
766
|
+
* if (result.ok) {
|
|
767
|
+
* console.log(`Submitted ${result.numGames} games`);
|
|
768
|
+
* }
|
|
769
|
+
* ```
|
|
770
|
+
*/
|
|
771
|
+
async submitMatch(match) {
|
|
772
|
+
return this.submitMatches([match]);
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Submit multiple matches in a batch.
|
|
776
|
+
*
|
|
777
|
+
* @param matches - List of Match objects
|
|
778
|
+
* @returns MatchResult with submission status
|
|
779
|
+
*
|
|
780
|
+
* @example
|
|
781
|
+
* ```ts
|
|
782
|
+
* const result = await client.submitMatches([match1, match2, match3]);
|
|
783
|
+
* console.log(`Submitted ${result.numGames} games from ${result.numMatches} matches`);
|
|
784
|
+
*
|
|
785
|
+
* if (result.dryRun) {
|
|
786
|
+
* console.log('This was a dry run - no data persisted');
|
|
787
|
+
* }
|
|
788
|
+
* ```
|
|
789
|
+
*/
|
|
790
|
+
async submitMatches(matches) {
|
|
791
|
+
const data = await this.request("POST", "/partner/matches", {
|
|
792
|
+
body: { matches: matches.map((m) => m.toJSON()) }
|
|
793
|
+
});
|
|
794
|
+
return new MatchResult(data);
|
|
795
|
+
}
|
|
796
|
+
// ---------------------------------------------------------------------------
|
|
797
|
+
// Rating Updates
|
|
798
|
+
// ---------------------------------------------------------------------------
|
|
799
|
+
/**
|
|
800
|
+
* Get rating updates for subscribed members.
|
|
801
|
+
*
|
|
802
|
+
* Members are subscribed when you call getMember().
|
|
803
|
+
*
|
|
804
|
+
* @returns List of RatingUpdate objects
|
|
805
|
+
*
|
|
806
|
+
* @example
|
|
807
|
+
* ```ts
|
|
808
|
+
* const updates = await client.getRatingUpdates();
|
|
809
|
+
* for (const update of updates) {
|
|
810
|
+
* console.log(`${update.memberId}: ${update.previousRating} → ${update.newRating}`);
|
|
811
|
+
* if (update.improved) {
|
|
812
|
+
* const member = await update.getMember();
|
|
813
|
+
* console.log(`${member.name} improved!`);
|
|
814
|
+
* }
|
|
815
|
+
* }
|
|
816
|
+
* ```
|
|
817
|
+
*/
|
|
818
|
+
async getRatingUpdates() {
|
|
819
|
+
const data = await this.request(
|
|
820
|
+
"GET",
|
|
821
|
+
"/partner/rating-updates"
|
|
822
|
+
);
|
|
823
|
+
return (data.updates ?? []).map((u) => new RatingUpdate(u, this));
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Test webhook endpoint.
|
|
827
|
+
*
|
|
828
|
+
* @param webhookUrl - URL to send test webhook to
|
|
829
|
+
* @returns Test result
|
|
830
|
+
*/
|
|
831
|
+
async testWebhook(webhookUrl) {
|
|
832
|
+
return this.request("POST", "/partner/webhook-test", {
|
|
833
|
+
body: { webhookUrl }
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
// ---------------------------------------------------------------------------
|
|
837
|
+
// OAuth Operations
|
|
838
|
+
// ---------------------------------------------------------------------------
|
|
839
|
+
/**
|
|
840
|
+
* Start an OAuth authorization flow.
|
|
841
|
+
*
|
|
842
|
+
* This creates a pending authorization and returns the URL where
|
|
843
|
+
* users should be redirected to approve access.
|
|
844
|
+
*
|
|
845
|
+
* @param redirectUri - Your application's callback URL
|
|
846
|
+
* @param scopes - Permission scopes to request (defaults to profile:read, rating:read)
|
|
847
|
+
* @param state - CSRF protection state parameter (recommended)
|
|
848
|
+
* @returns AuthorizationResponse with the URL to redirect users to
|
|
849
|
+
* @throws OAuthError if the authorization fails to start
|
|
850
|
+
*
|
|
851
|
+
* @example
|
|
852
|
+
* ```ts
|
|
853
|
+
* const auth = await client.startOAuth(
|
|
854
|
+
* 'https://myapp.com/callback',
|
|
855
|
+
* ['profile:read', 'rating:read', 'match:submit'],
|
|
856
|
+
* 'random_csrf_token',
|
|
857
|
+
* );
|
|
858
|
+
* // Redirect user to auth.authorizationUrl
|
|
859
|
+
* window.location.href = auth.authorizationUrl;
|
|
860
|
+
* ```
|
|
861
|
+
*
|
|
862
|
+
* @category OAuth
|
|
863
|
+
*/
|
|
864
|
+
async startOAuth(redirectUri, scopes = [...DEFAULT_SCOPES], state) {
|
|
865
|
+
const scopeSet = new Set(scopes);
|
|
866
|
+
scopeSet.add("profile:read");
|
|
867
|
+
const scopeList = Array.from(scopeSet);
|
|
868
|
+
for (const scope of scopeList) {
|
|
869
|
+
if (!(scope in SCOPES)) {
|
|
870
|
+
throw new OAuthError(`Invalid scope: ${scope}`, "invalid_scope");
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
const data = await this.request("POST", "/partner/oauth/authorize", {
|
|
874
|
+
body: {
|
|
875
|
+
redirectUri,
|
|
876
|
+
scope: scopeList.join(","),
|
|
877
|
+
state
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
return {
|
|
881
|
+
authorizationUrl: data.authorizationUrl,
|
|
882
|
+
code: data.code,
|
|
883
|
+
state
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Exchange an authorization code for access and refresh tokens.
|
|
888
|
+
*
|
|
889
|
+
* Call this after the user approves access and is redirected back
|
|
890
|
+
* to your application with a code parameter.
|
|
891
|
+
*
|
|
892
|
+
* @param code - Authorization code from the callback URL
|
|
893
|
+
* @param redirectUri - Must match the redirectUri used in startOAuth
|
|
894
|
+
* @returns TokenResponse with access_token, refresh_token, and player_id
|
|
895
|
+
* @throws OAuthError if the code is invalid or expired
|
|
896
|
+
*
|
|
897
|
+
* @example
|
|
898
|
+
* ```ts
|
|
899
|
+
* // After user is redirected to: https://myapp.com/callback?code=xxx
|
|
900
|
+
* const tokens = await client.exchangeToken(
|
|
901
|
+
* new URL(window.location.href).searchParams.get('code')!,
|
|
902
|
+
* 'https://myapp.com/callback',
|
|
903
|
+
* );
|
|
904
|
+
* // Store tokens.accessToken and tokens.refreshToken securely
|
|
905
|
+
* // Use tokens.playerId to identify the connected player
|
|
906
|
+
* ```
|
|
907
|
+
*
|
|
908
|
+
* @category OAuth
|
|
909
|
+
*/
|
|
910
|
+
async exchangeToken(code, redirectUri) {
|
|
911
|
+
const data = await this.request("POST", "/partner/oauth/token", {
|
|
912
|
+
body: { code, redirectUri }
|
|
913
|
+
});
|
|
914
|
+
return {
|
|
915
|
+
accessToken: data.accessToken,
|
|
916
|
+
refreshToken: data.refreshToken,
|
|
917
|
+
expiresIn: data.expiresIn,
|
|
918
|
+
scope: data.scope ? data.scope.split(",") : [],
|
|
919
|
+
playerId: data.playerId
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Refresh an expired access token.
|
|
924
|
+
*
|
|
925
|
+
* Use this when an access token expires to obtain a new one
|
|
926
|
+
* without requiring the user to re-authorize.
|
|
927
|
+
*
|
|
928
|
+
* @param refreshToken - The refresh token from a previous token exchange
|
|
929
|
+
* @returns TokenResponse with new access_token and optionally a new refresh_token
|
|
930
|
+
* @throws OAuthError if the refresh token is invalid or revoked
|
|
931
|
+
*
|
|
932
|
+
* @example
|
|
933
|
+
* ```ts
|
|
934
|
+
* try {
|
|
935
|
+
* const newTokens = await client.refreshAccessToken(storedRefreshToken);
|
|
936
|
+
* // Update stored tokens
|
|
937
|
+
* } catch (e) {
|
|
938
|
+
* if (e instanceof OAuthError && e.errorCode === 'invalid_grant') {
|
|
939
|
+
* // Refresh token revoked, user needs to re-authorize
|
|
940
|
+
* }
|
|
941
|
+
* }
|
|
942
|
+
* ```
|
|
943
|
+
*
|
|
944
|
+
* @category OAuth
|
|
945
|
+
*/
|
|
946
|
+
async refreshAccessToken(refreshToken) {
|
|
947
|
+
const data = await this.request("POST", "/partner/oauth/refresh", {
|
|
948
|
+
body: { refreshToken }
|
|
949
|
+
});
|
|
950
|
+
return {
|
|
951
|
+
accessToken: data.accessToken,
|
|
952
|
+
refreshToken: data.refreshToken,
|
|
953
|
+
expiresIn: data.expiresIn,
|
|
954
|
+
scope: data.scope ? data.scope.split(",") : [],
|
|
955
|
+
playerId: data.playerId
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Revoke a player's OAuth connection.
|
|
960
|
+
*
|
|
961
|
+
* This disconnects the player from your application. You will no
|
|
962
|
+
* longer be able to access their data or submit matches on their behalf.
|
|
963
|
+
*
|
|
964
|
+
* @param playerId - The player's external ID (vair_mem_xxx format)
|
|
965
|
+
* @throws OAuthError if the revocation fails
|
|
966
|
+
*
|
|
967
|
+
* @example
|
|
968
|
+
* ```ts
|
|
969
|
+
* await client.revokeConnection('vair_mem_0ABC123def456GHI789jk');
|
|
970
|
+
* // Player is now disconnected
|
|
971
|
+
* ```
|
|
972
|
+
*
|
|
973
|
+
* @category OAuth
|
|
974
|
+
*/
|
|
975
|
+
async revokeConnection(playerId) {
|
|
976
|
+
await this.request("POST", "/partner/oauth/revoke", {
|
|
977
|
+
body: { playerId }
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Get a list of available OAuth scopes.
|
|
982
|
+
*
|
|
983
|
+
* @returns List of scope objects with id, name, and description
|
|
984
|
+
*
|
|
985
|
+
* @example
|
|
986
|
+
* ```ts
|
|
987
|
+
* const scopes = await client.getAvailableScopes();
|
|
988
|
+
* for (const scope of scopes) {
|
|
989
|
+
* console.log(`${scope.id}: ${scope.description}`);
|
|
990
|
+
* }
|
|
991
|
+
* ```
|
|
992
|
+
*
|
|
993
|
+
* @category OAuth
|
|
994
|
+
*/
|
|
995
|
+
async getAvailableScopes() {
|
|
996
|
+
const data = await this.request("GET", "/partner/oauth/scopes");
|
|
997
|
+
return data.scopes ?? [];
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Get API usage statistics for your partner account.
|
|
1001
|
+
*
|
|
1002
|
+
* @returns Usage statistics (requests, limits, etc.)
|
|
1003
|
+
*
|
|
1004
|
+
* @example
|
|
1005
|
+
* ```ts
|
|
1006
|
+
* const usage = await client.getUsage();
|
|
1007
|
+
* console.log(`Requests today: ${usage.requestsToday}`);
|
|
1008
|
+
* console.log(`Rate limit: ${usage.rateLimit}/hour`);
|
|
1009
|
+
* ```
|
|
1010
|
+
*
|
|
1011
|
+
* @category Client
|
|
1012
|
+
*/
|
|
1013
|
+
async getUsage() {
|
|
1014
|
+
return this.request("GET", "/partner/usage");
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1018
|
+
0 && (module.exports = {
|
|
1019
|
+
AuthenticationError,
|
|
1020
|
+
DEFAULT_SCOPES,
|
|
1021
|
+
Match,
|
|
1022
|
+
MatchResult,
|
|
1023
|
+
Member,
|
|
1024
|
+
NotFoundError,
|
|
1025
|
+
OAuthError,
|
|
1026
|
+
Player,
|
|
1027
|
+
RateLimitError,
|
|
1028
|
+
RatingSplit,
|
|
1029
|
+
RatingSplits,
|
|
1030
|
+
RatingUpdate,
|
|
1031
|
+
SCOPES,
|
|
1032
|
+
SearchResults,
|
|
1033
|
+
Vairified,
|
|
1034
|
+
VairifiedError,
|
|
1035
|
+
ValidationError,
|
|
1036
|
+
describeScope,
|
|
1037
|
+
describeScopes,
|
|
1038
|
+
generateState,
|
|
1039
|
+
getAuthorizationUrl,
|
|
1040
|
+
validateScope
|
|
1041
|
+
});
|
|
1042
|
+
//# sourceMappingURL=index.cjs.map
|