@churchapps/content-providers 0.5.1 → 0.6.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.
- package/README.md +8 -2
- package/dist/index.cjs +159 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.js +159 -39
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -16,6 +16,8 @@ interface ContentProviderConfig {
|
|
|
16
16
|
apiBase: string;
|
|
17
17
|
oauthBase: string;
|
|
18
18
|
clientId: string;
|
|
19
|
+
/** For confidential OAuth clients (e.g. WP OAuth Server); sent on token exchange and refresh. */
|
|
20
|
+
clientSecret?: string;
|
|
19
21
|
scopes: string[];
|
|
20
22
|
supportsDeviceFlow?: boolean;
|
|
21
23
|
deviceAuthEndpoint?: string;
|
|
@@ -223,6 +225,8 @@ interface IProvider {
|
|
|
223
225
|
url: string;
|
|
224
226
|
challengeMethod: string;
|
|
225
227
|
}>;
|
|
228
|
+
/** For environments without Web Crypto (e.g. React Native): caller computes the S256 challenge. */
|
|
229
|
+
buildAuthUrlFromChallenge?(codeChallenge: string, redirectUri: string, state: string): string;
|
|
226
230
|
exchangeCodeForTokens?(code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
227
231
|
initiateDeviceFlow?(): Promise<DeviceAuthorizationResponse | null>;
|
|
228
232
|
pollDeviceFlowToken?(deviceCode: string): Promise<DeviceFlowPollResult>;
|
|
@@ -245,6 +249,7 @@ declare class OAuthHelper {
|
|
|
245
249
|
url: string;
|
|
246
250
|
challengeMethod: string;
|
|
247
251
|
}>;
|
|
252
|
+
buildAuthUrlFromChallenge(config: ContentProviderConfig, codeChallenge: string, redirectUri: string, state: string): string;
|
|
248
253
|
exchangeCodeForTokens(config: ContentProviderConfig, _providerId: string, code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
249
254
|
}
|
|
250
255
|
|
|
@@ -283,6 +288,7 @@ declare abstract class BaseProvider implements IProvider {
|
|
|
283
288
|
|
|
284
289
|
/** APlay Provider: media curriculum library with OAuth 2.0 auth. */
|
|
285
290
|
declare class APlayProvider extends BaseProvider {
|
|
291
|
+
private readonly oauthHelper;
|
|
286
292
|
readonly id = "aplay";
|
|
287
293
|
readonly name = "APlay";
|
|
288
294
|
readonly logos: ProviderLogos;
|
|
@@ -298,6 +304,13 @@ declare class APlayProvider extends BaseProvider {
|
|
|
298
304
|
getPlaylist(path: string, auth?: ContentProviderAuthData | null, _resolution?: number): Promise<ContentFile[] | null>;
|
|
299
305
|
getInstructions(path: string, auth?: ContentProviderAuthData | null): Promise<Instructions | null>;
|
|
300
306
|
checkMediaLicense(mediaId: string, auth?: ContentProviderAuthData | null): Promise<MediaLicenseResult | null>;
|
|
307
|
+
generateCodeVerifier(): string;
|
|
308
|
+
buildAuthUrl(codeVerifier: string, redirectUri: string, state?: string): Promise<{
|
|
309
|
+
url: string;
|
|
310
|
+
challengeMethod: string;
|
|
311
|
+
}>;
|
|
312
|
+
buildAuthUrlFromChallenge(codeChallenge: string, redirectUri: string, state: string): string;
|
|
313
|
+
exchangeCodeForTokens(code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
301
314
|
}
|
|
302
315
|
|
|
303
316
|
/** Browses SignPresenter playlists → messages (files). */
|
|
@@ -322,6 +335,7 @@ declare class SignPresenterProvider extends BaseProvider {
|
|
|
322
335
|
url: string;
|
|
323
336
|
challengeMethod: string;
|
|
324
337
|
}>;
|
|
338
|
+
buildAuthUrlFromChallenge(codeChallenge: string, redirectUri: string, state: string): string;
|
|
325
339
|
exchangeCodeForTokens(code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
326
340
|
initiateDeviceFlow(): Promise<DeviceAuthorizationResponse | null>;
|
|
327
341
|
pollDeviceFlowToken(deviceCode: string): Promise<DeviceFlowPollResult>;
|
|
@@ -366,11 +380,13 @@ declare class B1ChurchProvider extends BaseProvider {
|
|
|
366
380
|
readonly requiresAuth = true;
|
|
367
381
|
readonly authTypes: AuthType[];
|
|
368
382
|
readonly capabilities: ProviderCapabilities;
|
|
383
|
+
generateCodeVerifier(): string;
|
|
369
384
|
buildAuthUrl(codeVerifier: string, redirectUri: string, state?: string): Promise<{
|
|
370
385
|
url: string;
|
|
371
386
|
challengeMethod: string;
|
|
372
387
|
}>;
|
|
373
|
-
|
|
388
|
+
buildAuthUrlFromChallenge(codeChallenge: string, redirectUri: string, state: string): string;
|
|
389
|
+
exchangeCodeForTokens(code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
374
390
|
exchangeCodeForTokensWithSecret(code: string, redirectUri: string, clientSecret: string): Promise<ContentProviderAuthData | null>;
|
|
375
391
|
refreshTokenWithSecret(authData: ContentProviderAuthData, clientSecret: string): Promise<ContentProviderAuthData | null>;
|
|
376
392
|
initiateDeviceFlow(): Promise<DeviceAuthorizationResponse | null>;
|
|
@@ -379,7 +395,7 @@ declare class B1ChurchProvider extends BaseProvider {
|
|
|
379
395
|
private fetchPlanImages;
|
|
380
396
|
private planTypeId;
|
|
381
397
|
setPairingData(data: unknown): void;
|
|
382
|
-
getCurrentPlan(
|
|
398
|
+
getCurrentPlan(authData?: ContentProviderAuthData | null): Promise<CurrentPlan | null>;
|
|
383
399
|
getInstructions(path: string, authData?: ContentProviderAuthData | null): Promise<Instructions | null>;
|
|
384
400
|
private processInstructionItems;
|
|
385
401
|
private findItemByPath;
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ interface ContentProviderConfig {
|
|
|
16
16
|
apiBase: string;
|
|
17
17
|
oauthBase: string;
|
|
18
18
|
clientId: string;
|
|
19
|
+
/** For confidential OAuth clients (e.g. WP OAuth Server); sent on token exchange and refresh. */
|
|
20
|
+
clientSecret?: string;
|
|
19
21
|
scopes: string[];
|
|
20
22
|
supportsDeviceFlow?: boolean;
|
|
21
23
|
deviceAuthEndpoint?: string;
|
|
@@ -223,6 +225,8 @@ interface IProvider {
|
|
|
223
225
|
url: string;
|
|
224
226
|
challengeMethod: string;
|
|
225
227
|
}>;
|
|
228
|
+
/** For environments without Web Crypto (e.g. React Native): caller computes the S256 challenge. */
|
|
229
|
+
buildAuthUrlFromChallenge?(codeChallenge: string, redirectUri: string, state: string): string;
|
|
226
230
|
exchangeCodeForTokens?(code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
227
231
|
initiateDeviceFlow?(): Promise<DeviceAuthorizationResponse | null>;
|
|
228
232
|
pollDeviceFlowToken?(deviceCode: string): Promise<DeviceFlowPollResult>;
|
|
@@ -245,6 +249,7 @@ declare class OAuthHelper {
|
|
|
245
249
|
url: string;
|
|
246
250
|
challengeMethod: string;
|
|
247
251
|
}>;
|
|
252
|
+
buildAuthUrlFromChallenge(config: ContentProviderConfig, codeChallenge: string, redirectUri: string, state: string): string;
|
|
248
253
|
exchangeCodeForTokens(config: ContentProviderConfig, _providerId: string, code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
249
254
|
}
|
|
250
255
|
|
|
@@ -283,6 +288,7 @@ declare abstract class BaseProvider implements IProvider {
|
|
|
283
288
|
|
|
284
289
|
/** APlay Provider: media curriculum library with OAuth 2.0 auth. */
|
|
285
290
|
declare class APlayProvider extends BaseProvider {
|
|
291
|
+
private readonly oauthHelper;
|
|
286
292
|
readonly id = "aplay";
|
|
287
293
|
readonly name = "APlay";
|
|
288
294
|
readonly logos: ProviderLogos;
|
|
@@ -298,6 +304,13 @@ declare class APlayProvider extends BaseProvider {
|
|
|
298
304
|
getPlaylist(path: string, auth?: ContentProviderAuthData | null, _resolution?: number): Promise<ContentFile[] | null>;
|
|
299
305
|
getInstructions(path: string, auth?: ContentProviderAuthData | null): Promise<Instructions | null>;
|
|
300
306
|
checkMediaLicense(mediaId: string, auth?: ContentProviderAuthData | null): Promise<MediaLicenseResult | null>;
|
|
307
|
+
generateCodeVerifier(): string;
|
|
308
|
+
buildAuthUrl(codeVerifier: string, redirectUri: string, state?: string): Promise<{
|
|
309
|
+
url: string;
|
|
310
|
+
challengeMethod: string;
|
|
311
|
+
}>;
|
|
312
|
+
buildAuthUrlFromChallenge(codeChallenge: string, redirectUri: string, state: string): string;
|
|
313
|
+
exchangeCodeForTokens(code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
301
314
|
}
|
|
302
315
|
|
|
303
316
|
/** Browses SignPresenter playlists → messages (files). */
|
|
@@ -322,6 +335,7 @@ declare class SignPresenterProvider extends BaseProvider {
|
|
|
322
335
|
url: string;
|
|
323
336
|
challengeMethod: string;
|
|
324
337
|
}>;
|
|
338
|
+
buildAuthUrlFromChallenge(codeChallenge: string, redirectUri: string, state: string): string;
|
|
325
339
|
exchangeCodeForTokens(code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
326
340
|
initiateDeviceFlow(): Promise<DeviceAuthorizationResponse | null>;
|
|
327
341
|
pollDeviceFlowToken(deviceCode: string): Promise<DeviceFlowPollResult>;
|
|
@@ -366,11 +380,13 @@ declare class B1ChurchProvider extends BaseProvider {
|
|
|
366
380
|
readonly requiresAuth = true;
|
|
367
381
|
readonly authTypes: AuthType[];
|
|
368
382
|
readonly capabilities: ProviderCapabilities;
|
|
383
|
+
generateCodeVerifier(): string;
|
|
369
384
|
buildAuthUrl(codeVerifier: string, redirectUri: string, state?: string): Promise<{
|
|
370
385
|
url: string;
|
|
371
386
|
challengeMethod: string;
|
|
372
387
|
}>;
|
|
373
|
-
|
|
388
|
+
buildAuthUrlFromChallenge(codeChallenge: string, redirectUri: string, state: string): string;
|
|
389
|
+
exchangeCodeForTokens(code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
374
390
|
exchangeCodeForTokensWithSecret(code: string, redirectUri: string, clientSecret: string): Promise<ContentProviderAuthData | null>;
|
|
375
391
|
refreshTokenWithSecret(authData: ContentProviderAuthData, clientSecret: string): Promise<ContentProviderAuthData | null>;
|
|
376
392
|
initiateDeviceFlow(): Promise<DeviceAuthorizationResponse | null>;
|
|
@@ -379,7 +395,7 @@ declare class B1ChurchProvider extends BaseProvider {
|
|
|
379
395
|
private fetchPlanImages;
|
|
380
396
|
private planTypeId;
|
|
381
397
|
setPairingData(data: unknown): void;
|
|
382
|
-
getCurrentPlan(
|
|
398
|
+
getCurrentPlan(authData?: ContentProviderAuthData | null): Promise<CurrentPlan | null>;
|
|
383
399
|
getInstructions(path: string, authData?: ContentProviderAuthData | null): Promise<Instructions | null>;
|
|
384
400
|
private processInstructionItems;
|
|
385
401
|
private findItemByPath;
|
package/dist/index.js
CHANGED
|
@@ -47,6 +47,7 @@ var TokenHelper = class {
|
|
|
47
47
|
refresh_token: auth.refresh_token,
|
|
48
48
|
client_id: config.clientId
|
|
49
49
|
});
|
|
50
|
+
if (config.clientSecret) params.set("client_secret", config.clientSecret);
|
|
50
51
|
const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString() });
|
|
51
52
|
if (!response.ok) {
|
|
52
53
|
console.warn(`[TokenHelper] Token refresh failed: ${response.status} ${response.statusText}`);
|
|
@@ -93,6 +94,9 @@ var OAuthHelper = class {
|
|
|
93
94
|
}
|
|
94
95
|
async buildAuthUrl(config, codeVerifier, redirectUri, state) {
|
|
95
96
|
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
97
|
+
return { url: this.buildAuthUrlFromChallenge(config, codeChallenge, redirectUri, state || ""), challengeMethod: "S256" };
|
|
98
|
+
}
|
|
99
|
+
buildAuthUrlFromChallenge(config, codeChallenge, redirectUri, state) {
|
|
96
100
|
const params = new URLSearchParams({
|
|
97
101
|
response_type: "code",
|
|
98
102
|
client_id: config.clientId,
|
|
@@ -100,9 +104,9 @@ var OAuthHelper = class {
|
|
|
100
104
|
scope: config.scopes.join(" "),
|
|
101
105
|
code_challenge: codeChallenge,
|
|
102
106
|
code_challenge_method: "S256",
|
|
103
|
-
state
|
|
107
|
+
state
|
|
104
108
|
});
|
|
105
|
-
return
|
|
109
|
+
return `${config.oauthBase}/authorize?${params.toString()}`;
|
|
106
110
|
}
|
|
107
111
|
async exchangeCodeForTokens(config, _providerId, code, codeVerifier, redirectUri) {
|
|
108
112
|
try {
|
|
@@ -113,6 +117,7 @@ var OAuthHelper = class {
|
|
|
113
117
|
client_id: config.clientId,
|
|
114
118
|
code_verifier: codeVerifier
|
|
115
119
|
});
|
|
120
|
+
if (config.clientSecret) params.set("client_secret", config.clientSecret);
|
|
116
121
|
const tokenUrl = `${config.oauthBase}/token`;
|
|
117
122
|
const response = await fetch(tokenUrl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString() });
|
|
118
123
|
if (!response.ok) {
|
|
@@ -284,12 +289,15 @@ function filesToInstructions(name, files, section) {
|
|
|
284
289
|
const items = section ? [{ id: section.id, itemType: "section", label: section.label ?? name, children: actions }] : actions;
|
|
285
290
|
return { name, items };
|
|
286
291
|
}
|
|
292
|
+
function instructionItemMediaType(item) {
|
|
293
|
+
return detectMediaType(`${item.downloadUrl || ""} ${item.label || ""}`, item.mediaType);
|
|
294
|
+
}
|
|
287
295
|
function instructionsToPlaylist(instructions) {
|
|
288
296
|
const files = [];
|
|
289
297
|
function extractFiles(items) {
|
|
290
298
|
for (const item of items) {
|
|
291
299
|
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:
|
|
300
|
+
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
301
|
}
|
|
294
302
|
if (item.children) extractFiles(item.children);
|
|
295
303
|
}
|
|
@@ -399,6 +407,7 @@ function extractArray(response, ...keys) {
|
|
|
399
407
|
var APlayProvider = class extends BaseProvider {
|
|
400
408
|
constructor() {
|
|
401
409
|
super(...arguments);
|
|
410
|
+
this.oauthHelper = new OAuthHelper();
|
|
402
411
|
this.id = "aplay";
|
|
403
412
|
this.name = "APlay";
|
|
404
413
|
this.logos = { light: "https://www.joinamazing.com/_assets/v11/3ba846c5afd7e73d27bc4d87b63d423e7ae2dc73.svg", dark: "https://www.joinamazing.com/_assets/v11/3ba846c5afd7e73d27bc4d87b63d423e7ae2dc73.svg" };
|
|
@@ -468,6 +477,18 @@ var APlayProvider = class extends BaseProvider {
|
|
|
468
477
|
async checkMediaLicense(mediaId, auth) {
|
|
469
478
|
return checkMediaLicense(mediaId, auth);
|
|
470
479
|
}
|
|
480
|
+
generateCodeVerifier() {
|
|
481
|
+
return this.oauthHelper.generateCodeVerifier();
|
|
482
|
+
}
|
|
483
|
+
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
484
|
+
return this.oauthHelper.buildAuthUrl(this.config, codeVerifier, redirectUri, state || this.id);
|
|
485
|
+
}
|
|
486
|
+
buildAuthUrlFromChallenge(codeChallenge, redirectUri, state) {
|
|
487
|
+
return this.oauthHelper.buildAuthUrlFromChallenge(this.config, codeChallenge, redirectUri, state);
|
|
488
|
+
}
|
|
489
|
+
async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
|
|
490
|
+
return this.oauthHelper.exchangeCodeForTokens(this.config, this.id, code, codeVerifier, redirectUri);
|
|
491
|
+
}
|
|
471
492
|
};
|
|
472
493
|
|
|
473
494
|
// src/providers/signPresenter/SignPresenterProvider.ts
|
|
@@ -552,6 +573,9 @@ var SignPresenterProvider = class extends BaseProvider {
|
|
|
552
573
|
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
553
574
|
return this.oauthHelper.buildAuthUrl(this.config, codeVerifier, redirectUri, state || this.id);
|
|
554
575
|
}
|
|
576
|
+
buildAuthUrlFromChallenge(codeChallenge, redirectUri, state) {
|
|
577
|
+
return this.oauthHelper.buildAuthUrlFromChallenge(this.config, codeChallenge, redirectUri, state);
|
|
578
|
+
}
|
|
555
579
|
async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
|
|
556
580
|
return this.oauthHelper.exchangeCodeForTokens(this.config, this.id, code, codeVerifier, redirectUri);
|
|
557
581
|
}
|
|
@@ -911,21 +935,8 @@ var LessonsChurchProvider = class extends BaseProvider {
|
|
|
911
935
|
}
|
|
912
936
|
};
|
|
913
937
|
|
|
914
|
-
// src/providers/registry.ts
|
|
915
|
-
var providerRegistry = /* @__PURE__ */ new Map();
|
|
916
|
-
function getProvider(providerId) {
|
|
917
|
-
return providerRegistry.get(providerId) || null;
|
|
918
|
-
}
|
|
919
|
-
function getAllProviders() {
|
|
920
|
-
return Array.from(providerRegistry.values());
|
|
921
|
-
}
|
|
922
|
-
function registerProvider(provider) {
|
|
923
|
-
providerRegistry.set(provider.id, provider);
|
|
924
|
-
}
|
|
925
|
-
|
|
926
938
|
// src/providers/b1Church/B1ChurchAuth.ts
|
|
927
|
-
|
|
928
|
-
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
939
|
+
function buildB1AuthUrlFromChallenge(config, appBase, redirectUri, codeChallenge, state) {
|
|
929
940
|
const oauthParams = new URLSearchParams({
|
|
930
941
|
response_type: "code",
|
|
931
942
|
client_id: config.clientId,
|
|
@@ -933,10 +944,13 @@ async function buildB1AuthUrl(config, appBase, redirectUri, codeVerifier, state)
|
|
|
933
944
|
scope: config.scopes.join(" "),
|
|
934
945
|
code_challenge: codeChallenge,
|
|
935
946
|
code_challenge_method: "S256",
|
|
936
|
-
state
|
|
947
|
+
state
|
|
937
948
|
});
|
|
938
|
-
|
|
939
|
-
|
|
949
|
+
return `${appBase}/oauth?${oauthParams.toString()}`;
|
|
950
|
+
}
|
|
951
|
+
async function buildB1AuthUrl(config, appBase, redirectUri, codeVerifier, state) {
|
|
952
|
+
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
953
|
+
return { url: buildB1AuthUrlFromChallenge(config, appBase, redirectUri, codeChallenge, state || ""), challengeMethod: "S256" };
|
|
940
954
|
}
|
|
941
955
|
async function exchangeCodeForTokensWithPKCE(config, code, redirectUri, codeVerifier) {
|
|
942
956
|
try {
|
|
@@ -1168,6 +1182,7 @@ function collectFilesFromNode(node) {
|
|
|
1168
1182
|
id: node.id,
|
|
1169
1183
|
name: node.label || "",
|
|
1170
1184
|
url: node.downloadUrl,
|
|
1185
|
+
fileType: instructionItemMediaType(node),
|
|
1171
1186
|
seconds: node.seconds || 10
|
|
1172
1187
|
});
|
|
1173
1188
|
}
|
|
@@ -1233,6 +1248,9 @@ function findFirstThumbnail(items) {
|
|
|
1233
1248
|
}
|
|
1234
1249
|
return void 0;
|
|
1235
1250
|
}
|
|
1251
|
+
function isLessonsChurchContent(plan) {
|
|
1252
|
+
return !plan.providerId || plan.providerId === "lessonschurch";
|
|
1253
|
+
}
|
|
1236
1254
|
function isExternalProviderItem(item) {
|
|
1237
1255
|
if (!item.providerId || item.providerId === "b1church") return false;
|
|
1238
1256
|
if (item.providerPath) return true;
|
|
@@ -1259,10 +1277,16 @@ var B1ChurchProvider = class extends BaseProvider {
|
|
|
1259
1277
|
this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
|
|
1260
1278
|
this.planTypeId = null;
|
|
1261
1279
|
}
|
|
1280
|
+
generateCodeVerifier() {
|
|
1281
|
+
return generateCodeVerifier();
|
|
1282
|
+
}
|
|
1262
1283
|
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
1263
1284
|
return buildB1AuthUrl(this.config, this.appBase, redirectUri, codeVerifier, state);
|
|
1264
1285
|
}
|
|
1265
|
-
|
|
1286
|
+
buildAuthUrlFromChallenge(codeChallenge, redirectUri, state) {
|
|
1287
|
+
return buildB1AuthUrlFromChallenge(this.config, this.appBase, redirectUri, codeChallenge, state);
|
|
1288
|
+
}
|
|
1289
|
+
async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
|
|
1266
1290
|
return exchangeCodeForTokensWithPKCE(this.config, code, redirectUri, codeVerifier);
|
|
1267
1291
|
}
|
|
1268
1292
|
async exchangeCodeForTokensWithSecret(code, redirectUri, clientSecret) {
|
|
@@ -1369,14 +1393,12 @@ var B1ChurchProvider = class extends BaseProvider {
|
|
|
1369
1393
|
this.planTypeId = null;
|
|
1370
1394
|
}
|
|
1371
1395
|
}
|
|
1372
|
-
async getCurrentPlan(
|
|
1396
|
+
async getCurrentPlan(authData) {
|
|
1373
1397
|
if (!this.planTypeId) return null;
|
|
1374
1398
|
const plan = await fetchCurrentPlanByType(this.planTypeId);
|
|
1375
|
-
if (!plan?.providerId || !plan.providerPlanId) return null;
|
|
1399
|
+
if (!plan?.providerId || !plan.providerPlanId || !plan.ministryId) return null;
|
|
1376
1400
|
const items = plan.churchId && plan.id ? await fetchPlanItems(plan.churchId, plan.id) : [];
|
|
1377
|
-
const
|
|
1378
|
-
if (!innerProvider?.getInstructions) return null;
|
|
1379
|
-
const instructions = await innerProvider.getInstructions(plan.providerPlanId, null);
|
|
1401
|
+
const instructions = await fetchFromProviderProxy("getInstructions", plan.ministryId, plan.providerId, plan.providerPlanId, authData);
|
|
1380
1402
|
if (!instructions) return null;
|
|
1381
1403
|
const files = getOrderedFiles(instructions, items);
|
|
1382
1404
|
const thumbnail = findFirstThumbnail(instructions.items);
|
|
@@ -1398,7 +1420,7 @@ var B1ChurchProvider = class extends BaseProvider {
|
|
|
1398
1420
|
const planFolder = plans.find((p) => p.id === planId);
|
|
1399
1421
|
if (!planFolder) return null;
|
|
1400
1422
|
const churchId = planFolder.churchId;
|
|
1401
|
-
const venueId = planFolder.contentId;
|
|
1423
|
+
const venueId = isLessonsChurchContent(planFolder) ? planFolder.contentId : void 0;
|
|
1402
1424
|
const planTitle = planFolder.name || "Plan";
|
|
1403
1425
|
if (!churchId) {
|
|
1404
1426
|
console.warn("[B1Church getInstructions] planFolder missing churchId:", planFolder.id);
|
|
@@ -1507,7 +1529,7 @@ var B1ChurchProvider = class extends BaseProvider {
|
|
|
1507
1529
|
return null;
|
|
1508
1530
|
}
|
|
1509
1531
|
const churchId = planFolder.churchId;
|
|
1510
|
-
const venueId = planFolder.contentId;
|
|
1532
|
+
const venueId = isLessonsChurchContent(planFolder) ? planFolder.contentId : void 0;
|
|
1511
1533
|
if (!churchId) {
|
|
1512
1534
|
console.warn("[B1Church] getPlaylist: planFolder missing churchId:", planFolder.id);
|
|
1513
1535
|
return null;
|
|
@@ -1531,7 +1553,7 @@ var B1ChurchProvider = class extends BaseProvider {
|
|
|
1531
1553
|
const venueFeed = venueId ? await fetchVenueFeed(venueId) : null;
|
|
1532
1554
|
const files = [];
|
|
1533
1555
|
for (const sectionItem of planItems) {
|
|
1534
|
-
for (const child of sectionItem.children
|
|
1556
|
+
for (const child of sectionItem.children?.length ? sectionItem.children : [sectionItem]) {
|
|
1535
1557
|
const childItemType = child.itemType;
|
|
1536
1558
|
const isSectionType = childItemType === "section" || childItemType === "lessonSection" || childItemType === "providerSection";
|
|
1537
1559
|
const canExpandLocally = isSectionType && venueFeed && child.relatedId;
|
|
@@ -1847,7 +1869,8 @@ var PlanningCenterProvider = class extends BaseProvider {
|
|
|
1847
1869
|
this.logos = { light: "https://www.planningcenter.com/icons/icon-512x512.png", dark: "https://www.planningcenter.com/icons/icon-512x512.png" };
|
|
1848
1870
|
this.config = { id: "planningcenter", name: "Planning Center", apiBase: "https://api.planningcenteronline.com", oauthBase: "https://api.planningcenteronline.com/oauth", clientId: "", scopes: ["services"] };
|
|
1849
1871
|
this.requiresAuth = true;
|
|
1850
|
-
|
|
1872
|
+
// No client-side auth flow: clientId is unset and no PKCE methods exist. Auth tokens must be supplied externally (server-side).
|
|
1873
|
+
this.authTypes = [];
|
|
1851
1874
|
// Plan items come back without media URLs, so playlist/instructions are not actually supported yet.
|
|
1852
1875
|
this.capabilities = { browse: true, playlist: false, instructions: false, mediaLicensing: false };
|
|
1853
1876
|
}
|
|
@@ -16218,6 +16241,110 @@ var LifeChurchProvider = class extends BaseProvider {
|
|
|
16218
16241
|
}
|
|
16219
16242
|
};
|
|
16220
16243
|
|
|
16244
|
+
// src/providers/registry.ts
|
|
16245
|
+
var providerRegistry = /* @__PURE__ */ new Map();
|
|
16246
|
+
function getProvider(providerId) {
|
|
16247
|
+
return providerRegistry.get(providerId) || null;
|
|
16248
|
+
}
|
|
16249
|
+
function getAllProviders() {
|
|
16250
|
+
return Array.from(providerRegistry.values());
|
|
16251
|
+
}
|
|
16252
|
+
function registerProvider(provider) {
|
|
16253
|
+
providerRegistry.set(provider.id, provider);
|
|
16254
|
+
}
|
|
16255
|
+
|
|
16256
|
+
// src/providers/goCurriculum/data.json
|
|
16257
|
+
var data_default4 = {
|
|
16258
|
+
collections: []
|
|
16259
|
+
};
|
|
16260
|
+
|
|
16261
|
+
// src/providers/goCurriculum/GoCurriculumProvider.ts
|
|
16262
|
+
var GoCurriculumProvider = class extends BaseProvider {
|
|
16263
|
+
constructor() {
|
|
16264
|
+
super(...arguments);
|
|
16265
|
+
this.oauthHelper = new OAuthHelper();
|
|
16266
|
+
this.verifiedTokens = /* @__PURE__ */ new Set();
|
|
16267
|
+
this.id = "gocurriculum";
|
|
16268
|
+
this.name = "Go Curriculum";
|
|
16269
|
+
this.logos = {
|
|
16270
|
+
light: "https://gocurriculum.com/wp-content/uploads/go-logo-curriculum-v2.png",
|
|
16271
|
+
dark: "https://gocurriculum.com/wp-content/uploads/go-logo-curriculum-v2.png"
|
|
16272
|
+
};
|
|
16273
|
+
this.config = {
|
|
16274
|
+
id: "gocurriculum",
|
|
16275
|
+
name: "Go Curriculum",
|
|
16276
|
+
apiBase: "https://gocurriculum.com",
|
|
16277
|
+
oauthBase: "https://gocurriculum.com/oauth",
|
|
16278
|
+
clientId: "Rdrvz9jz9nTNf8rCsbpuAgVLdoWHZmcjdmmrP0Bw",
|
|
16279
|
+
clientSecret: "yfAbd2NERjzwVZP38g1AALdIvPPXwhFayCguiTCS",
|
|
16280
|
+
scopes: ["basic"]
|
|
16281
|
+
};
|
|
16282
|
+
this.data = data_default4;
|
|
16283
|
+
this.requiresAuth = true;
|
|
16284
|
+
this.authTypes = ["oauth_pkce"];
|
|
16285
|
+
this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
|
|
16286
|
+
}
|
|
16287
|
+
// ponytail: any valid login unlocks everything for the pilot — per-tier (MemberPress) filtering waits on Go's membership endpoint
|
|
16288
|
+
async verifyAuth(auth) {
|
|
16289
|
+
const token = auth?.access_token;
|
|
16290
|
+
if (!token) return false;
|
|
16291
|
+
if (this.verifiedTokens.has(token)) return true;
|
|
16292
|
+
try {
|
|
16293
|
+
const response = await fetch(`${this.config.oauthBase}/me`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ access_token: token }).toString() });
|
|
16294
|
+
if (!response.ok) return false;
|
|
16295
|
+
this.verifiedTokens.add(token);
|
|
16296
|
+
return true;
|
|
16297
|
+
} catch {
|
|
16298
|
+
return false;
|
|
16299
|
+
}
|
|
16300
|
+
}
|
|
16301
|
+
async browse(path, auth) {
|
|
16302
|
+
if (!await this.verifyAuth(auth)) return [];
|
|
16303
|
+
const { segments, depth } = parsePath(path);
|
|
16304
|
+
if (depth === 0) return this.data.collections.map((c) => createFolder(c.id, c.name, `/${c.id}`, c.image));
|
|
16305
|
+
const collection = this.data.collections.find((c) => c.id === segments[0]);
|
|
16306
|
+
if (!collection) return [];
|
|
16307
|
+
if (depth === 1) return collection.lessons.map((l) => createFolder(l.id, l.name, `/${collection.id}/${l.id}`, l.image || collection.image, true));
|
|
16308
|
+
if (depth === 2) return this.getLessonFiles(collection, segments[1]);
|
|
16309
|
+
return [];
|
|
16310
|
+
}
|
|
16311
|
+
getLessonFiles(collection, lessonId) {
|
|
16312
|
+
const lesson = collection.lessons.find((l) => l.id === lessonId);
|
|
16313
|
+
if (!lesson) return [];
|
|
16314
|
+
return lesson.files.map((f) => createFile(f.id, f.name, f.url, { mediaType: detectMediaType(`${f.url} ${f.name}`, f.fileType), thumbnail: f.thumbnail || lesson.image || collection.image, seconds: f.seconds, loop: f.loop, loopVideo: f.loopVideo }));
|
|
16315
|
+
}
|
|
16316
|
+
async getPlaylist(path, auth, _resolution) {
|
|
16317
|
+
if (!await this.verifyAuth(auth)) return null;
|
|
16318
|
+
const { segments, depth } = parsePath(path);
|
|
16319
|
+
if (depth !== 2) return null;
|
|
16320
|
+
const collection = this.data.collections.find((c) => c.id === segments[0]);
|
|
16321
|
+
if (!collection) return null;
|
|
16322
|
+
const files = this.getLessonFiles(collection, segments[1]);
|
|
16323
|
+
return files.length > 0 ? files : null;
|
|
16324
|
+
}
|
|
16325
|
+
async getInstructions(path, auth) {
|
|
16326
|
+
const { segments, depth } = parsePath(path);
|
|
16327
|
+
if (depth !== 2) return null;
|
|
16328
|
+
const files = await this.getPlaylist(path, auth);
|
|
16329
|
+
if (!files) return null;
|
|
16330
|
+
const collection = this.data.collections.find((c) => c.id === segments[0]);
|
|
16331
|
+
const lesson = collection?.lessons.find((l) => l.id === segments[1]);
|
|
16332
|
+
return filesToInstructions(lesson?.name || "Lesson", files);
|
|
16333
|
+
}
|
|
16334
|
+
generateCodeVerifier() {
|
|
16335
|
+
return this.oauthHelper.generateCodeVerifier();
|
|
16336
|
+
}
|
|
16337
|
+
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
16338
|
+
return this.oauthHelper.buildAuthUrl(this.config, codeVerifier, redirectUri, state || this.id);
|
|
16339
|
+
}
|
|
16340
|
+
buildAuthUrlFromChallenge(codeChallenge, redirectUri, state) {
|
|
16341
|
+
return this.oauthHelper.buildAuthUrlFromChallenge(this.config, codeChallenge, redirectUri, state);
|
|
16342
|
+
}
|
|
16343
|
+
async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
|
|
16344
|
+
return this.oauthHelper.exchangeCodeForTokens(this.config, this.id, code, codeVerifier, redirectUri);
|
|
16345
|
+
}
|
|
16346
|
+
};
|
|
16347
|
+
|
|
16221
16348
|
// src/providers/index.ts
|
|
16222
16349
|
var unimplementedProviders = [
|
|
16223
16350
|
{
|
|
@@ -16236,14 +16363,6 @@ var unimplementedProviders = [
|
|
|
16236
16363
|
dark: "https://freeshow.app/images/favicon.png"
|
|
16237
16364
|
}
|
|
16238
16365
|
},
|
|
16239
|
-
{
|
|
16240
|
-
id: "gocurriculum",
|
|
16241
|
-
name: "Go Curriculum",
|
|
16242
|
-
logos: {
|
|
16243
|
-
light: "https://gocurriculum.com/wp-content/uploads/go-logo-curriculum-v2.png",
|
|
16244
|
-
dark: "https://gocurriculum.com/wp-content/uploads/go-logo-curriculum-v2.png"
|
|
16245
|
-
}
|
|
16246
|
-
},
|
|
16247
16366
|
{
|
|
16248
16367
|
id: "iteachchurch",
|
|
16249
16368
|
name: "iTeachChurch",
|
|
@@ -16266,6 +16385,7 @@ function initializeProviders() {
|
|
|
16266
16385
|
new APlayProvider(),
|
|
16267
16386
|
new B1ChurchProvider(),
|
|
16268
16387
|
new DropboxProvider(),
|
|
16388
|
+
new GoCurriculumProvider(),
|
|
16269
16389
|
new BibleProjectProvider(),
|
|
16270
16390
|
new HighVoltageKidsProvider(),
|
|
16271
16391
|
new JesusFilmProvider(),
|
|
@@ -16310,7 +16430,7 @@ function getAvailableProviders(ids) {
|
|
|
16310
16430
|
}
|
|
16311
16431
|
|
|
16312
16432
|
// src/index.ts
|
|
16313
|
-
var VERSION = true ? "0.
|
|
16433
|
+
var VERSION = true ? "0.6.1" : "dev";
|
|
16314
16434
|
export {
|
|
16315
16435
|
APlayProvider,
|
|
16316
16436
|
B1ChurchProvider,
|