vairified 0.3.2 → 0.6.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/README.md CHANGED
@@ -52,7 +52,7 @@ Every operation lives on a sub-resource that mirrors the REST path:
52
52
 
53
53
  | Sub-resource | Operations |
54
54
  |-------------------------|------------------------------------------------------------------|
55
- | `client.members` | `get`, `getBulk`, `search`, `find`, `ratingUpdates` |
55
+ | `client.members` | `get`, `getBulk`, `getByEmail`, `search`, `find`, `ratingUpdates` |
56
56
  | `client.matches` | `submit`, `tournamentImport`, `testWebhook` |
57
57
  | `client.oauth` | `authorize`, `exchangeToken`, `refresh`, `revoke` |
58
58
  | `client.webhooks` | `deliveries` |
@@ -69,12 +69,13 @@ const member = await client.members.get('vair_mem_xxx');
69
69
  console.log(member.name); // Full name
70
70
  console.log(member.displayName); // "Mike B."
71
71
  console.log(member.ratingFor('pickleball')); // 3.915
72
- console.log(member.status.isVairified); // true
72
+ console.log(member.sport.get('pickleball')?.isVairified); // true (per-sport)
73
73
 
74
74
  // Dict-like access to rating splits for a specific sport
75
75
  const pb = member.sport.get('pickleball');
76
76
  if (pb) {
77
77
  console.log(pb.rating, pb.abbr); // 3.915 VO
78
+ console.log(pb.isVairified, pb.isVairPro); // per-sport status flags
78
79
  console.log(pb.get('overall-open')?.rating); // 3.915
79
80
  console.log(pb.has('singles-open')); // true
80
81
  for (const [key, split] of pb) {
@@ -99,6 +100,34 @@ const pb = await client.members.getBulk([4873327], { sport: 'pickleball' });
99
100
 
100
101
  Unknown IDs are silently omitted — the returned array may be shorter than the input.
101
102
 
103
+ ### Look members up by email
104
+
105
+ Resolve up to 100 members by their **exact** email address — useful for linking
106
+ your users to their VAIR identity when you hold their email but not their member
107
+ ID, instead of waiting for each player to complete SSO:
108
+
109
+ ```ts
110
+ const result = await client.members.getByEmail(['ada@example.com', 'nobody@example.com']);
111
+
112
+ for (const match of result.matched) {
113
+ const member = match.sole; // null when the address is ambiguous
114
+ if (member) console.log(match.email, '->', member.memberId);
115
+ }
116
+
117
+ // Read notFound directly — don't diff your input against the results
118
+ console.log('no VAIR account found for:', result.notFound);
119
+ ```
120
+
121
+ Requires the **`key:player:lookup`** scope, granted per partner on approval —
122
+ holding `key:player:search` does not imply it. Ask VAIR to enable it for your app.
123
+
124
+ Unlike `getBulk`, **nothing is silently dropped**: every address you supply comes
125
+ back in either `matched` or `notFound`. Matching is exact and case-insensitive
126
+ (no partial or fuzzy matching), and `match.members` is an array because an email
127
+ is not a unique key in VAIR — use `.sole` or check `.isAmbiguous` rather than
128
+ assuming a single result. A `notFound` address is not proof the person has no VAIR
129
+ account: unclaimed imported records are excluded from this lookup.
130
+
102
131
  ### Filter ratings to specific sports
103
132
 
104
133
  ```ts
@@ -357,6 +386,60 @@ export VAIRIFIED_ENV="staging" # optional; default: production
357
386
  const client = new Vairified(); // reads both env vars
358
387
  ```
359
388
 
389
+ ## React Native
390
+
391
+ The SDK keeps its **zero-dependency** promise on React Native too — it doesn't bundle
392
+ any polyfills. Instead it feature-detects the platform primitives it needs and expects
393
+ your app to provide the two that Hermes lacks.
394
+
395
+ ### Required polyfills
396
+
397
+ Install them and import each **once, at your app entry** (e.g. the top of `index.js`),
398
+ before any SDK call:
399
+
400
+ ```bash
401
+ npm install react-native-get-random-values react-native-url-polyfill
402
+ ```
403
+
404
+ ```ts
405
+ // index.js — must run before `import { Vairified } from 'vairified'`
406
+ import 'react-native-get-random-values'; // Web Crypto for generateState()
407
+ import 'react-native-url-polyfill/auto'; // WHATWG URL / URLSearchParams
408
+ ```
409
+
410
+ - **`react-native-get-random-values`** backs `crypto.getRandomValues`, which
411
+ `generateState()` uses for CSRF tokens. Without it, `generateState()` throws a
412
+ descriptive error (not a bare `ReferenceError`); you can also skip it and pass your
413
+ own high-entropy `state` string to `oauth.authorize()`.
414
+ - **`react-native-url-polyfill`** provides a complete `URL`/`URLSearchParams`. Hermes'
415
+ built-ins are incomplete, so request-URL and authorization-URL building need this.
416
+
417
+ ### Pass `apiKey` and `env` explicitly
418
+
419
+ React Native has no `process.env`, so the `VAIRIFIED_API_KEY` / `VAIRIFIED_ENV` fallbacks
420
+ never resolve there (the SDK guards against the missing global rather than crashing).
421
+ Always construct the client explicitly:
422
+
423
+ ```ts
424
+ const client = new Vairified({ apiKey: 'vair_pk_xxx', env: 'production' });
425
+ ```
426
+
427
+ ### Token topology — never ship the API key in the app
428
+
429
+ The secret partner API key (`X-API-Key`) **must never ship in a mobile app binary**.
430
+ Split the OAuth flow across the app and your backend:
431
+
432
+ 1. **App:** open the authorization URL using your public `client_id` (your `PartnerApp`
433
+ slug) — build it with `getAuthorizationUrl({ redirectUri, clientId })` — and capture
434
+ the `myapp://callback` deep link to read the `code` (and `state`).
435
+ 2. **Your backend:** perform the code exchange there with a `Vairified` client that holds
436
+ the secret key — `client.oauth.exchangeToken({ code, redirectUri })`, and likewise
437
+ `refresh()` / `revoke()`. The SDK injects `X-API-Key` on these calls, so they belong
438
+ on the server, never in the app.
439
+
440
+ The app sends the captured `code` to your backend over your own authenticated channel;
441
+ the backend returns only the resulting access token (or a session) to the app.
442
+
360
443
  ## Error Handling
361
444
 
362
445
  The SDK maps HTTP status codes to typed exceptions. All typed exceptions inherit from
@@ -410,7 +493,8 @@ member.displayName // "Mike B."
410
493
  member.firstName / lastName
411
494
  member.gender // 'MALE' | 'FEMALE' | 'OTHER' | 'UNKNOWN' | null
412
495
  member.age / city / state / zip / country
413
- member.status.isVairified // grouped status flags
496
+ member.status.isWheelchair // global status flags
497
+ member.status.isAmbassador
414
498
  member.status.isConnected
415
499
  member.sport // MemberSportMap
416
500
  member.sports // readonly string[] of sport codes
@@ -418,12 +502,19 @@ member.ratingFor('pickleball') // number | null
418
502
  member.split('overall-open') // RatingSplitWire | null
419
503
  ```
420
504
 
505
+ VAIRification & VAIR-Pro status are **per-sport** — read them off each
506
+ `member.sport` entry, not `member.status` (see `SportRating` below).
507
+
421
508
  ### `SportRating` (dict-like)
422
509
 
423
510
  ```ts
424
511
  const pb = member.sport.get('pickleball');
425
512
  pb?.rating // Primary rating for this sport
426
513
  pb?.abbr // "VO", "VG", etc.
514
+ pb?.isVairified // per-sport VAIRified flag (Vairified#783)
515
+ pb?.isRater // per-sport rater flag
516
+ pb?.isVairPro // per-sport VAIR-Pro flag
517
+ pb?.isVairProStatus // 'PENDING' | 'ACTIVE' | null
427
518
  pb?.get('overall-open') // Any split key
428
519
  pb?.size // Number of splits
429
520
  pb?.has('singles-40+') // Membership check
@@ -432,6 +523,16 @@ for (const [key, split] of pb ?? []) { /* iterate */ }
432
523
 
433
524
  ## Migrating
434
525
 
526
+ **From 0.3.x → 0.4.0 (breaking):** VAIRification & VAIR-Pro status are now
527
+ **per-sport** (Vairified#783). `isVairified`, `isRater`, `isVairPro`, and
528
+ `isVairProStatus` moved off the member `status` object onto each per-sport
529
+ entry — read them via `member.sport.get(code)?.isVairified` instead of
530
+ `member.status.isVairified`. The `status` object keeps only the genuinely
531
+ global flags (`isWheelchair`, `isAmbassador`, `isConnected`). This mirrors
532
+ the backend: a player can be VAIRified / a VAIR Pro in one sport but not
533
+ another. Publish only after the backend #788 reaches production — against
534
+ the old prod shape the per-sport flags default to `false`/`null`.
535
+
435
536
  **From 0.2.x → 0.3.0:** All OAuth scope strings gained a `user:` prefix
436
537
  (`profile:read` → `user:profile:read`). Update any hardcoded scope arrays.
437
538
  New: `members.getBulk()`, `matches.tournamentImport()`, `webhooks.deliveries()`.