@churchapps/content-providers 0.5.1 → 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 +8 -2
- package/dist/index.cjs +49 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +49 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -223,6 +223,8 @@ interface IProvider {
|
|
|
223
223
|
url: string;
|
|
224
224
|
challengeMethod: string;
|
|
225
225
|
}>;
|
|
226
|
+
/** For environments without Web Crypto (e.g. React Native): caller computes the S256 challenge. */
|
|
227
|
+
buildAuthUrlFromChallenge?(codeChallenge: string, redirectUri: string, state: string): string;
|
|
226
228
|
exchangeCodeForTokens?(code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
227
229
|
initiateDeviceFlow?(): Promise<DeviceAuthorizationResponse | null>;
|
|
228
230
|
pollDeviceFlowToken?(deviceCode: string): Promise<DeviceFlowPollResult>;
|
|
@@ -245,6 +247,7 @@ declare class OAuthHelper {
|
|
|
245
247
|
url: string;
|
|
246
248
|
challengeMethod: string;
|
|
247
249
|
}>;
|
|
250
|
+
buildAuthUrlFromChallenge(config: ContentProviderConfig, codeChallenge: string, redirectUri: string, state: string): string;
|
|
248
251
|
exchangeCodeForTokens(config: ContentProviderConfig, _providerId: string, code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
249
252
|
}
|
|
250
253
|
|
|
@@ -283,6 +286,7 @@ declare abstract class BaseProvider implements IProvider {
|
|
|
283
286
|
|
|
284
287
|
/** APlay Provider: media curriculum library with OAuth 2.0 auth. */
|
|
285
288
|
declare class APlayProvider extends BaseProvider {
|
|
289
|
+
private readonly oauthHelper;
|
|
286
290
|
readonly id = "aplay";
|
|
287
291
|
readonly name = "APlay";
|
|
288
292
|
readonly logos: ProviderLogos;
|
|
@@ -298,6 +302,13 @@ declare class APlayProvider extends BaseProvider {
|
|
|
298
302
|
getPlaylist(path: string, auth?: ContentProviderAuthData | null, _resolution?: number): Promise<ContentFile[] | null>;
|
|
299
303
|
getInstructions(path: string, auth?: ContentProviderAuthData | null): Promise<Instructions | null>;
|
|
300
304
|
checkMediaLicense(mediaId: string, auth?: ContentProviderAuthData | null): Promise<MediaLicenseResult | null>;
|
|
305
|
+
generateCodeVerifier(): string;
|
|
306
|
+
buildAuthUrl(codeVerifier: string, redirectUri: string, state?: string): Promise<{
|
|
307
|
+
url: string;
|
|
308
|
+
challengeMethod: string;
|
|
309
|
+
}>;
|
|
310
|
+
buildAuthUrlFromChallenge(codeChallenge: string, redirectUri: string, state: string): string;
|
|
311
|
+
exchangeCodeForTokens(code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
301
312
|
}
|
|
302
313
|
|
|
303
314
|
/** Browses SignPresenter playlists → messages (files). */
|
|
@@ -322,6 +333,7 @@ declare class SignPresenterProvider extends BaseProvider {
|
|
|
322
333
|
url: string;
|
|
323
334
|
challengeMethod: string;
|
|
324
335
|
}>;
|
|
336
|
+
buildAuthUrlFromChallenge(codeChallenge: string, redirectUri: string, state: string): string;
|
|
325
337
|
exchangeCodeForTokens(code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
326
338
|
initiateDeviceFlow(): Promise<DeviceAuthorizationResponse | null>;
|
|
327
339
|
pollDeviceFlowToken(deviceCode: string): Promise<DeviceFlowPollResult>;
|
|
@@ -366,11 +378,13 @@ declare class B1ChurchProvider extends BaseProvider {
|
|
|
366
378
|
readonly requiresAuth = true;
|
|
367
379
|
readonly authTypes: AuthType[];
|
|
368
380
|
readonly capabilities: ProviderCapabilities;
|
|
381
|
+
generateCodeVerifier(): string;
|
|
369
382
|
buildAuthUrl(codeVerifier: string, redirectUri: string, state?: string): Promise<{
|
|
370
383
|
url: string;
|
|
371
384
|
challengeMethod: string;
|
|
372
385
|
}>;
|
|
373
|
-
|
|
386
|
+
buildAuthUrlFromChallenge(codeChallenge: string, redirectUri: string, state: string): string;
|
|
387
|
+
exchangeCodeForTokens(code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
374
388
|
exchangeCodeForTokensWithSecret(code: string, redirectUri: string, clientSecret: string): Promise<ContentProviderAuthData | null>;
|
|
375
389
|
refreshTokenWithSecret(authData: ContentProviderAuthData, clientSecret: string): Promise<ContentProviderAuthData | null>;
|
|
376
390
|
initiateDeviceFlow(): Promise<DeviceAuthorizationResponse | null>;
|
package/dist/index.d.ts
CHANGED
|
@@ -223,6 +223,8 @@ interface IProvider {
|
|
|
223
223
|
url: string;
|
|
224
224
|
challengeMethod: string;
|
|
225
225
|
}>;
|
|
226
|
+
/** For environments without Web Crypto (e.g. React Native): caller computes the S256 challenge. */
|
|
227
|
+
buildAuthUrlFromChallenge?(codeChallenge: string, redirectUri: string, state: string): string;
|
|
226
228
|
exchangeCodeForTokens?(code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
227
229
|
initiateDeviceFlow?(): Promise<DeviceAuthorizationResponse | null>;
|
|
228
230
|
pollDeviceFlowToken?(deviceCode: string): Promise<DeviceFlowPollResult>;
|
|
@@ -245,6 +247,7 @@ declare class OAuthHelper {
|
|
|
245
247
|
url: string;
|
|
246
248
|
challengeMethod: string;
|
|
247
249
|
}>;
|
|
250
|
+
buildAuthUrlFromChallenge(config: ContentProviderConfig, codeChallenge: string, redirectUri: string, state: string): string;
|
|
248
251
|
exchangeCodeForTokens(config: ContentProviderConfig, _providerId: string, code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
249
252
|
}
|
|
250
253
|
|
|
@@ -283,6 +286,7 @@ declare abstract class BaseProvider implements IProvider {
|
|
|
283
286
|
|
|
284
287
|
/** APlay Provider: media curriculum library with OAuth 2.0 auth. */
|
|
285
288
|
declare class APlayProvider extends BaseProvider {
|
|
289
|
+
private readonly oauthHelper;
|
|
286
290
|
readonly id = "aplay";
|
|
287
291
|
readonly name = "APlay";
|
|
288
292
|
readonly logos: ProviderLogos;
|
|
@@ -298,6 +302,13 @@ declare class APlayProvider extends BaseProvider {
|
|
|
298
302
|
getPlaylist(path: string, auth?: ContentProviderAuthData | null, _resolution?: number): Promise<ContentFile[] | null>;
|
|
299
303
|
getInstructions(path: string, auth?: ContentProviderAuthData | null): Promise<Instructions | null>;
|
|
300
304
|
checkMediaLicense(mediaId: string, auth?: ContentProviderAuthData | null): Promise<MediaLicenseResult | null>;
|
|
305
|
+
generateCodeVerifier(): string;
|
|
306
|
+
buildAuthUrl(codeVerifier: string, redirectUri: string, state?: string): Promise<{
|
|
307
|
+
url: string;
|
|
308
|
+
challengeMethod: string;
|
|
309
|
+
}>;
|
|
310
|
+
buildAuthUrlFromChallenge(codeChallenge: string, redirectUri: string, state: string): string;
|
|
311
|
+
exchangeCodeForTokens(code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
301
312
|
}
|
|
302
313
|
|
|
303
314
|
/** Browses SignPresenter playlists → messages (files). */
|
|
@@ -322,6 +333,7 @@ declare class SignPresenterProvider extends BaseProvider {
|
|
|
322
333
|
url: string;
|
|
323
334
|
challengeMethod: string;
|
|
324
335
|
}>;
|
|
336
|
+
buildAuthUrlFromChallenge(codeChallenge: string, redirectUri: string, state: string): string;
|
|
325
337
|
exchangeCodeForTokens(code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
326
338
|
initiateDeviceFlow(): Promise<DeviceAuthorizationResponse | null>;
|
|
327
339
|
pollDeviceFlowToken(deviceCode: string): Promise<DeviceFlowPollResult>;
|
|
@@ -366,11 +378,13 @@ declare class B1ChurchProvider extends BaseProvider {
|
|
|
366
378
|
readonly requiresAuth = true;
|
|
367
379
|
readonly authTypes: AuthType[];
|
|
368
380
|
readonly capabilities: ProviderCapabilities;
|
|
381
|
+
generateCodeVerifier(): string;
|
|
369
382
|
buildAuthUrl(codeVerifier: string, redirectUri: string, state?: string): Promise<{
|
|
370
383
|
url: string;
|
|
371
384
|
challengeMethod: string;
|
|
372
385
|
}>;
|
|
373
|
-
|
|
386
|
+
buildAuthUrlFromChallenge(codeChallenge: string, redirectUri: string, state: string): string;
|
|
387
|
+
exchangeCodeForTokens(code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
374
388
|
exchangeCodeForTokensWithSecret(code: string, redirectUri: string, clientSecret: string): Promise<ContentProviderAuthData | null>;
|
|
375
389
|
refreshTokenWithSecret(authData: ContentProviderAuthData, clientSecret: string): Promise<ContentProviderAuthData | null>;
|
|
376
390
|
initiateDeviceFlow(): Promise<DeviceAuthorizationResponse | null>;
|
package/dist/index.js
CHANGED
|
@@ -93,6 +93,9 @@ var OAuthHelper = class {
|
|
|
93
93
|
}
|
|
94
94
|
async buildAuthUrl(config, codeVerifier, redirectUri, state) {
|
|
95
95
|
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
96
|
+
return { url: this.buildAuthUrlFromChallenge(config, codeChallenge, redirectUri, state || ""), challengeMethod: "S256" };
|
|
97
|
+
}
|
|
98
|
+
buildAuthUrlFromChallenge(config, codeChallenge, redirectUri, state) {
|
|
96
99
|
const params = new URLSearchParams({
|
|
97
100
|
response_type: "code",
|
|
98
101
|
client_id: config.clientId,
|
|
@@ -100,9 +103,9 @@ var OAuthHelper = class {
|
|
|
100
103
|
scope: config.scopes.join(" "),
|
|
101
104
|
code_challenge: codeChallenge,
|
|
102
105
|
code_challenge_method: "S256",
|
|
103
|
-
state
|
|
106
|
+
state
|
|
104
107
|
});
|
|
105
|
-
return
|
|
108
|
+
return `${config.oauthBase}/authorize?${params.toString()}`;
|
|
106
109
|
}
|
|
107
110
|
async exchangeCodeForTokens(config, _providerId, code, codeVerifier, redirectUri) {
|
|
108
111
|
try {
|
|
@@ -284,12 +287,15 @@ function filesToInstructions(name, files, section) {
|
|
|
284
287
|
const items = section ? [{ id: section.id, itemType: "section", label: section.label ?? name, children: actions }] : actions;
|
|
285
288
|
return { name, items };
|
|
286
289
|
}
|
|
290
|
+
function instructionItemMediaType(item) {
|
|
291
|
+
return detectMediaType(`${item.downloadUrl || ""} ${item.label || ""}`, item.mediaType);
|
|
292
|
+
}
|
|
287
293
|
function instructionsToPlaylist(instructions) {
|
|
288
294
|
const files = [];
|
|
289
295
|
function extractFiles(items) {
|
|
290
296
|
for (const item of items) {
|
|
291
297
|
if (item.downloadUrl && (item.itemType === "file" || !item.children?.length)) {
|
|
292
|
-
files.push({ type: "file", id: item.id || item.relatedId || generateId(), title: item.label || "Untitled", mediaType:
|
|
298
|
+
files.push({ type: "file", id: item.id || item.relatedId || generateId(), title: item.label || "Untitled", mediaType: instructionItemMediaType(item), url: item.downloadUrl, downloadUrl: item.downloadUrl, seconds: item.seconds, thumbnail: item.thumbnail });
|
|
293
299
|
}
|
|
294
300
|
if (item.children) extractFiles(item.children);
|
|
295
301
|
}
|
|
@@ -399,6 +405,7 @@ function extractArray(response, ...keys) {
|
|
|
399
405
|
var APlayProvider = class extends BaseProvider {
|
|
400
406
|
constructor() {
|
|
401
407
|
super(...arguments);
|
|
408
|
+
this.oauthHelper = new OAuthHelper();
|
|
402
409
|
this.id = "aplay";
|
|
403
410
|
this.name = "APlay";
|
|
404
411
|
this.logos = { light: "https://www.joinamazing.com/_assets/v11/3ba846c5afd7e73d27bc4d87b63d423e7ae2dc73.svg", dark: "https://www.joinamazing.com/_assets/v11/3ba846c5afd7e73d27bc4d87b63d423e7ae2dc73.svg" };
|
|
@@ -468,6 +475,18 @@ var APlayProvider = class extends BaseProvider {
|
|
|
468
475
|
async checkMediaLicense(mediaId, auth) {
|
|
469
476
|
return checkMediaLicense(mediaId, auth);
|
|
470
477
|
}
|
|
478
|
+
generateCodeVerifier() {
|
|
479
|
+
return this.oauthHelper.generateCodeVerifier();
|
|
480
|
+
}
|
|
481
|
+
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
482
|
+
return this.oauthHelper.buildAuthUrl(this.config, codeVerifier, redirectUri, state || this.id);
|
|
483
|
+
}
|
|
484
|
+
buildAuthUrlFromChallenge(codeChallenge, redirectUri, state) {
|
|
485
|
+
return this.oauthHelper.buildAuthUrlFromChallenge(this.config, codeChallenge, redirectUri, state);
|
|
486
|
+
}
|
|
487
|
+
async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
|
|
488
|
+
return this.oauthHelper.exchangeCodeForTokens(this.config, this.id, code, codeVerifier, redirectUri);
|
|
489
|
+
}
|
|
471
490
|
};
|
|
472
491
|
|
|
473
492
|
// src/providers/signPresenter/SignPresenterProvider.ts
|
|
@@ -552,6 +571,9 @@ var SignPresenterProvider = class extends BaseProvider {
|
|
|
552
571
|
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
553
572
|
return this.oauthHelper.buildAuthUrl(this.config, codeVerifier, redirectUri, state || this.id);
|
|
554
573
|
}
|
|
574
|
+
buildAuthUrlFromChallenge(codeChallenge, redirectUri, state) {
|
|
575
|
+
return this.oauthHelper.buildAuthUrlFromChallenge(this.config, codeChallenge, redirectUri, state);
|
|
576
|
+
}
|
|
555
577
|
async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
|
|
556
578
|
return this.oauthHelper.exchangeCodeForTokens(this.config, this.id, code, codeVerifier, redirectUri);
|
|
557
579
|
}
|
|
@@ -924,8 +946,7 @@ function registerProvider(provider) {
|
|
|
924
946
|
}
|
|
925
947
|
|
|
926
948
|
// src/providers/b1Church/B1ChurchAuth.ts
|
|
927
|
-
|
|
928
|
-
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
949
|
+
function buildB1AuthUrlFromChallenge(config, appBase, redirectUri, codeChallenge, state) {
|
|
929
950
|
const oauthParams = new URLSearchParams({
|
|
930
951
|
response_type: "code",
|
|
931
952
|
client_id: config.clientId,
|
|
@@ -933,10 +954,13 @@ async function buildB1AuthUrl(config, appBase, redirectUri, codeVerifier, state)
|
|
|
933
954
|
scope: config.scopes.join(" "),
|
|
934
955
|
code_challenge: codeChallenge,
|
|
935
956
|
code_challenge_method: "S256",
|
|
936
|
-
state
|
|
957
|
+
state
|
|
937
958
|
});
|
|
938
|
-
|
|
939
|
-
|
|
959
|
+
return `${appBase}/oauth?${oauthParams.toString()}`;
|
|
960
|
+
}
|
|
961
|
+
async function buildB1AuthUrl(config, appBase, redirectUri, codeVerifier, state) {
|
|
962
|
+
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
963
|
+
return { url: buildB1AuthUrlFromChallenge(config, appBase, redirectUri, codeChallenge, state || ""), challengeMethod: "S256" };
|
|
940
964
|
}
|
|
941
965
|
async function exchangeCodeForTokensWithPKCE(config, code, redirectUri, codeVerifier) {
|
|
942
966
|
try {
|
|
@@ -1168,6 +1192,7 @@ function collectFilesFromNode(node) {
|
|
|
1168
1192
|
id: node.id,
|
|
1169
1193
|
name: node.label || "",
|
|
1170
1194
|
url: node.downloadUrl,
|
|
1195
|
+
fileType: instructionItemMediaType(node),
|
|
1171
1196
|
seconds: node.seconds || 10
|
|
1172
1197
|
});
|
|
1173
1198
|
}
|
|
@@ -1233,6 +1258,9 @@ function findFirstThumbnail(items) {
|
|
|
1233
1258
|
}
|
|
1234
1259
|
return void 0;
|
|
1235
1260
|
}
|
|
1261
|
+
function isLessonsChurchContent(plan) {
|
|
1262
|
+
return !plan.providerId || plan.providerId === "lessonschurch";
|
|
1263
|
+
}
|
|
1236
1264
|
function isExternalProviderItem(item) {
|
|
1237
1265
|
if (!item.providerId || item.providerId === "b1church") return false;
|
|
1238
1266
|
if (item.providerPath) return true;
|
|
@@ -1259,10 +1287,16 @@ var B1ChurchProvider = class extends BaseProvider {
|
|
|
1259
1287
|
this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
|
|
1260
1288
|
this.planTypeId = null;
|
|
1261
1289
|
}
|
|
1290
|
+
generateCodeVerifier() {
|
|
1291
|
+
return generateCodeVerifier();
|
|
1292
|
+
}
|
|
1262
1293
|
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
1263
1294
|
return buildB1AuthUrl(this.config, this.appBase, redirectUri, codeVerifier, state);
|
|
1264
1295
|
}
|
|
1265
|
-
|
|
1296
|
+
buildAuthUrlFromChallenge(codeChallenge, redirectUri, state) {
|
|
1297
|
+
return buildB1AuthUrlFromChallenge(this.config, this.appBase, redirectUri, codeChallenge, state);
|
|
1298
|
+
}
|
|
1299
|
+
async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
|
|
1266
1300
|
return exchangeCodeForTokensWithPKCE(this.config, code, redirectUri, codeVerifier);
|
|
1267
1301
|
}
|
|
1268
1302
|
async exchangeCodeForTokensWithSecret(code, redirectUri, clientSecret) {
|
|
@@ -1398,7 +1432,7 @@ var B1ChurchProvider = class extends BaseProvider {
|
|
|
1398
1432
|
const planFolder = plans.find((p) => p.id === planId);
|
|
1399
1433
|
if (!planFolder) return null;
|
|
1400
1434
|
const churchId = planFolder.churchId;
|
|
1401
|
-
const venueId = planFolder.contentId;
|
|
1435
|
+
const venueId = isLessonsChurchContent(planFolder) ? planFolder.contentId : void 0;
|
|
1402
1436
|
const planTitle = planFolder.name || "Plan";
|
|
1403
1437
|
if (!churchId) {
|
|
1404
1438
|
console.warn("[B1Church getInstructions] planFolder missing churchId:", planFolder.id);
|
|
@@ -1507,7 +1541,7 @@ var B1ChurchProvider = class extends BaseProvider {
|
|
|
1507
1541
|
return null;
|
|
1508
1542
|
}
|
|
1509
1543
|
const churchId = planFolder.churchId;
|
|
1510
|
-
const venueId = planFolder.contentId;
|
|
1544
|
+
const venueId = isLessonsChurchContent(planFolder) ? planFolder.contentId : void 0;
|
|
1511
1545
|
if (!churchId) {
|
|
1512
1546
|
console.warn("[B1Church] getPlaylist: planFolder missing churchId:", planFolder.id);
|
|
1513
1547
|
return null;
|
|
@@ -1531,7 +1565,7 @@ var B1ChurchProvider = class extends BaseProvider {
|
|
|
1531
1565
|
const venueFeed = venueId ? await fetchVenueFeed(venueId) : null;
|
|
1532
1566
|
const files = [];
|
|
1533
1567
|
for (const sectionItem of planItems) {
|
|
1534
|
-
for (const child of sectionItem.children
|
|
1568
|
+
for (const child of sectionItem.children?.length ? sectionItem.children : [sectionItem]) {
|
|
1535
1569
|
const childItemType = child.itemType;
|
|
1536
1570
|
const isSectionType = childItemType === "section" || childItemType === "lessonSection" || childItemType === "providerSection";
|
|
1537
1571
|
const canExpandLocally = isSectionType && venueFeed && child.relatedId;
|
|
@@ -1847,7 +1881,8 @@ var PlanningCenterProvider = class extends BaseProvider {
|
|
|
1847
1881
|
this.logos = { light: "https://www.planningcenter.com/icons/icon-512x512.png", dark: "https://www.planningcenter.com/icons/icon-512x512.png" };
|
|
1848
1882
|
this.config = { id: "planningcenter", name: "Planning Center", apiBase: "https://api.planningcenteronline.com", oauthBase: "https://api.planningcenteronline.com/oauth", clientId: "", scopes: ["services"] };
|
|
1849
1883
|
this.requiresAuth = true;
|
|
1850
|
-
|
|
1884
|
+
// No client-side auth flow: clientId is unset and no PKCE methods exist. Auth tokens must be supplied externally (server-side).
|
|
1885
|
+
this.authTypes = [];
|
|
1851
1886
|
// Plan items come back without media URLs, so playlist/instructions are not actually supported yet.
|
|
1852
1887
|
this.capabilities = { browse: true, playlist: false, instructions: false, mediaLicensing: false };
|
|
1853
1888
|
}
|
|
@@ -16310,7 +16345,7 @@ function getAvailableProviders(ids) {
|
|
|
16310
16345
|
}
|
|
16311
16346
|
|
|
16312
16347
|
// src/index.ts
|
|
16313
|
-
var VERSION = true ? "0.
|
|
16348
|
+
var VERSION = true ? "0.6.0" : "dev";
|
|
16314
16349
|
export {
|
|
16315
16350
|
APlayProvider,
|
|
16316
16351
|
B1ChurchProvider,
|