@youversion/platform-core 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.env.example +7 -0
  2. package/.env.local +10 -0
  3. package/.turbo/turbo-build.log +18 -0
  4. package/CHANGELOG.md +7 -0
  5. package/LICENSE +201 -0
  6. package/README.md +369 -0
  7. package/dist/index.cjs +1330 -0
  8. package/dist/index.d.cts +737 -0
  9. package/dist/index.d.ts +737 -0
  10. package/dist/index.js +1286 -0
  11. package/package.json +46 -0
  12. package/src/AuthenticationStrategy.ts +78 -0
  13. package/src/SignInWithYouVersionResult.ts +53 -0
  14. package/src/StorageStrategy.ts +81 -0
  15. package/src/URLBuilder.ts +71 -0
  16. package/src/Users.ts +137 -0
  17. package/src/WebAuthenticationStrategy.ts +127 -0
  18. package/src/YouVersionAPI.ts +27 -0
  19. package/src/YouVersionPlatformConfiguration.ts +80 -0
  20. package/src/YouVersionUserInfo.ts +49 -0
  21. package/src/__tests__/StorageStrategy.test.ts +404 -0
  22. package/src/__tests__/URLBuilder.test.ts +289 -0
  23. package/src/__tests__/YouVersionPlatformConfiguration.test.ts +150 -0
  24. package/src/__tests__/authentication.test.ts +174 -0
  25. package/src/__tests__/bible.test.ts +356 -0
  26. package/src/__tests__/client.test.ts +109 -0
  27. package/src/__tests__/handlers.ts +41 -0
  28. package/src/__tests__/highlights.test.ts +485 -0
  29. package/src/__tests__/languages.test.ts +139 -0
  30. package/src/__tests__/setup.ts +17 -0
  31. package/src/authentication.ts +27 -0
  32. package/src/bible.ts +272 -0
  33. package/src/client.ts +162 -0
  34. package/src/highlight.ts +16 -0
  35. package/src/highlights.ts +173 -0
  36. package/src/index.ts +20 -0
  37. package/src/languages.ts +80 -0
  38. package/src/schemas/bible-index.ts +48 -0
  39. package/src/schemas/book.ts +34 -0
  40. package/src/schemas/chapter.ts +24 -0
  41. package/src/schemas/collection.ts +28 -0
  42. package/src/schemas/highlight.ts +23 -0
  43. package/src/schemas/index.ts +11 -0
  44. package/src/schemas/language.ts +38 -0
  45. package/src/schemas/passage.ts +14 -0
  46. package/src/schemas/user.ts +10 -0
  47. package/src/schemas/verse.ts +17 -0
  48. package/src/schemas/version.ts +31 -0
  49. package/src/schemas/votd.ts +10 -0
  50. package/src/types/api-config.ts +9 -0
  51. package/src/types/auth.ts +15 -0
  52. package/src/types/book.ts +116 -0
  53. package/src/types/chapter.ts +5 -0
  54. package/src/types/highlight.ts +9 -0
  55. package/src/types/index.ts +22 -0
  56. package/src/utils/constants.ts +219 -0
  57. package/tsconfig.build.json +11 -0
  58. package/tsconfig.json +12 -0
  59. package/vitest.config.ts +9 -0
package/dist/index.js ADDED
@@ -0,0 +1,1286 @@
1
+ // src/client.ts
2
+ var ApiClient = class {
3
+ baseURL;
4
+ timeout;
5
+ defaultHeaders;
6
+ config;
7
+ /**
8
+ * Creates an instance of ApiClient.
9
+ *
10
+ * @param config - The API configuration object containing baseUrl, timeout, and appId.
11
+ */
12
+ constructor(config) {
13
+ this.config = {
14
+ version: config.version || "v1",
15
+ ...config
16
+ };
17
+ this.baseURL = config.baseUrl || "https://api-dev.youversion.com";
18
+ this.timeout = config.timeout || 1e4;
19
+ this.defaultHeaders = {
20
+ "Content-Type": "application/json",
21
+ "X-YVP-App-Key": this.config.appId,
22
+ "X-YVP-Installation-Id": this.config.installationId || "web-sdk-default"
23
+ };
24
+ }
25
+ /**
26
+ * Builds the query string from parameters
27
+ */
28
+ buildQueryString(params) {
29
+ if (!params) return "";
30
+ const queryString = new URLSearchParams(
31
+ Object.entries(params).map(([key, value]) => [key, String(value)])
32
+ ).toString();
33
+ return queryString ? `?${queryString}` : "";
34
+ }
35
+ /**
36
+ * Makes an HTTP request with timeout support
37
+ */
38
+ async request(url, options = {}) {
39
+ const controller = new AbortController();
40
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
41
+ try {
42
+ const response = await fetch(url, {
43
+ ...options,
44
+ signal: controller.signal,
45
+ headers: {
46
+ ...this.defaultHeaders,
47
+ ...options.headers
48
+ }
49
+ });
50
+ clearTimeout(timeoutId);
51
+ if (!response.ok) {
52
+ const isDevelopment = process.env.NODE_ENV === "development";
53
+ let errorMessage = `HTTP error! status: ${response.status}`;
54
+ try {
55
+ const errorBody = await response.text();
56
+ if (errorBody) {
57
+ try {
58
+ const errorJson = JSON.parse(errorBody);
59
+ const detailedMessage = errorJson.message || errorJson.error || errorBody;
60
+ if (isDevelopment) {
61
+ errorMessage = detailedMessage;
62
+ } else {
63
+ errorMessage = `Request failed with status ${response.status}`;
64
+ }
65
+ } catch {
66
+ if (isDevelopment) {
67
+ errorMessage = errorBody;
68
+ }
69
+ }
70
+ }
71
+ } catch (error2) {
72
+ if (isDevelopment && error2 instanceof Error) {
73
+ console.error("Failed to read error response:", error2.message);
74
+ }
75
+ }
76
+ const error = Object.assign(new Error(errorMessage), {
77
+ status: response.status,
78
+ statusText: response.statusText
79
+ });
80
+ throw error;
81
+ }
82
+ const contentType = response.headers.get("content-type");
83
+ if (contentType?.includes("application/json")) {
84
+ const data = await response.json();
85
+ return data;
86
+ } else {
87
+ const text = await response.text();
88
+ return text;
89
+ }
90
+ } catch (error) {
91
+ clearTimeout(timeoutId);
92
+ if (error instanceof Error && error.name === "AbortError") {
93
+ throw new Error(`Request timeout after ${this.timeout}ms`);
94
+ }
95
+ throw error;
96
+ }
97
+ }
98
+ /**
99
+ * Sends a GET request to the specified API path with optional query parameters.
100
+ *
101
+ * @typeParam T - The expected response type.
102
+ * @param path - The API endpoint path (relative to baseURL).
103
+ * @param params - Optional query parameters to include in the request.
104
+ * @returns A promise resolving to the response data of type T.
105
+ */
106
+ async get(path, params, headers) {
107
+ const url = `${this.baseURL}${path}${this.buildQueryString(params)}`;
108
+ return this.request(url, {
109
+ method: "GET",
110
+ headers
111
+ });
112
+ }
113
+ /**
114
+ * Sends a POST request to the specified API path with optional data and query parameters.
115
+ *
116
+ * @typeParam T - The expected response type.
117
+ * @param path - The API endpoint path (relative to baseURL).
118
+ * @param data - Optional request body data to send.
119
+ * @param params - Optional query parameters to include in the request.
120
+ * @returns A promise resolving to the response data of type T.
121
+ */
122
+ async post(path, data, params) {
123
+ const url = `${this.baseURL}${path}${this.buildQueryString(params)}`;
124
+ return this.request(url, {
125
+ method: "POST",
126
+ body: data ? JSON.stringify(data) : void 0
127
+ });
128
+ }
129
+ /**
130
+ * Sends a DELETE request to the specified API path with optional query parameters.
131
+ *
132
+ * @typeParam T - The expected response type.
133
+ * @param path - The API endpoint path (relative to baseURL).
134
+ * @param params - Optional query parameters to include in the request.
135
+ * @returns A promise resolving to the response data of type T (may be empty for 204 responses).
136
+ */
137
+ async delete(path, params) {
138
+ const url = `${this.baseURL}${path}${this.buildQueryString(params)}`;
139
+ return this.request(url, {
140
+ method: "DELETE"
141
+ });
142
+ }
143
+ };
144
+
145
+ // src/bible.ts
146
+ import { z } from "zod";
147
+ var BibleClient = class {
148
+ client;
149
+ // Validation schemas
150
+ versionIdSchema = z.number().int().positive("Version ID must be a positive integer");
151
+ bookSchema = z.string().trim().min(3, "Book ID must be exactly 3 characters").max(3, "Book ID must be exactly 3 characters");
152
+ chapterSchema = z.number().int("Chapter must be an integer").positive("Chapter must be a positive integer");
153
+ verseSchema = z.number().int("Verse must be an integer").positive("Verse must be a positive integer");
154
+ languageRangesSchema = z.string().trim().min(1, "Language ranges must be a non-empty string");
155
+ booleanSchema = z.boolean();
156
+ /**
157
+ * Creates a new BibleClient instance.
158
+ * @param client The API client to use for requests.
159
+ */
160
+ constructor(client) {
161
+ this.client = client;
162
+ }
163
+ get rootPath() {
164
+ return `/${this.client.config.version}`;
165
+ }
166
+ /**
167
+ * Fetches a collection of Bible versions filtered by language ranges.
168
+ *
169
+ * @param language_ranges - A comma-separated list of language codes or ranges to filter the versions (required).
170
+ * @param license_id - Optional license ID to filter versions by license.
171
+ * @returns A promise that resolves to a collection of BibleVersion objects.
172
+ */
173
+ async getVersions(language_ranges, license_id) {
174
+ this.languageRangesSchema.parse(language_ranges);
175
+ const params = {
176
+ "language_ranges[]": language_ranges
177
+ };
178
+ if (license_id !== void 0) {
179
+ params.license_id = license_id;
180
+ }
181
+ return this.client.get(`${this.rootPath}/bibles`, params);
182
+ }
183
+ /**
184
+ * Fetches a Bible version by its ID.
185
+ * @param id The version ID.
186
+ * @returns The requested BibleVersion object.
187
+ */
188
+ async getVersion(id) {
189
+ this.versionIdSchema.parse(id);
190
+ return this.client.get(`${this.rootPath}/bibles/${id}`);
191
+ }
192
+ /**
193
+ * Fetches all books for a given Bible version.
194
+ * @param versionId The version ID.
195
+ * @param canon Optional canon filter ('ot', 'nt', 'deuterocanon').
196
+ * @returns An array of BibleBook objects.
197
+ */
198
+ async getBooks(versionId, canon) {
199
+ this.versionIdSchema.parse(versionId);
200
+ return this.client.get(`${this.rootPath}/bibles/${versionId}/books`, {
201
+ ...canon && { canon }
202
+ });
203
+ }
204
+ /**
205
+ * Fetches a specific book by USFM code for a given version.
206
+ * @param versionId The version ID.
207
+ * @param book The Book Identifier code of the book.
208
+ * @returns The requested BibleBook object.
209
+ */
210
+ async getBook(versionId, book) {
211
+ this.versionIdSchema.parse(versionId);
212
+ this.bookSchema.parse(book);
213
+ return this.client.get(`${this.rootPath}/bibles/${versionId}/books/${book}`);
214
+ }
215
+ /**
216
+ * Fetches all chapters for a specific book in a version.
217
+ * @param versionId The version ID.
218
+ * @param book The Book Identifier code of the book.
219
+ * @returns An array of BibleChapter objects.
220
+ */
221
+ async getChapters(versionId, book) {
222
+ this.versionIdSchema.parse(versionId);
223
+ this.bookSchema.parse(book);
224
+ return this.client.get(
225
+ `${this.rootPath}/bibles/${versionId}/books/${book}/chapters`
226
+ );
227
+ }
228
+ /**
229
+ * Fetches a specific chapter for a book in a version.
230
+ * @param versionId The version ID.
231
+ * @param book The Book Identifier code of the book (3 characters, e.g., "MAT").
232
+ * @param chapter The chapter number
233
+ * @returns The requested BibleChapter object.
234
+ */
235
+ async getChapter(versionId, book, chapter) {
236
+ this.versionIdSchema.parse(versionId);
237
+ this.bookSchema.parse(book);
238
+ this.chapterSchema.parse(chapter);
239
+ return this.client.get(
240
+ `${this.rootPath}/bibles/${versionId}/books/${book}/chapters/${chapter}`
241
+ );
242
+ }
243
+ /**
244
+ * Fetches all verses for a specific chapter in a book and version.
245
+ * @param versionId The version ID.
246
+ * @param book The Book Identifier code of the book (3 characters, e.g., "MAT").
247
+ * @param chapter The chapter number.
248
+ * @returns An array of BibleVerse objects.
249
+ */
250
+ async getVerses(versionId, book, chapter) {
251
+ this.versionIdSchema.parse(versionId);
252
+ this.bookSchema.parse(book);
253
+ this.chapterSchema.parse(chapter);
254
+ return this.client.get(
255
+ `${this.rootPath}/bibles/${versionId}/books/${book}/chapters/${chapter}/verses`
256
+ );
257
+ }
258
+ /**
259
+ * Fetches a specific verse from a chapter, book, and version.
260
+ * @param versionId The version ID.
261
+ * @param book The Book Identifier code of the book (3 characters, e.g., "MAT").
262
+ * @param chapter The chapter number.
263
+ * @param verse The verse number.
264
+ * @returns The requested BibleVerse object.
265
+ */
266
+ async getVerse(versionId, book, chapter, verse) {
267
+ this.versionIdSchema.parse(versionId);
268
+ this.bookSchema.parse(book);
269
+ this.chapterSchema.parse(chapter);
270
+ this.verseSchema.parse(verse);
271
+ return this.client.get(
272
+ `${this.rootPath}/bibles/${versionId}/books/${book}/chapters/${chapter}/verses/${verse}`
273
+ );
274
+ }
275
+ /**
276
+ * Fetches a passage (range of verses) from the Bible using the passages endpoint.
277
+ * This is the new API format that returns HTML-formatted content.
278
+ * @param versionId The version ID.
279
+ * @param usfm The USFM reference (e.g., "JHN.3.1-2", "GEN.1", "JHN.3.16").
280
+ * @param format The format to return ("html" or "text", default: "html").
281
+ * @param include_headings Whether to include headings in the content.
282
+ * @param include_notes Whether to include notes in the content.
283
+ * @returns The requested BiblePassage object with HTML content.
284
+ * @example
285
+ * ```ts
286
+ * // Get a single verse
287
+ * const verse = await bibleClient.getPassage(111, "JHN.3.16");
288
+ *
289
+ * // Get a range of verses
290
+ * const verses = await bibleClient.getPassage(111, "JHN.3.1-5");
291
+ *
292
+ * // Get an entire chapter
293
+ * const chapter = await bibleClient.getPassage(111, "GEN.1");
294
+ * ```
295
+ */
296
+ async getPassage(versionId, usfm, format = "html", include_headings, include_notes) {
297
+ this.versionIdSchema.parse(versionId);
298
+ if (include_headings !== void 0) {
299
+ this.booleanSchema.parse(include_headings);
300
+ }
301
+ if (include_notes !== void 0) {
302
+ this.booleanSchema.parse(include_notes);
303
+ }
304
+ const params = {
305
+ format
306
+ };
307
+ if (include_headings !== void 0) {
308
+ params.include_headings = include_headings;
309
+ }
310
+ if (include_notes !== void 0) {
311
+ params.include_notes = include_notes;
312
+ }
313
+ return this.client.get(
314
+ `${this.rootPath}/bibles/${versionId}/passages/${usfm}`,
315
+ params
316
+ );
317
+ }
318
+ /**
319
+ * Fetches the indexing structure for a Bible version.
320
+ * @param versionId The version ID.
321
+ * @returns The BibleIndex object containing full hierarchy of books, chapters, and verses.
322
+ */
323
+ async getIndex(versionId) {
324
+ this.versionIdSchema.parse(versionId);
325
+ return this.client.get(`${this.rootPath}/bibles/${versionId}/index`);
326
+ }
327
+ /**
328
+ * Fetches the verse of the day calendar for an entire year.
329
+ * @returns A collection of VOTD objects for all days of the year.
330
+ */
331
+ async getAllVOTDs() {
332
+ return this.client.get(`${this.rootPath}/verse_of_the_days`);
333
+ }
334
+ /**
335
+ * Fetches the passage_id for the Verse Of The Day.
336
+ * @param day The day of the year (1-366).
337
+ * @returns The day of the year and the passage_id for that day of the year
338
+ * @example
339
+ * ```ts
340
+ * // Get the passageId for the verse of the first day of the year.
341
+ * const passageId = await bibleClient.getVOTD(1);
342
+ *
343
+ * // Get the passageId for the verse of the 100th day of the year.
344
+ * const passageId = await bibleClient.getVOTD(100);
345
+ * ```
346
+ */
347
+ async getVOTD(day) {
348
+ const daySchema = z.number().int().min(1).max(366);
349
+ daySchema.parse(day);
350
+ return this.client.get(`${this.rootPath}/verse_of_the_days/${day}`);
351
+ }
352
+ };
353
+
354
+ // src/languages.ts
355
+ import { z as z2 } from "zod";
356
+ var LanguagesClient = class {
357
+ client;
358
+ languageIdSchema = z2.string().trim().min(1, "Language ID must be a non-empty string").regex(
359
+ /^[a-z]{2,3}(?:-[A-Z][a-z]{3})?$/,
360
+ "Language ID must match BCP 47 format (language or language+script)"
361
+ );
362
+ countrySchema = z2.string().trim().length(2, "Country code must be a 2-character ISO 3166-1 alpha-2 code").toUpperCase();
363
+ /**
364
+ * Creates a new LanguagesClient instance.
365
+ * @param client The API client to use for requests.
366
+ */
367
+ constructor(client) {
368
+ this.client = client;
369
+ }
370
+ get rootPath() {
371
+ return `/${this.client.config.version}`;
372
+ }
373
+ /**
374
+ * Fetches a collection of languages supported in the Platform.
375
+ * @param options Query parameters for pagination and filtering (country is required).
376
+ * @returns A collection of Language objects.
377
+ */
378
+ async getLanguages(options) {
379
+ const params = {};
380
+ const country = this.countrySchema.parse(options.country);
381
+ params.country = country;
382
+ if (options.page_size !== void 0) {
383
+ const pageSizeSchema = z2.number().int().positive();
384
+ pageSizeSchema.parse(options.page_size);
385
+ params.page_size = options.page_size;
386
+ }
387
+ if (options.page_token !== void 0) {
388
+ params.page_token = options.page_token;
389
+ }
390
+ return this.client.get(`${this.rootPath}/languages`, params);
391
+ }
392
+ /**
393
+ * Fetches details about a specific language in the Platform.
394
+ * @param languageId The BCP 47 language code (optionally including script, e.g., "en" or "sr-Latn").
395
+ * @returns The requested Language object.
396
+ */
397
+ async getLanguage(languageId) {
398
+ this.languageIdSchema.parse(languageId);
399
+ return this.client.get(`${this.rootPath}/languages/${languageId}`);
400
+ }
401
+ };
402
+
403
+ // src/highlights.ts
404
+ import { z as z3 } from "zod";
405
+
406
+ // src/YouVersionPlatformConfiguration.ts
407
+ var YouVersionPlatformConfiguration = class {
408
+ static _appId = null;
409
+ static _installationId = null;
410
+ static _accessToken = null;
411
+ static _apiHost = "api-dev.youversion.com";
412
+ static _isPreviewMode = false;
413
+ static _previewUserInfo = null;
414
+ static getOrSetInstallationId() {
415
+ if (typeof window === "undefined") {
416
+ return "";
417
+ }
418
+ const existingId = localStorage.getItem("x-yvp-installation-id");
419
+ if (existingId) {
420
+ return existingId;
421
+ }
422
+ const newId = crypto.randomUUID();
423
+ localStorage.setItem("x-yvp-installation-id", newId);
424
+ return newId;
425
+ }
426
+ static get appId() {
427
+ return this._appId;
428
+ }
429
+ static set appId(value) {
430
+ this._appId = value;
431
+ }
432
+ static get installationId() {
433
+ if (!this._installationId) {
434
+ this._installationId = this.getOrSetInstallationId();
435
+ }
436
+ return this._installationId;
437
+ }
438
+ static set installationId(value) {
439
+ this._installationId = value || this.getOrSetInstallationId();
440
+ }
441
+ static setAccessToken(token) {
442
+ if (token !== null && (typeof token !== "string" || token.trim().length === 0)) {
443
+ throw new Error("Access token must be a non-empty string or null");
444
+ }
445
+ this._accessToken = token;
446
+ }
447
+ static get accessToken() {
448
+ return this._accessToken;
449
+ }
450
+ static get apiHost() {
451
+ return this._apiHost;
452
+ }
453
+ static set apiHost(value) {
454
+ this._apiHost = value;
455
+ }
456
+ static get isPreviewMode() {
457
+ return this._isPreviewMode;
458
+ }
459
+ static set isPreviewMode(value) {
460
+ this._isPreviewMode = value;
461
+ }
462
+ static get previewUserInfo() {
463
+ return this._previewUserInfo;
464
+ }
465
+ static set previewUserInfo(value) {
466
+ this._previewUserInfo = value;
467
+ }
468
+ };
469
+
470
+ // src/highlights.ts
471
+ var HighlightsClient = class {
472
+ client;
473
+ versionIdSchema = z3.number().int().positive("Version ID must be a positive integer");
474
+ passageIdSchema = z3.string().trim().min(1, "Passage ID must be a non-empty string");
475
+ colorSchema = z3.string().regex(/^[0-9a-f]{6}$/i, "Color must be a 6-character hex string without #");
476
+ /**
477
+ * Creates a new HighlightsClient instance.
478
+ * @param client The API client to use for requests.
479
+ */
480
+ constructor(client) {
481
+ this.client = client;
482
+ }
483
+ get rootPath() {
484
+ return `/${this.client.config.version}`;
485
+ }
486
+ /**
487
+ * Gets the authentication token, either from the provided parameter or from the platform configuration.
488
+ * @param lat Optional explicit long access token. If not provided, retrieves from YouVersionPlatformConfiguration.
489
+ * @returns The authentication token.
490
+ * @throws Error if no token is available.
491
+ */
492
+ getAuthToken(lat) {
493
+ if (lat) {
494
+ return lat;
495
+ }
496
+ const token = YouVersionPlatformConfiguration.accessToken;
497
+ if (!token) {
498
+ throw new Error(
499
+ "Authentication required. Please provide a token or sign in before accessing highlights."
500
+ );
501
+ }
502
+ return token;
503
+ }
504
+ validateVersionId(value) {
505
+ try {
506
+ this.versionIdSchema.parse(value);
507
+ } catch (error) {
508
+ if (error instanceof z3.ZodError) {
509
+ throw new Error("Version ID must be a positive integer");
510
+ }
511
+ throw error;
512
+ }
513
+ }
514
+ validatePassageId(value) {
515
+ try {
516
+ this.passageIdSchema.parse(value);
517
+ } catch (error) {
518
+ if (error instanceof z3.ZodError) {
519
+ throw new Error("Passage ID must be a non-empty string");
520
+ }
521
+ throw error;
522
+ }
523
+ }
524
+ validateColor(value) {
525
+ try {
526
+ this.colorSchema.parse(value);
527
+ } catch (error) {
528
+ if (error instanceof z3.ZodError) {
529
+ throw new Error("Color must be a 6-character hex string without #");
530
+ }
531
+ throw error;
532
+ }
533
+ }
534
+ /**
535
+ * Fetches a collection of highlights for a user.
536
+ * The response will return a color per verse without ranges.
537
+ * Requires OAuth with read_highlights scope.
538
+ * @param options Query parameters for filtering highlights.
539
+ * @param lat Optional long access token. If not provided, retrieves from YouVersionPlatformConfiguration.
540
+ * @returns A collection of Highlight objects.
541
+ */
542
+ async getHighlights(options, lat) {
543
+ const token = this.getAuthToken(lat);
544
+ const params = {
545
+ lat: token
546
+ };
547
+ if (options?.version_id !== void 0) {
548
+ this.validateVersionId(options.version_id);
549
+ params.version_id = options.version_id;
550
+ }
551
+ if (options?.passage_id !== void 0) {
552
+ this.validatePassageId(options.passage_id);
553
+ params.passage_id = options.passage_id;
554
+ }
555
+ return this.client.get(`${this.rootPath}/highlights`, params);
556
+ }
557
+ /**
558
+ * Creates or updates a highlight on a passage.
559
+ * Verse ranges may be used in the passage_id attribute.
560
+ * Requires OAuth with write_highlights scope.
561
+ * @param data The highlight data to create or update.
562
+ * @param lat Optional long access token. If not provided, retrieves from YouVersionPlatformConfiguration.
563
+ * @returns The created or updated Highlight object.
564
+ */
565
+ async createHighlight(data, lat) {
566
+ this.validateVersionId(data.version_id);
567
+ this.validatePassageId(data.passage_id);
568
+ this.validateColor(data.color);
569
+ const token = this.getAuthToken(lat);
570
+ return this.client.post(`${this.rootPath}/highlights`, data, { lat: token });
571
+ }
572
+ /**
573
+ * Clears highlights for a passage.
574
+ * Requires OAuth with write_highlights scope.
575
+ * @param passageId The passage identifier (USFM format, e.g., "MAT.1.1" or "MAT.1.1-5").
576
+ * @param options Optional query parameters including bible_id.
577
+ * @param lat Optional long access token. If not provided, retrieves from YouVersionPlatformConfiguration.
578
+ * @returns Promise that resolves when highlights are deleted (204 response).
579
+ */
580
+ async deleteHighlight(passageId, options, lat) {
581
+ this.validatePassageId(passageId);
582
+ const token = this.getAuthToken(lat);
583
+ const params = {
584
+ lat: token
585
+ };
586
+ if (options?.version_id !== void 0) {
587
+ this.validateVersionId(options.version_id);
588
+ params.version_id = options.version_id;
589
+ }
590
+ await this.client.delete(`${this.rootPath}/highlights/${passageId}`, params);
591
+ }
592
+ };
593
+
594
+ // src/authentication.ts
595
+ var AuthClient = class {
596
+ client;
597
+ /**
598
+ * Creates an instance of AuthClient.
599
+ * @param client - The ApiClient instance to use for requests.
600
+ */
601
+ constructor(client) {
602
+ this.client = client;
603
+ }
604
+ /**
605
+ * Retrieves the current authenticated user.
606
+ *
607
+ * @param lat - The long access token (LAT) used for authentication.
608
+ * @returns A promise that resolves to the authenticated User.
609
+ */
610
+ async getUser(lat) {
611
+ return this.client.get(`/auth/me`, { lat });
612
+ }
613
+ };
614
+
615
+ // src/AuthenticationStrategy.ts
616
+ var AuthenticationStrategyRegistry = class {
617
+ static strategy = null;
618
+ /**
619
+ * Registers a platform-specific authentication strategy
620
+ *
621
+ * @param strategy - The authentication strategy to register
622
+ * @throws Error if strategy is null, undefined, or missing required methods
623
+ */
624
+ static register(strategy) {
625
+ if (!strategy) {
626
+ throw new Error("Authentication strategy cannot be null or undefined");
627
+ }
628
+ if (typeof strategy.authenticate !== "function") {
629
+ throw new Error("Authentication strategy must implement authenticate method");
630
+ }
631
+ this.strategy = strategy;
632
+ }
633
+ /**
634
+ * Gets the currently registered authentication strategy
635
+ *
636
+ * @returns The registered authentication strategy
637
+ * @throws Error if no strategy has been registered
638
+ */
639
+ static get() {
640
+ if (!this.strategy) {
641
+ throw new Error(
642
+ "No authentication strategy registered. Please register a platform-specific strategy using AuthenticationStrategyRegistry.register()"
643
+ );
644
+ }
645
+ return this.strategy;
646
+ }
647
+ /**
648
+ * Checks if a strategy is currently registered
649
+ *
650
+ * @returns true if a strategy is registered, false otherwise
651
+ */
652
+ static isRegistered() {
653
+ return this.strategy !== null;
654
+ }
655
+ /**
656
+ * Resets the registry by removing the current strategy
657
+ *
658
+ * This method is primarily intended for testing scenarios
659
+ * where you need to clean up between test cases.
660
+ */
661
+ static reset() {
662
+ this.strategy = null;
663
+ }
664
+ };
665
+
666
+ // src/StorageStrategy.ts
667
+ var SessionStorageStrategy = class {
668
+ setItem(key, value) {
669
+ if (typeof sessionStorage === "undefined") {
670
+ console.warn("SessionStorage is not available in this environment");
671
+ return;
672
+ }
673
+ sessionStorage.setItem(key, value);
674
+ }
675
+ getItem(key) {
676
+ if (typeof sessionStorage === "undefined") {
677
+ return null;
678
+ }
679
+ return sessionStorage.getItem(key);
680
+ }
681
+ removeItem(key) {
682
+ if (typeof sessionStorage === "undefined") {
683
+ return;
684
+ }
685
+ sessionStorage.removeItem(key);
686
+ }
687
+ clear() {
688
+ if (typeof sessionStorage === "undefined") {
689
+ return;
690
+ }
691
+ sessionStorage.clear();
692
+ }
693
+ };
694
+ var MemoryStorageStrategy = class {
695
+ store = /* @__PURE__ */ new Map();
696
+ setItem(key, value) {
697
+ this.store.set(key, value);
698
+ }
699
+ getItem(key) {
700
+ return this.store.get(key) ?? null;
701
+ }
702
+ removeItem(key) {
703
+ this.store.delete(key);
704
+ }
705
+ clear() {
706
+ this.store.clear();
707
+ }
708
+ };
709
+
710
+ // src/WebAuthenticationStrategy.ts
711
+ var WebAuthenticationStrategy = class _WebAuthenticationStrategy {
712
+ redirectUri;
713
+ callbackPath;
714
+ timeout;
715
+ storage;
716
+ static pendingAuthResolve = null;
717
+ static pendingAuthReject = null;
718
+ static timeoutId = null;
719
+ constructor(options) {
720
+ this.callbackPath = options?.callbackPath ?? "/auth/callback";
721
+ this.redirectUri = options?.redirectUri ?? window.location.origin + this.callbackPath;
722
+ this.timeout = options?.timeout ?? 3e5;
723
+ this.storage = options?.storage ?? new SessionStorageStrategy();
724
+ }
725
+ async authenticate(authUrl) {
726
+ authUrl.searchParams.set("redirect_uri", this.redirectUri);
727
+ return this.authenticateWithRedirect(authUrl);
728
+ }
729
+ /**
730
+ * Call this method when your app loads to handle the redirect callback
731
+ */
732
+ static handleCallback(callbackPath = "/auth/callback") {
733
+ const currentUrl = new URL(window.location.href);
734
+ if (currentUrl.pathname === callbackPath && currentUrl.searchParams.has("status")) {
735
+ const callbackUrl = new URL(currentUrl.toString());
736
+ if (_WebAuthenticationStrategy.pendingAuthResolve) {
737
+ _WebAuthenticationStrategy.pendingAuthResolve(callbackUrl);
738
+ _WebAuthenticationStrategy.cleanup();
739
+ } else {
740
+ const storageStrategy2 = new SessionStorageStrategy();
741
+ storageStrategy2.setItem("youversion-auth-callback", callbackUrl.toString());
742
+ }
743
+ const storageStrategy = new SessionStorageStrategy();
744
+ const returnUrl = storageStrategy.getItem("youversion-auth-return") ?? "/";
745
+ storageStrategy.removeItem("youversion-auth-return");
746
+ window.history.replaceState({}, "", returnUrl);
747
+ return true;
748
+ }
749
+ return false;
750
+ }
751
+ /**
752
+ * Clean up pending authentication state
753
+ */
754
+ static cleanup() {
755
+ if (_WebAuthenticationStrategy.timeoutId) {
756
+ clearTimeout(_WebAuthenticationStrategy.timeoutId);
757
+ _WebAuthenticationStrategy.timeoutId = null;
758
+ }
759
+ _WebAuthenticationStrategy.pendingAuthResolve = null;
760
+ _WebAuthenticationStrategy.pendingAuthReject = null;
761
+ }
762
+ /**
763
+ * Retrieve stored callback result if available
764
+ */
765
+ static getStoredCallback() {
766
+ const storageStrategy = new SessionStorageStrategy();
767
+ const stored = storageStrategy.getItem("youversion-auth-callback");
768
+ if (stored) {
769
+ storageStrategy.removeItem("youversion-auth-callback");
770
+ try {
771
+ return new URL(stored);
772
+ } catch {
773
+ return null;
774
+ }
775
+ }
776
+ return null;
777
+ }
778
+ authenticateWithRedirect(authUrl) {
779
+ _WebAuthenticationStrategy.cleanup();
780
+ this.storage.setItem("youversion-auth-return", window.location.href);
781
+ return new Promise((resolve, reject) => {
782
+ _WebAuthenticationStrategy.pendingAuthResolve = resolve;
783
+ _WebAuthenticationStrategy.pendingAuthReject = reject;
784
+ _WebAuthenticationStrategy.timeoutId = setTimeout(() => {
785
+ _WebAuthenticationStrategy.cleanup();
786
+ reject(new Error("Authentication timeout"));
787
+ }, this.timeout);
788
+ try {
789
+ window.location.href = authUrl.toString();
790
+ } catch (error) {
791
+ _WebAuthenticationStrategy.cleanup();
792
+ reject(
793
+ new Error(
794
+ `Failed to navigate to auth URL: ${error instanceof Error ? error.message : "Unknown error"}`
795
+ )
796
+ );
797
+ }
798
+ });
799
+ }
800
+ };
801
+
802
+ // src/SignInWithYouVersionResult.ts
803
+ var SignInWithYouVersionPermission = {
804
+ bibles: "bibles",
805
+ highlights: "highlights",
806
+ votd: "votd",
807
+ demographics: "demographics",
808
+ bibleActivity: "bible_activity"
809
+ };
810
+ var SignInWithYouVersionResult = class {
811
+ accessToken;
812
+ permissions;
813
+ errorMsg;
814
+ yvpUserId;
815
+ constructor(url) {
816
+ const queryParams = new URLSearchParams(url.search);
817
+ const status = queryParams.get("status");
818
+ const userId = queryParams.get("yvp_user_id");
819
+ const latValue = queryParams.get("lat");
820
+ const grants = queryParams.get("grants");
821
+ const perms = grants?.split(",").map((grant) => grant.trim()).filter(
822
+ (grant) => Object.values(SignInWithYouVersionPermission).includes(
823
+ grant
824
+ )
825
+ ).map((grant) => grant) ?? [];
826
+ if (status === "success" && latValue && userId) {
827
+ this.accessToken = latValue;
828
+ this.permissions = perms;
829
+ this.errorMsg = null;
830
+ this.yvpUserId = userId;
831
+ } else if (status === "canceled") {
832
+ this.accessToken = null;
833
+ this.permissions = [];
834
+ this.errorMsg = null;
835
+ this.yvpUserId = null;
836
+ } else {
837
+ this.accessToken = null;
838
+ this.permissions = [];
839
+ this.errorMsg = "Authentication failed";
840
+ this.yvpUserId = null;
841
+ }
842
+ }
843
+ };
844
+
845
+ // src/YouVersionUserInfo.ts
846
+ var YouVersionUserInfo = class {
847
+ firstName;
848
+ lastName;
849
+ userId;
850
+ avatarUrlFormat;
851
+ constructor(data) {
852
+ if (!data || typeof data !== "object") {
853
+ throw new Error("Invalid user data provided");
854
+ }
855
+ this.firstName = data.first_name;
856
+ this.lastName = data.last_name;
857
+ this.userId = data.id;
858
+ this.avatarUrlFormat = data.avatar_url;
859
+ }
860
+ getAvatarUrl(width = 200, height = 200) {
861
+ if (!this.avatarUrlFormat) {
862
+ return null;
863
+ }
864
+ let urlString = this.avatarUrlFormat;
865
+ if (urlString.startsWith("//")) {
866
+ urlString = "https:" + urlString;
867
+ }
868
+ urlString = urlString.replace("{width}", width.toString());
869
+ urlString = urlString.replace("{height}", height.toString());
870
+ try {
871
+ return new URL(urlString);
872
+ } catch {
873
+ return null;
874
+ }
875
+ }
876
+ get avatarUrl() {
877
+ return this.getAvatarUrl();
878
+ }
879
+ };
880
+
881
+ // src/YouVersionAPI.ts
882
+ var YouVersionAPI = class {
883
+ static addStandardHeaders(url) {
884
+ const headers = {
885
+ Accept: "application/json",
886
+ "Content-Type": "application/json"
887
+ };
888
+ const appId = YouVersionPlatformConfiguration.appId;
889
+ if (appId) {
890
+ headers["X-App-Id"] = appId;
891
+ }
892
+ const installationId = YouVersionPlatformConfiguration.installationId;
893
+ if (installationId) {
894
+ headers["x-yvp-installation-id"] = installationId;
895
+ }
896
+ const request = new Request(url.toString(), {
897
+ headers
898
+ });
899
+ return request;
900
+ }
901
+ };
902
+
903
+ // src/URLBuilder.ts
904
+ var URLBuilder = class {
905
+ static get baseURL() {
906
+ return new URL(`https://${YouVersionPlatformConfiguration.apiHost}`);
907
+ }
908
+ static authURL(appId, requiredPermissions = /* @__PURE__ */ new Set(), optionalPermissions = /* @__PURE__ */ new Set()) {
909
+ if (typeof appId !== "string" || appId.trim().length === 0) {
910
+ throw new Error("appId must be a non-empty string");
911
+ }
912
+ try {
913
+ const url = new URL(this.baseURL);
914
+ url.pathname = "/auth/login";
915
+ const searchParams = new URLSearchParams();
916
+ searchParams.append("app_id", appId);
917
+ searchParams.append("language", "en");
918
+ if (requiredPermissions.size > 0) {
919
+ const requiredList = Array.from(requiredPermissions).map((p) => p.toString());
920
+ searchParams.append("required_perms", requiredList.join(","));
921
+ }
922
+ if (optionalPermissions.size > 0) {
923
+ const optionalList = Array.from(optionalPermissions).map((p) => p.toString());
924
+ searchParams.append("opt_perms", optionalList.join(","));
925
+ }
926
+ const installationId = YouVersionPlatformConfiguration.installationId;
927
+ if (installationId) {
928
+ searchParams.append("x-yvp-installation-id", installationId);
929
+ }
930
+ url.search = searchParams.toString();
931
+ return url;
932
+ } catch (error) {
933
+ throw new Error(
934
+ `Failed to construct auth URL: ${error instanceof Error ? error.message : "Unknown error"}`
935
+ );
936
+ }
937
+ }
938
+ static userURL(accessToken) {
939
+ if (typeof accessToken !== "string" || accessToken.trim().length === 0) {
940
+ throw new Error("accessToken must be a non-empty string");
941
+ }
942
+ try {
943
+ const url = new URL(this.baseURL);
944
+ url.pathname = "/auth/me";
945
+ const searchParams = new URLSearchParams();
946
+ searchParams.append("lat", accessToken);
947
+ url.search = searchParams.toString();
948
+ return url;
949
+ } catch (error) {
950
+ throw new Error(
951
+ `Failed to construct user URL: ${error instanceof Error ? error.message : "Unknown error"}`
952
+ );
953
+ }
954
+ }
955
+ };
956
+
957
+ // src/Users.ts
958
+ var MAX_RETRY_ATTEMPTS = 3;
959
+ var RETRY_DELAY_MS = 1e3;
960
+ var YouVersionAPIUsers = class {
961
+ /**
962
+ * Presents the YouVersion login flow to the user and returns the login result upon completion.
963
+ *
964
+ * This function authenticates the user with YouVersion, requesting the specified required and optional permissions.
965
+ * The function returns a promise that resolves when the user completes or cancels the login flow,
966
+ * returning the login result containing the authorization code and granted permissions.
967
+ *
968
+ * @param requiredPermissions - The set of permissions that must be granted by the user for successful login.
969
+ * @param optionalPermissions - The set of permissions that will be requested from the user but are not required for successful login.
970
+ * @returns A Promise resolving to a SignInWithYouVersionResult containing the authorization code and granted permissions upon successful login.
971
+ * @throws An error if authentication fails or is cancelled by the user.
972
+ */
973
+ static async signIn(requiredPermissions, optionalPermissions) {
974
+ if (!requiredPermissions || !(requiredPermissions instanceof Set)) {
975
+ throw new Error("Invalid requiredPermissions: must be a Set");
976
+ }
977
+ if (!optionalPermissions || !(optionalPermissions instanceof Set)) {
978
+ throw new Error("Invalid optionalPermissions: must be a Set");
979
+ }
980
+ const appId = YouVersionPlatformConfiguration.appId;
981
+ if (!appId) {
982
+ throw new Error("YouVersionPlatformConfiguration.appId must be set before calling signIn");
983
+ }
984
+ const url = URLBuilder.authURL(appId, requiredPermissions, optionalPermissions);
985
+ const strategy = AuthenticationStrategyRegistry.get();
986
+ const callbackUrl = await strategy.authenticate(url);
987
+ const result = new SignInWithYouVersionResult(callbackUrl);
988
+ if (result.accessToken) {
989
+ YouVersionPlatformConfiguration.setAccessToken(result.accessToken);
990
+ }
991
+ return result;
992
+ }
993
+ static signOut() {
994
+ YouVersionPlatformConfiguration.setAccessToken(null);
995
+ }
996
+ /**
997
+ * Retrieves user information for the authenticated user using the provided access token.
998
+ *
999
+ * This function fetches the user's profile information from the YouVersion API, decoding it into a YouVersionUserInfo model.
1000
+ *
1001
+ * @param accessToken - The access token obtained from the login process.
1002
+ * @returns A Promise resolving to a YouVersionUserInfo object containing the user's profile information.
1003
+ * @throws An error if the URL is invalid, the network request fails, or the response cannot be decoded.
1004
+ */
1005
+ static async userInfo(accessToken) {
1006
+ if (!accessToken || typeof accessToken !== "string") {
1007
+ throw new Error("Invalid access token: must be a non-empty string");
1008
+ }
1009
+ if (YouVersionPlatformConfiguration.isPreviewMode && accessToken === "preview") {
1010
+ return YouVersionPlatformConfiguration.previewUserInfo || new YouVersionUserInfo({
1011
+ first_name: "Preview",
1012
+ last_name: "User",
1013
+ id: "preview-user",
1014
+ avatar_url: void 0
1015
+ });
1016
+ }
1017
+ const url = URLBuilder.userURL(accessToken);
1018
+ const request = YouVersionAPI.addStandardHeaders(url);
1019
+ let lastError = null;
1020
+ for (let attempt = 1; attempt <= MAX_RETRY_ATTEMPTS; attempt++) {
1021
+ try {
1022
+ const response = await fetch(request);
1023
+ if (response.status === 401) {
1024
+ throw new Error(
1025
+ "Authentication failed: Invalid or expired access token. Please sign in again."
1026
+ );
1027
+ }
1028
+ if (response.status === 403) {
1029
+ throw new Error("Access denied: Insufficient permissions to retrieve user information");
1030
+ }
1031
+ if (response.status !== 200) {
1032
+ throw new Error(
1033
+ `Failed to retrieve user information: Server responded with status ${response.status}`
1034
+ );
1035
+ }
1036
+ const data = await response.json();
1037
+ return data;
1038
+ } catch (error) {
1039
+ lastError = error instanceof Error ? error : new Error("Failed to parse server response");
1040
+ if (error instanceof Error && (error.message.includes("401") || error.message.includes("403"))) {
1041
+ throw error;
1042
+ }
1043
+ if (attempt < MAX_RETRY_ATTEMPTS) {
1044
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS * attempt));
1045
+ continue;
1046
+ }
1047
+ }
1048
+ }
1049
+ throw lastError || new Error("Failed to retrieve user information after multiple attempts");
1050
+ }
1051
+ };
1052
+
1053
+ // src/utils/constants.ts
1054
+ var BOOK_IDS = [
1055
+ "GEN",
1056
+ "EXO",
1057
+ "LEV",
1058
+ "NUM",
1059
+ "DEU",
1060
+ "JOS",
1061
+ "JDG",
1062
+ "RUT",
1063
+ "1SA",
1064
+ "2SA",
1065
+ "1KI",
1066
+ "2KI",
1067
+ "1CH",
1068
+ "2CH",
1069
+ "EZR",
1070
+ "NEH",
1071
+ "EST",
1072
+ "JOB",
1073
+ "PSA",
1074
+ "PRO",
1075
+ "ECC",
1076
+ "SNG",
1077
+ "ISA",
1078
+ "JER",
1079
+ "LAM",
1080
+ "EZK",
1081
+ "DAN",
1082
+ "HOS",
1083
+ "JOL",
1084
+ "AMO",
1085
+ "OBA",
1086
+ "JON",
1087
+ "MIC",
1088
+ "NAM",
1089
+ "HAB",
1090
+ "ZEP",
1091
+ "HAG",
1092
+ "ZEC",
1093
+ "MAL",
1094
+ "MAT",
1095
+ "MRK",
1096
+ "LUK",
1097
+ "JHN",
1098
+ "ACT",
1099
+ "ROM",
1100
+ "1CO",
1101
+ "2CO",
1102
+ "GAL",
1103
+ "EPH",
1104
+ "PHP",
1105
+ "COL",
1106
+ "1TH",
1107
+ "2TH",
1108
+ "1TI",
1109
+ "2TI",
1110
+ "TIT",
1111
+ "PHM",
1112
+ "HEB",
1113
+ "JAS",
1114
+ "1PE",
1115
+ "2PE",
1116
+ "1JN",
1117
+ "2JN",
1118
+ "3JN",
1119
+ "JUD",
1120
+ "REV",
1121
+ // dc (Apocrypha) books
1122
+ "TOB",
1123
+ "JDT",
1124
+ "ESG",
1125
+ "WIS",
1126
+ "SIR",
1127
+ "BAR",
1128
+ "LJE",
1129
+ "S3Y",
1130
+ "SUS",
1131
+ "BEL",
1132
+ "1MA",
1133
+ "2MA",
1134
+ "3MA",
1135
+ "4MA",
1136
+ "1ES",
1137
+ "2ES",
1138
+ "MAN",
1139
+ "PS2",
1140
+ "ODA",
1141
+ "PSS",
1142
+ "3ES",
1143
+ "EZA",
1144
+ "5EZ",
1145
+ "6EZ",
1146
+ "DAG",
1147
+ "PS3",
1148
+ "2BA",
1149
+ "LBA",
1150
+ "JUB",
1151
+ "ENO",
1152
+ "1MQ",
1153
+ "2MQ",
1154
+ "3MQ",
1155
+ "REP",
1156
+ "4BA",
1157
+ "LAO",
1158
+ // Luke-Acts combo, treated canonically as New Testament
1159
+ "LKA"
1160
+ ];
1161
+ var BOOK_CANON = {
1162
+ GEN: "ot",
1163
+ EXO: "ot",
1164
+ LEV: "ot",
1165
+ NUM: "ot",
1166
+ DEU: "ot",
1167
+ JOS: "ot",
1168
+ JDG: "ot",
1169
+ RUT: "ot",
1170
+ "1SA": "ot",
1171
+ "2SA": "ot",
1172
+ "1KI": "ot",
1173
+ "2KI": "ot",
1174
+ "1CH": "ot",
1175
+ "2CH": "ot",
1176
+ EZR: "ot",
1177
+ NEH: "ot",
1178
+ EST: "ot",
1179
+ JOB: "ot",
1180
+ PSA: "ot",
1181
+ PRO: "ot",
1182
+ ECC: "ot",
1183
+ SNG: "ot",
1184
+ ISA: "ot",
1185
+ JER: "ot",
1186
+ LAM: "ot",
1187
+ EZK: "ot",
1188
+ DAN: "ot",
1189
+ HOS: "ot",
1190
+ JOL: "ot",
1191
+ AMO: "ot",
1192
+ OBA: "ot",
1193
+ JON: "ot",
1194
+ MIC: "ot",
1195
+ NAM: "ot",
1196
+ HAB: "ot",
1197
+ ZEP: "ot",
1198
+ HAG: "ot",
1199
+ ZEC: "ot",
1200
+ MAL: "ot",
1201
+ MAT: "nt",
1202
+ MRK: "nt",
1203
+ LUK: "nt",
1204
+ JHN: "nt",
1205
+ ACT: "nt",
1206
+ ROM: "nt",
1207
+ "1CO": "nt",
1208
+ "2CO": "nt",
1209
+ GAL: "nt",
1210
+ EPH: "nt",
1211
+ PHP: "nt",
1212
+ COL: "nt",
1213
+ "1TH": "nt",
1214
+ "2TH": "nt",
1215
+ "1TI": "nt",
1216
+ "2TI": "nt",
1217
+ TIT: "nt",
1218
+ PHM: "nt",
1219
+ HEB: "nt",
1220
+ JAS: "nt",
1221
+ "1PE": "nt",
1222
+ "2PE": "nt",
1223
+ "1JN": "nt",
1224
+ "2JN": "nt",
1225
+ "3JN": "nt",
1226
+ JUD: "nt",
1227
+ REV: "nt",
1228
+ TOB: "dc",
1229
+ JDT: "dc",
1230
+ ESG: "dc",
1231
+ WIS: "dc",
1232
+ SIR: "dc",
1233
+ BAR: "dc",
1234
+ LJE: "dc",
1235
+ S3Y: "dc",
1236
+ SUS: "dc",
1237
+ BEL: "dc",
1238
+ "1MA": "dc",
1239
+ "2MA": "dc",
1240
+ "3MA": "dc",
1241
+ "4MA": "dc",
1242
+ "1ES": "dc",
1243
+ "2ES": "dc",
1244
+ MAN: "dc",
1245
+ PS2: "dc",
1246
+ ODA: "dc",
1247
+ PSS: "dc",
1248
+ "3ES": "dc",
1249
+ EZA: "dc",
1250
+ "5EZ": "dc",
1251
+ "6EZ": "dc",
1252
+ DAG: "dc",
1253
+ PS3: "dc",
1254
+ "2BA": "dc",
1255
+ LBA: "dc",
1256
+ JUB: "dc",
1257
+ ENO: "dc",
1258
+ "1MQ": "dc",
1259
+ "2MQ": "dc",
1260
+ "3MQ": "dc",
1261
+ REP: "dc",
1262
+ "4BA": "dc",
1263
+ LAO: "dc",
1264
+ LKA: "nt"
1265
+ // Luke-Acts combo, treated canonically as New Testament
1266
+ };
1267
+ export {
1268
+ ApiClient,
1269
+ AuthClient,
1270
+ AuthenticationStrategyRegistry,
1271
+ BOOK_CANON,
1272
+ BOOK_IDS,
1273
+ BibleClient,
1274
+ HighlightsClient,
1275
+ LanguagesClient,
1276
+ MemoryStorageStrategy,
1277
+ SessionStorageStrategy,
1278
+ SignInWithYouVersionPermission,
1279
+ SignInWithYouVersionResult,
1280
+ URLBuilder,
1281
+ WebAuthenticationStrategy,
1282
+ YouVersionAPI,
1283
+ YouVersionAPIUsers,
1284
+ YouVersionPlatformConfiguration,
1285
+ YouVersionUserInfo
1286
+ };