@youversion/platform-core 0.4.1 → 0.4.3
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/.env.example +3 -5
- package/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +12 -0
- package/README.md +62 -322
- package/dist/index.cjs +44 -51
- package/dist/index.d.cts +7 -12
- package/dist/index.d.ts +7 -12
- package/dist/index.js +44 -51
- package/package.json +1 -1
- package/src/URLBuilder.ts +4 -4
- package/src/Users.ts +4 -4
- package/src/YouVersionAPI.ts +4 -6
- package/src/YouVersionPlatformConfiguration.ts +6 -6
- package/src/__tests__/URLBuilder.test.ts +34 -30
- package/src/__tests__/YouVersionPlatformConfiguration.test.ts +22 -13
- package/src/__tests__/authentication.test.ts +1 -3
- package/src/__tests__/bible.test.ts +1 -3
- package/src/__tests__/client.test.ts +12 -16
- package/src/__tests__/handlers.ts +7 -4
- package/src/__tests__/highlights.test.ts +1 -3
- package/src/__tests__/languages.test.ts +1 -3
- package/src/bible.ts +12 -19
- package/src/client.ts +9 -4
- package/src/highlights.ts +3 -7
- package/src/languages.ts +2 -6
- package/src/types/api-config.ts +2 -4
- package/.env.local +0 -10
package/dist/index.js
CHANGED
|
@@ -7,18 +7,23 @@ var ApiClient = class {
|
|
|
7
7
|
/**
|
|
8
8
|
* Creates an instance of ApiClient.
|
|
9
9
|
*
|
|
10
|
-
* @param config - The API configuration object containing baseUrl, timeout, and
|
|
10
|
+
* @param config - The API configuration object containing baseUrl, timeout, and appKey.
|
|
11
11
|
*/
|
|
12
12
|
constructor(config) {
|
|
13
13
|
this.config = {
|
|
14
|
-
version: config.version || "v1",
|
|
15
14
|
...config
|
|
16
15
|
};
|
|
17
|
-
|
|
16
|
+
const apiHost = config.apiHost ?? process.env.YVP_API_HOST ?? "api.youversion.com";
|
|
17
|
+
if (!apiHost) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
"ApiClient requires a host name. Provide an apiHost in the config or set the YVP_API_HOST environment variable."
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
this.baseURL = "https://" + apiHost;
|
|
18
23
|
this.timeout = config.timeout || 1e4;
|
|
19
24
|
this.defaultHeaders = {
|
|
20
25
|
"Content-Type": "application/json",
|
|
21
|
-
"X-YVP-App-Key": this.config.
|
|
26
|
+
"X-YVP-App-Key": this.config.appKey,
|
|
22
27
|
"X-YVP-Installation-Id": this.config.installationId || "web-sdk-default"
|
|
23
28
|
};
|
|
24
29
|
}
|
|
@@ -160,9 +165,6 @@ var BibleClient = class {
|
|
|
160
165
|
constructor(client) {
|
|
161
166
|
this.client = client;
|
|
162
167
|
}
|
|
163
|
-
get rootPath() {
|
|
164
|
-
return `/${this.client.config.version}`;
|
|
165
|
-
}
|
|
166
168
|
/**
|
|
167
169
|
* Fetches a collection of Bible versions filtered by language ranges.
|
|
168
170
|
*
|
|
@@ -178,7 +180,7 @@ var BibleClient = class {
|
|
|
178
180
|
if (license_id !== void 0) {
|
|
179
181
|
params.license_id = license_id;
|
|
180
182
|
}
|
|
181
|
-
return this.client.get(
|
|
183
|
+
return this.client.get(`/v1/bibles`, params);
|
|
182
184
|
}
|
|
183
185
|
/**
|
|
184
186
|
* Fetches a Bible version by its ID.
|
|
@@ -187,7 +189,7 @@ var BibleClient = class {
|
|
|
187
189
|
*/
|
|
188
190
|
async getVersion(id) {
|
|
189
191
|
this.versionIdSchema.parse(id);
|
|
190
|
-
return this.client.get(
|
|
192
|
+
return this.client.get(`/v1/bibles/${id}`);
|
|
191
193
|
}
|
|
192
194
|
/**
|
|
193
195
|
* Fetches all books for a given Bible version.
|
|
@@ -197,7 +199,7 @@ var BibleClient = class {
|
|
|
197
199
|
*/
|
|
198
200
|
async getBooks(versionId, canon) {
|
|
199
201
|
this.versionIdSchema.parse(versionId);
|
|
200
|
-
return this.client.get(
|
|
202
|
+
return this.client.get(`/v1/bibles/${versionId}/books`, {
|
|
201
203
|
...canon && { canon }
|
|
202
204
|
});
|
|
203
205
|
}
|
|
@@ -210,7 +212,7 @@ var BibleClient = class {
|
|
|
210
212
|
async getBook(versionId, book) {
|
|
211
213
|
this.versionIdSchema.parse(versionId);
|
|
212
214
|
this.bookSchema.parse(book);
|
|
213
|
-
return this.client.get(
|
|
215
|
+
return this.client.get(`/v1/bibles/${versionId}/books/${book}`);
|
|
214
216
|
}
|
|
215
217
|
/**
|
|
216
218
|
* Fetches all chapters for a specific book in a version.
|
|
@@ -222,7 +224,7 @@ var BibleClient = class {
|
|
|
222
224
|
this.versionIdSchema.parse(versionId);
|
|
223
225
|
this.bookSchema.parse(book);
|
|
224
226
|
return this.client.get(
|
|
225
|
-
|
|
227
|
+
`/v1/bibles/${versionId}/books/${book}/chapters`
|
|
226
228
|
);
|
|
227
229
|
}
|
|
228
230
|
/**
|
|
@@ -237,7 +239,7 @@ var BibleClient = class {
|
|
|
237
239
|
this.bookSchema.parse(book);
|
|
238
240
|
this.chapterSchema.parse(chapter);
|
|
239
241
|
return this.client.get(
|
|
240
|
-
|
|
242
|
+
`/v1/bibles/${versionId}/books/${book}/chapters/${chapter}`
|
|
241
243
|
);
|
|
242
244
|
}
|
|
243
245
|
/**
|
|
@@ -252,7 +254,7 @@ var BibleClient = class {
|
|
|
252
254
|
this.bookSchema.parse(book);
|
|
253
255
|
this.chapterSchema.parse(chapter);
|
|
254
256
|
return this.client.get(
|
|
255
|
-
|
|
257
|
+
`/v1/bibles/${versionId}/books/${book}/chapters/${chapter}/verses`
|
|
256
258
|
);
|
|
257
259
|
}
|
|
258
260
|
/**
|
|
@@ -269,7 +271,7 @@ var BibleClient = class {
|
|
|
269
271
|
this.chapterSchema.parse(chapter);
|
|
270
272
|
this.verseSchema.parse(verse);
|
|
271
273
|
return this.client.get(
|
|
272
|
-
|
|
274
|
+
`/v1/bibles/${versionId}/books/${book}/chapters/${chapter}/verses/${verse}`
|
|
273
275
|
);
|
|
274
276
|
}
|
|
275
277
|
/**
|
|
@@ -310,10 +312,7 @@ var BibleClient = class {
|
|
|
310
312
|
if (include_notes !== void 0) {
|
|
311
313
|
params.include_notes = include_notes;
|
|
312
314
|
}
|
|
313
|
-
return this.client.get(
|
|
314
|
-
`${this.rootPath}/bibles/${versionId}/passages/${usfm}`,
|
|
315
|
-
params
|
|
316
|
-
);
|
|
315
|
+
return this.client.get(`/v1/bibles/${versionId}/passages/${usfm}`, params);
|
|
317
316
|
}
|
|
318
317
|
/**
|
|
319
318
|
* Fetches the indexing structure for a Bible version.
|
|
@@ -322,14 +321,14 @@ var BibleClient = class {
|
|
|
322
321
|
*/
|
|
323
322
|
async getIndex(versionId) {
|
|
324
323
|
this.versionIdSchema.parse(versionId);
|
|
325
|
-
return this.client.get(
|
|
324
|
+
return this.client.get(`/v1/bibles/${versionId}/index`);
|
|
326
325
|
}
|
|
327
326
|
/**
|
|
328
327
|
* Fetches the verse of the day calendar for an entire year.
|
|
329
328
|
* @returns A collection of VOTD objects for all days of the year.
|
|
330
329
|
*/
|
|
331
330
|
async getAllVOTDs() {
|
|
332
|
-
return this.client.get(
|
|
331
|
+
return this.client.get(`/v1/verse_of_the_days`);
|
|
333
332
|
}
|
|
334
333
|
/**
|
|
335
334
|
* Fetches the passage_id for the Verse Of The Day.
|
|
@@ -347,7 +346,7 @@ var BibleClient = class {
|
|
|
347
346
|
async getVOTD(day) {
|
|
348
347
|
const daySchema = z.number().int().min(1).max(366);
|
|
349
348
|
daySchema.parse(day);
|
|
350
|
-
return this.client.get(
|
|
349
|
+
return this.client.get(`/v1/verse_of_the_days/${day}`);
|
|
351
350
|
}
|
|
352
351
|
};
|
|
353
352
|
|
|
@@ -367,9 +366,6 @@ var LanguagesClient = class {
|
|
|
367
366
|
constructor(client) {
|
|
368
367
|
this.client = client;
|
|
369
368
|
}
|
|
370
|
-
get rootPath() {
|
|
371
|
-
return `/${this.client.config.version}`;
|
|
372
|
-
}
|
|
373
369
|
/**
|
|
374
370
|
* Fetches a collection of languages supported in the Platform.
|
|
375
371
|
* @param options Query parameters for pagination and filtering (country is required).
|
|
@@ -387,7 +383,7 @@ var LanguagesClient = class {
|
|
|
387
383
|
if (options.page_token !== void 0) {
|
|
388
384
|
params.page_token = options.page_token;
|
|
389
385
|
}
|
|
390
|
-
return this.client.get(
|
|
386
|
+
return this.client.get(`/v1/languages`, params);
|
|
391
387
|
}
|
|
392
388
|
/**
|
|
393
389
|
* Fetches details about a specific language in the Platform.
|
|
@@ -396,7 +392,7 @@ var LanguagesClient = class {
|
|
|
396
392
|
*/
|
|
397
393
|
async getLanguage(languageId) {
|
|
398
394
|
this.languageIdSchema.parse(languageId);
|
|
399
|
-
return this.client.get(
|
|
395
|
+
return this.client.get(`/v1/languages/${languageId}`);
|
|
400
396
|
}
|
|
401
397
|
};
|
|
402
398
|
|
|
@@ -405,10 +401,10 @@ import { z as z3 } from "zod";
|
|
|
405
401
|
|
|
406
402
|
// src/YouVersionPlatformConfiguration.ts
|
|
407
403
|
var YouVersionPlatformConfiguration = class {
|
|
408
|
-
static
|
|
404
|
+
static _appKey = null;
|
|
409
405
|
static _installationId = null;
|
|
410
406
|
static _accessToken = null;
|
|
411
|
-
static _apiHost = "api
|
|
407
|
+
static _apiHost = "api.youversion.com";
|
|
412
408
|
static _isPreviewMode = false;
|
|
413
409
|
static _previewUserInfo = null;
|
|
414
410
|
static getOrSetInstallationId() {
|
|
@@ -423,11 +419,11 @@ var YouVersionPlatformConfiguration = class {
|
|
|
423
419
|
localStorage.setItem("x-yvp-installation-id", newId);
|
|
424
420
|
return newId;
|
|
425
421
|
}
|
|
426
|
-
static get
|
|
427
|
-
return this.
|
|
422
|
+
static get appKey() {
|
|
423
|
+
return this._appKey;
|
|
428
424
|
}
|
|
429
|
-
static set
|
|
430
|
-
this.
|
|
425
|
+
static set appKey(value) {
|
|
426
|
+
this._appKey = value;
|
|
431
427
|
}
|
|
432
428
|
static get installationId() {
|
|
433
429
|
if (!this._installationId) {
|
|
@@ -480,9 +476,6 @@ var HighlightsClient = class {
|
|
|
480
476
|
constructor(client) {
|
|
481
477
|
this.client = client;
|
|
482
478
|
}
|
|
483
|
-
get rootPath() {
|
|
484
|
-
return `/${this.client.config.version}`;
|
|
485
|
-
}
|
|
486
479
|
/**
|
|
487
480
|
* Gets the authentication token, either from the provided parameter or from the platform configuration.
|
|
488
481
|
* @param lat Optional explicit long access token. If not provided, retrieves from YouVersionPlatformConfiguration.
|
|
@@ -552,7 +545,7 @@ var HighlightsClient = class {
|
|
|
552
545
|
this.validatePassageId(options.passage_id);
|
|
553
546
|
params.passage_id = options.passage_id;
|
|
554
547
|
}
|
|
555
|
-
return this.client.get(
|
|
548
|
+
return this.client.get(`/v1/highlights`, params);
|
|
556
549
|
}
|
|
557
550
|
/**
|
|
558
551
|
* Creates or updates a highlight on a passage.
|
|
@@ -567,7 +560,7 @@ var HighlightsClient = class {
|
|
|
567
560
|
this.validatePassageId(data.passage_id);
|
|
568
561
|
this.validateColor(data.color);
|
|
569
562
|
const token = this.getAuthToken(lat);
|
|
570
|
-
return this.client.post(
|
|
563
|
+
return this.client.post(`/v1/highlights`, data, { lat: token });
|
|
571
564
|
}
|
|
572
565
|
/**
|
|
573
566
|
* Clears highlights for a passage.
|
|
@@ -587,7 +580,7 @@ var HighlightsClient = class {
|
|
|
587
580
|
this.validateVersionId(options.version_id);
|
|
588
581
|
params.version_id = options.version_id;
|
|
589
582
|
}
|
|
590
|
-
await this.client.delete(
|
|
583
|
+
await this.client.delete(`/v1/highlights/${passageId}`, params);
|
|
591
584
|
}
|
|
592
585
|
};
|
|
593
586
|
|
|
@@ -885,13 +878,13 @@ var YouVersionAPI = class {
|
|
|
885
878
|
Accept: "application/json",
|
|
886
879
|
"Content-Type": "application/json"
|
|
887
880
|
};
|
|
888
|
-
const
|
|
889
|
-
if (
|
|
890
|
-
headers["X-App-
|
|
881
|
+
const appKey = YouVersionPlatformConfiguration.appKey;
|
|
882
|
+
if (appKey) {
|
|
883
|
+
headers["X-YVP-App-Key"] = appKey;
|
|
891
884
|
}
|
|
892
885
|
const installationId = YouVersionPlatformConfiguration.installationId;
|
|
893
886
|
if (installationId) {
|
|
894
|
-
headers["
|
|
887
|
+
headers["X-YVP-Installation-ID"] = installationId;
|
|
895
888
|
}
|
|
896
889
|
const request = new Request(url.toString(), {
|
|
897
890
|
headers
|
|
@@ -905,15 +898,15 @@ var URLBuilder = class {
|
|
|
905
898
|
static get baseURL() {
|
|
906
899
|
return new URL(`https://${YouVersionPlatformConfiguration.apiHost}`);
|
|
907
900
|
}
|
|
908
|
-
static authURL(
|
|
909
|
-
if (typeof
|
|
910
|
-
throw new Error("
|
|
901
|
+
static authURL(appKey, requiredPermissions = /* @__PURE__ */ new Set(), optionalPermissions = /* @__PURE__ */ new Set()) {
|
|
902
|
+
if (typeof appKey !== "string" || appKey.trim().length === 0) {
|
|
903
|
+
throw new Error("appKey must be a non-empty string");
|
|
911
904
|
}
|
|
912
905
|
try {
|
|
913
906
|
const url = new URL(this.baseURL);
|
|
914
907
|
url.pathname = "/auth/login";
|
|
915
908
|
const searchParams = new URLSearchParams();
|
|
916
|
-
searchParams.append("
|
|
909
|
+
searchParams.append("APP_KEY", appKey);
|
|
917
910
|
searchParams.append("language", "en");
|
|
918
911
|
if (requiredPermissions.size > 0) {
|
|
919
912
|
const requiredList = Array.from(requiredPermissions).map((p) => p.toString());
|
|
@@ -977,11 +970,11 @@ var YouVersionAPIUsers = class {
|
|
|
977
970
|
if (!optionalPermissions || !(optionalPermissions instanceof Set)) {
|
|
978
971
|
throw new Error("Invalid optionalPermissions: must be a Set");
|
|
979
972
|
}
|
|
980
|
-
const
|
|
981
|
-
if (!
|
|
982
|
-
throw new Error("YouVersionPlatformConfiguration.
|
|
973
|
+
const appKey = YouVersionPlatformConfiguration.appKey;
|
|
974
|
+
if (!appKey) {
|
|
975
|
+
throw new Error("YouVersionPlatformConfiguration.appKey must be set before calling signIn");
|
|
983
976
|
}
|
|
984
|
-
const url = URLBuilder.authURL(
|
|
977
|
+
const url = URLBuilder.authURL(appKey, requiredPermissions, optionalPermissions);
|
|
985
978
|
const strategy = AuthenticationStrategyRegistry.get();
|
|
986
979
|
const callbackUrl = await strategy.authenticate(url);
|
|
987
980
|
const result = new SignInWithYouVersionResult(callbackUrl);
|
package/package.json
CHANGED
package/src/URLBuilder.ts
CHANGED
|
@@ -7,12 +7,12 @@ export class URLBuilder {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
static authURL(
|
|
10
|
-
|
|
10
|
+
appKey: string,
|
|
11
11
|
requiredPermissions: Set<SignInWithYouVersionPermissionValues> = new Set<SignInWithYouVersionPermissionValues>(),
|
|
12
12
|
optionalPermissions: Set<SignInWithYouVersionPermissionValues> = new Set<SignInWithYouVersionPermissionValues>(),
|
|
13
13
|
): URL {
|
|
14
|
-
if (typeof
|
|
15
|
-
throw new Error('
|
|
14
|
+
if (typeof appKey !== 'string' || appKey.trim().length === 0) {
|
|
15
|
+
throw new Error('appKey must be a non-empty string');
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
try {
|
|
@@ -21,7 +21,7 @@ export class URLBuilder {
|
|
|
21
21
|
|
|
22
22
|
// Add query parameters
|
|
23
23
|
const searchParams = new URLSearchParams();
|
|
24
|
-
searchParams.append('
|
|
24
|
+
searchParams.append('APP_KEY', appKey);
|
|
25
25
|
searchParams.append('language', 'en'); // TODO: load from system
|
|
26
26
|
|
|
27
27
|
if (requiredPermissions.size > 0) {
|
package/src/Users.ts
CHANGED
|
@@ -34,12 +34,12 @@ export class YouVersionAPIUsers {
|
|
|
34
34
|
throw new Error('Invalid optionalPermissions: must be a Set');
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
const
|
|
38
|
-
if (!
|
|
39
|
-
throw new Error('YouVersionPlatformConfiguration.
|
|
37
|
+
const appKey = YouVersionPlatformConfiguration.appKey;
|
|
38
|
+
if (!appKey) {
|
|
39
|
+
throw new Error('YouVersionPlatformConfiguration.appKey must be set before calling signIn');
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
const url = URLBuilder.authURL(
|
|
42
|
+
const url = URLBuilder.authURL(appKey, requiredPermissions, optionalPermissions);
|
|
43
43
|
|
|
44
44
|
// Use the registered authentication strategy
|
|
45
45
|
const strategy = AuthenticationStrategyRegistry.get();
|
package/src/YouVersionAPI.ts
CHANGED
|
@@ -7,16 +7,14 @@ export class YouVersionAPI {
|
|
|
7
7
|
'Content-Type': 'application/json',
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
headers['X-App-Id'] = appId;
|
|
10
|
+
const appKey = YouVersionPlatformConfiguration.appKey;
|
|
11
|
+
if (appKey) {
|
|
12
|
+
headers['X-YVP-App-Key'] = appKey;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
// Add installation ID header
|
|
17
15
|
const installationId = YouVersionPlatformConfiguration.installationId;
|
|
18
16
|
if (installationId) {
|
|
19
|
-
headers['
|
|
17
|
+
headers['X-YVP-Installation-ID'] = installationId;
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
const request = new Request(url.toString(), {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { YouVersionUserInfo } from './YouVersionUserInfo';
|
|
2
2
|
|
|
3
3
|
export class YouVersionPlatformConfiguration {
|
|
4
|
-
private static
|
|
4
|
+
private static _appKey: string | null = null;
|
|
5
5
|
private static _installationId: string | null = null;
|
|
6
6
|
private static _accessToken: string | null = null;
|
|
7
|
-
private static _apiHost: string = 'api
|
|
7
|
+
private static _apiHost: string = 'api.youversion.com';
|
|
8
8
|
private static _isPreviewMode: boolean = false;
|
|
9
9
|
private static _previewUserInfo: YouVersionUserInfo | null = null;
|
|
10
10
|
|
|
@@ -23,12 +23,12 @@ export class YouVersionPlatformConfiguration {
|
|
|
23
23
|
return newId;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
static get
|
|
27
|
-
return this.
|
|
26
|
+
static get appKey(): string | null {
|
|
27
|
+
return this._appKey;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
static set
|
|
31
|
-
this.
|
|
30
|
+
static set appKey(value: string | null) {
|
|
31
|
+
this._appKey = value;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
static get installationId(): string {
|
|
@@ -5,7 +5,7 @@ import type { SignInWithYouVersionPermissionValues } from '../types';
|
|
|
5
5
|
|
|
6
6
|
describe('URLBuilder - Input Validation', () => {
|
|
7
7
|
let originalApiHost: string;
|
|
8
|
-
let originalInstallationId: string |
|
|
8
|
+
let originalInstallationId: string | null;
|
|
9
9
|
|
|
10
10
|
beforeEach(() => {
|
|
11
11
|
// Store original config
|
|
@@ -13,7 +13,11 @@ describe('URLBuilder - Input Validation', () => {
|
|
|
13
13
|
originalInstallationId = YouVersionPlatformConfiguration.installationId;
|
|
14
14
|
|
|
15
15
|
// Set test config
|
|
16
|
-
|
|
16
|
+
const apiHost = process.env.YVP_API_HOST;
|
|
17
|
+
if (!apiHost) {
|
|
18
|
+
throw new Error('YVP_API_HOST environment variable must be set for URLBuilder tests.');
|
|
19
|
+
}
|
|
20
|
+
YouVersionPlatformConfiguration.apiHost = apiHost;
|
|
17
21
|
YouVersionPlatformConfiguration.installationId = 'test-installation-id';
|
|
18
22
|
});
|
|
19
23
|
|
|
@@ -23,23 +27,23 @@ describe('URLBuilder - Input Validation', () => {
|
|
|
23
27
|
YouVersionPlatformConfiguration.installationId = originalInstallationId;
|
|
24
28
|
});
|
|
25
29
|
|
|
26
|
-
describe('authURL -
|
|
27
|
-
it('should throw error for empty string
|
|
30
|
+
describe('authURL - appKey validation', () => {
|
|
31
|
+
it('should throw error for empty string appKey', () => {
|
|
28
32
|
expect(() => {
|
|
29
33
|
URLBuilder.authURL('');
|
|
30
|
-
}).toThrow('
|
|
34
|
+
}).toThrow('appKey must be a non-empty string');
|
|
31
35
|
});
|
|
32
36
|
|
|
33
|
-
it('should throw error for whitespace-only
|
|
37
|
+
it('should throw error for whitespace-only appKey', () => {
|
|
34
38
|
expect(() => {
|
|
35
39
|
URLBuilder.authURL(' ');
|
|
36
|
-
}).toThrow('
|
|
40
|
+
}).toThrow('appKey must be a non-empty string');
|
|
37
41
|
});
|
|
38
42
|
|
|
39
|
-
it('should throw error for tab/newline-only
|
|
43
|
+
it('should throw error for tab/newline-only appKey', () => {
|
|
40
44
|
expect(() => {
|
|
41
45
|
URLBuilder.authURL('\t\n ');
|
|
42
|
-
}).toThrow('
|
|
46
|
+
}).toThrow('appKey must be a non-empty string');
|
|
43
47
|
});
|
|
44
48
|
|
|
45
49
|
it('should throw descriptive error message', () => {
|
|
@@ -49,34 +53,34 @@ describe('URLBuilder - Input Validation', () => {
|
|
|
49
53
|
expect.fail('Should have thrown an error');
|
|
50
54
|
} catch (error) {
|
|
51
55
|
expect(error).toBeInstanceOf(Error);
|
|
52
|
-
expect((error as Error).message).toContain('
|
|
56
|
+
expect((error as Error).message).toContain('appKey');
|
|
53
57
|
expect((error as Error).message).toContain('non-empty string');
|
|
54
58
|
}
|
|
55
59
|
});
|
|
56
60
|
|
|
57
|
-
it('should accept valid non-empty
|
|
58
|
-
const url = URLBuilder.authURL('valid-app-
|
|
61
|
+
it('should accept valid non-empty appKey', () => {
|
|
62
|
+
const url = URLBuilder.authURL('valid-app-key');
|
|
59
63
|
|
|
60
64
|
expect(url).toBeInstanceOf(URL);
|
|
61
|
-
expect(url.hostname
|
|
65
|
+
expect(url.hostname.endsWith('.youversion.com')).toBe(true);
|
|
62
66
|
expect(url.pathname).toBe('/auth/login');
|
|
63
|
-
expect(url.searchParams.get('
|
|
67
|
+
expect(url.searchParams.get('APP_KEY')).toBe('valid-app-key');
|
|
64
68
|
});
|
|
65
69
|
|
|
66
|
-
it('should trim and accept
|
|
70
|
+
it('should trim and accept appKey with surrounding whitespace', () => {
|
|
67
71
|
// Note: The validation checks trim().length, so this should pass
|
|
68
|
-
const url = URLBuilder.authURL(' valid-app-
|
|
72
|
+
const url = URLBuilder.authURL(' valid-app-key ');
|
|
69
73
|
|
|
70
74
|
expect(url).toBeInstanceOf(URL);
|
|
71
75
|
// The actual value passed has whitespace preserved in the URL
|
|
72
|
-
expect(url.searchParams.get('
|
|
76
|
+
expect(url.searchParams.get('APP_KEY')).toBe(' valid-app-key ');
|
|
73
77
|
});
|
|
74
78
|
|
|
75
|
-
it('should accept
|
|
76
|
-
const
|
|
77
|
-
const url = URLBuilder.authURL(
|
|
79
|
+
it('should accept appKey with special characters', () => {
|
|
80
|
+
const specialAppKey = 'app-key_123.test';
|
|
81
|
+
const url = URLBuilder.authURL(specialAppKey);
|
|
78
82
|
|
|
79
|
-
expect(url.searchParams.get('
|
|
83
|
+
expect(url.searchParams.get('APP_KEY')).toBe(specialAppKey);
|
|
80
84
|
});
|
|
81
85
|
});
|
|
82
86
|
|
|
@@ -85,14 +89,14 @@ describe('URLBuilder - Input Validation', () => {
|
|
|
85
89
|
const url = URLBuilder.authURL('test-app');
|
|
86
90
|
|
|
87
91
|
expect(url.protocol).toBe('https:');
|
|
88
|
-
expect(url.hostname
|
|
92
|
+
expect(url.hostname.endsWith('.youversion.com')).toBe(true);
|
|
89
93
|
expect(url.pathname).toBe('/auth/login');
|
|
90
94
|
});
|
|
91
95
|
|
|
92
|
-
it('should include
|
|
93
|
-
const url = URLBuilder.authURL('my-app-
|
|
96
|
+
it('should include APP_KEY in query parameters', () => {
|
|
97
|
+
const url = URLBuilder.authURL('my-app-key');
|
|
94
98
|
|
|
95
|
-
expect(url.searchParams.get('
|
|
99
|
+
expect(url.searchParams.get('APP_KEY')).toBe('my-app-key');
|
|
96
100
|
});
|
|
97
101
|
|
|
98
102
|
it('should include default language parameter', () => {
|
|
@@ -109,7 +113,7 @@ describe('URLBuilder - Input Validation', () => {
|
|
|
109
113
|
});
|
|
110
114
|
|
|
111
115
|
it('should not include installation ID when not configured', () => {
|
|
112
|
-
YouVersionPlatformConfiguration.installationId =
|
|
116
|
+
YouVersionPlatformConfiguration.installationId = null;
|
|
113
117
|
const url = URLBuilder.authURL('test-app');
|
|
114
118
|
|
|
115
119
|
expect(url.searchParams.get('x-yvp-installation-id')).toBeNull();
|
|
@@ -187,7 +191,7 @@ describe('URLBuilder - Input Validation', () => {
|
|
|
187
191
|
const url = URLBuilder.userURL('valid-access-token-123');
|
|
188
192
|
|
|
189
193
|
expect(url).toBeInstanceOf(URL);
|
|
190
|
-
expect(url.hostname
|
|
194
|
+
expect(url.hostname.endsWith('.youversion.com')).toBe(true);
|
|
191
195
|
expect(url.pathname).toBe('/auth/me');
|
|
192
196
|
expect(url.searchParams.get('lat')).toBe('valid-access-token-123');
|
|
193
197
|
});
|
|
@@ -205,7 +209,7 @@ describe('URLBuilder - Input Validation', () => {
|
|
|
205
209
|
const url = URLBuilder.userURL('test-token');
|
|
206
210
|
|
|
207
211
|
expect(url.protocol).toBe('https:');
|
|
208
|
-
expect(url.hostname
|
|
212
|
+
expect(url.hostname.endsWith('.youversion.com')).toBe(true);
|
|
209
213
|
expect(url.pathname).toBe('/auth/me');
|
|
210
214
|
});
|
|
211
215
|
|
|
@@ -227,7 +231,7 @@ describe('URLBuilder - Input Validation', () => {
|
|
|
227
231
|
});
|
|
228
232
|
|
|
229
233
|
describe('Error handling', () => {
|
|
230
|
-
it('should throw errors instead of returning null for invalid
|
|
234
|
+
it('should throw errors instead of returning null for invalid appKey', () => {
|
|
231
235
|
// Verify that the method throws, not returns null
|
|
232
236
|
let threwError = false;
|
|
233
237
|
let returnValue: any;
|
|
@@ -260,7 +264,7 @@ describe('URLBuilder - Input Validation', () => {
|
|
|
260
264
|
// This is hard to trigger, but we can at least verify the pattern exists
|
|
261
265
|
// by checking that valid inputs don't trigger the catch block
|
|
262
266
|
expect(() => {
|
|
263
|
-
URLBuilder.authURL('valid-app-
|
|
267
|
+
URLBuilder.authURL('valid-app-key');
|
|
264
268
|
}).not.toThrow(/Failed to construct auth URL/);
|
|
265
269
|
});
|
|
266
270
|
|
|
@@ -14,13 +14,20 @@ vi.stubGlobal('localStorage', { getItem: mockGetItem, setItem: mockSetItem });
|
|
|
14
14
|
|
|
15
15
|
import { YouVersionPlatformConfiguration } from '../YouVersionPlatformConfiguration';
|
|
16
16
|
|
|
17
|
+
const envApiHost = process.env.YVP_API_HOST || 'api.youversion.com';
|
|
18
|
+
if (!envApiHost) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
'YVP_API_HOST environment variable must be set for YouVersionPlatformConfiguration tests.',
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
describe('YouVersionPlatformConfiguration', () => {
|
|
18
25
|
beforeEach(() => {
|
|
19
26
|
// Reset all static properties
|
|
20
|
-
YouVersionPlatformConfiguration.
|
|
27
|
+
YouVersionPlatformConfiguration.appKey = null;
|
|
21
28
|
YouVersionPlatformConfiguration.installationId = null;
|
|
22
29
|
YouVersionPlatformConfiguration.setAccessToken(null);
|
|
23
|
-
YouVersionPlatformConfiguration.apiHost =
|
|
30
|
+
YouVersionPlatformConfiguration.apiHost = envApiHost;
|
|
24
31
|
YouVersionPlatformConfiguration.isPreviewMode = false;
|
|
25
32
|
YouVersionPlatformConfiguration.previewUserInfo = null;
|
|
26
33
|
|
|
@@ -37,15 +44,15 @@ describe('YouVersionPlatformConfiguration', () => {
|
|
|
37
44
|
vi.restoreAllMocks();
|
|
38
45
|
});
|
|
39
46
|
|
|
40
|
-
describe('
|
|
41
|
-
it('should get and set
|
|
42
|
-
expect(YouVersionPlatformConfiguration.
|
|
47
|
+
describe('appKey', () => {
|
|
48
|
+
it('should get and set appKey', () => {
|
|
49
|
+
expect(YouVersionPlatformConfiguration.appKey).toBeNull();
|
|
43
50
|
|
|
44
|
-
YouVersionPlatformConfiguration.
|
|
45
|
-
expect(YouVersionPlatformConfiguration.
|
|
51
|
+
YouVersionPlatformConfiguration.appKey = 'test-app-key';
|
|
52
|
+
expect(YouVersionPlatformConfiguration.appKey).toBe('test-app-key');
|
|
46
53
|
|
|
47
|
-
YouVersionPlatformConfiguration.
|
|
48
|
-
expect(YouVersionPlatformConfiguration.
|
|
54
|
+
YouVersionPlatformConfiguration.appKey = null;
|
|
55
|
+
expect(YouVersionPlatformConfiguration.appKey).toBeNull();
|
|
49
56
|
});
|
|
50
57
|
});
|
|
51
58
|
|
|
@@ -104,10 +111,12 @@ describe('YouVersionPlatformConfiguration', () => {
|
|
|
104
111
|
|
|
105
112
|
describe('apiHost', () => {
|
|
106
113
|
it('should get and set apiHost', () => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
YouVersionPlatformConfiguration.apiHost = '
|
|
110
|
-
expect(YouVersionPlatformConfiguration.apiHost).toBe('
|
|
114
|
+
const apiHost = process.env.YVP_API_HOST || 'api.youversion.com';
|
|
115
|
+
expect(YouVersionPlatformConfiguration.apiHost).toBe(apiHost);
|
|
116
|
+
YouVersionPlatformConfiguration.apiHost = 'somethingelse.youversion.com';
|
|
117
|
+
expect(YouVersionPlatformConfiguration.apiHost).toBe('somethingelse.youversion.com');
|
|
118
|
+
YouVersionPlatformConfiguration.apiHost = apiHost;
|
|
119
|
+
expect(YouVersionPlatformConfiguration.apiHost).toBe(apiHost);
|
|
111
120
|
});
|
|
112
121
|
});
|
|
113
122
|
|
|
@@ -13,9 +13,7 @@ describe('AuthClient', () => {
|
|
|
13
13
|
beforeEach(() => {
|
|
14
14
|
global.fetch = vi.fn();
|
|
15
15
|
apiClient = new ApiClient({
|
|
16
|
-
|
|
17
|
-
appId: 'test-app-id',
|
|
18
|
-
version: 'v1',
|
|
16
|
+
appKey: 'test-app-key',
|
|
19
17
|
});
|
|
20
18
|
authClient = new AuthClient(apiClient);
|
|
21
19
|
});
|
|
@@ -16,9 +16,7 @@ describe('BibleClient', () => {
|
|
|
16
16
|
|
|
17
17
|
beforeEach(() => {
|
|
18
18
|
apiClient = new ApiClient({
|
|
19
|
-
|
|
20
|
-
appId: process.env.YVP_APP_ID || '',
|
|
21
|
-
version: 'v1',
|
|
19
|
+
appKey: process.env.YVP_APP_KEY || '',
|
|
22
20
|
installationId: 'test-installation',
|
|
23
21
|
});
|
|
24
22
|
bibleClient = new BibleClient(apiClient);
|