@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 CHANGED
@@ -60,8 +60,14 @@ import { BaseProvider, registerProvider } from '@churchapps/content-providers';
60
60
 
61
61
  class MyProvider extends BaseProvider {
62
62
  // Declare id/name/logos/config/requiresAuth/authTypes/capabilities and implement browse().
63
- // Optionally implement getPlaylist()/getInstructions(); the protected apiRequest() helper
64
- // performs authenticated JSON fetches against config.apiBase.
63
+ // Every declared authType/capability must be backed by its IProvider methods (enforced by
64
+ // tests/contentProviders.test.ts):
65
+ // oauth_pkce -> generateCodeVerifier / buildAuthUrl / buildAuthUrlFromChallenge / exchangeCodeForTokens
66
+ // (OAuthHelper implements the standard flow; delegate to it like SignPresenterProvider)
67
+ // device_flow -> initiateDeviceFlow / pollDeviceFlowToken (see DeviceFlowHelper)
68
+ // form_login -> performLogin
69
+ // playlist / instructions / mediaLicensing -> getPlaylist / getInstructions / checkMediaLicense
70
+ // The protected apiRequest() helper performs authenticated JSON fetches against config.apiBase.
65
71
  }
66
72
 
67
73
  registerProvider(new MyProvider());
package/dist/index.cjs CHANGED
@@ -143,6 +143,9 @@ var OAuthHelper = class {
143
143
  }
144
144
  async buildAuthUrl(config, codeVerifier, redirectUri, state) {
145
145
  const codeChallenge = await generateCodeChallenge(codeVerifier);
146
+ return { url: this.buildAuthUrlFromChallenge(config, codeChallenge, redirectUri, state || ""), challengeMethod: "S256" };
147
+ }
148
+ buildAuthUrlFromChallenge(config, codeChallenge, redirectUri, state) {
146
149
  const params = new URLSearchParams({
147
150
  response_type: "code",
148
151
  client_id: config.clientId,
@@ -150,9 +153,9 @@ var OAuthHelper = class {
150
153
  scope: config.scopes.join(" "),
151
154
  code_challenge: codeChallenge,
152
155
  code_challenge_method: "S256",
153
- state: state || ""
156
+ state
154
157
  });
155
- return { url: `${config.oauthBase}/authorize?${params.toString()}`, challengeMethod: "S256" };
158
+ return `${config.oauthBase}/authorize?${params.toString()}`;
156
159
  }
157
160
  async exchangeCodeForTokens(config, _providerId, code, codeVerifier, redirectUri) {
158
161
  try {
@@ -334,12 +337,15 @@ function filesToInstructions(name, files, section) {
334
337
  const items = section ? [{ id: section.id, itemType: "section", label: section.label ?? name, children: actions }] : actions;
335
338
  return { name, items };
336
339
  }
340
+ function instructionItemMediaType(item) {
341
+ return detectMediaType(`${item.downloadUrl || ""} ${item.label || ""}`, item.mediaType);
342
+ }
337
343
  function instructionsToPlaylist(instructions) {
338
344
  const files = [];
339
345
  function extractFiles(items) {
340
346
  for (const item of items) {
341
347
  if (item.downloadUrl && (item.itemType === "file" || !item.children?.length)) {
342
- files.push({ type: "file", id: item.id || item.relatedId || generateId(), title: item.label || "Untitled", mediaType: detectMediaType(item.downloadUrl), url: item.downloadUrl, downloadUrl: item.downloadUrl, seconds: item.seconds, thumbnail: item.thumbnail });
348
+ 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 });
343
349
  }
344
350
  if (item.children) extractFiles(item.children);
345
351
  }
@@ -449,6 +455,7 @@ function extractArray(response, ...keys) {
449
455
  var APlayProvider = class extends BaseProvider {
450
456
  constructor() {
451
457
  super(...arguments);
458
+ this.oauthHelper = new OAuthHelper();
452
459
  this.id = "aplay";
453
460
  this.name = "APlay";
454
461
  this.logos = { light: "https://www.joinamazing.com/_assets/v11/3ba846c5afd7e73d27bc4d87b63d423e7ae2dc73.svg", dark: "https://www.joinamazing.com/_assets/v11/3ba846c5afd7e73d27bc4d87b63d423e7ae2dc73.svg" };
@@ -518,6 +525,18 @@ var APlayProvider = class extends BaseProvider {
518
525
  async checkMediaLicense(mediaId, auth) {
519
526
  return checkMediaLicense(mediaId, auth);
520
527
  }
528
+ generateCodeVerifier() {
529
+ return this.oauthHelper.generateCodeVerifier();
530
+ }
531
+ async buildAuthUrl(codeVerifier, redirectUri, state) {
532
+ return this.oauthHelper.buildAuthUrl(this.config, codeVerifier, redirectUri, state || this.id);
533
+ }
534
+ buildAuthUrlFromChallenge(codeChallenge, redirectUri, state) {
535
+ return this.oauthHelper.buildAuthUrlFromChallenge(this.config, codeChallenge, redirectUri, state);
536
+ }
537
+ async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
538
+ return this.oauthHelper.exchangeCodeForTokens(this.config, this.id, code, codeVerifier, redirectUri);
539
+ }
521
540
  };
522
541
 
523
542
  // src/providers/signPresenter/SignPresenterProvider.ts
@@ -602,6 +621,9 @@ var SignPresenterProvider = class extends BaseProvider {
602
621
  async buildAuthUrl(codeVerifier, redirectUri, state) {
603
622
  return this.oauthHelper.buildAuthUrl(this.config, codeVerifier, redirectUri, state || this.id);
604
623
  }
624
+ buildAuthUrlFromChallenge(codeChallenge, redirectUri, state) {
625
+ return this.oauthHelper.buildAuthUrlFromChallenge(this.config, codeChallenge, redirectUri, state);
626
+ }
605
627
  async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
606
628
  return this.oauthHelper.exchangeCodeForTokens(this.config, this.id, code, codeVerifier, redirectUri);
607
629
  }
@@ -974,8 +996,7 @@ function registerProvider(provider) {
974
996
  }
975
997
 
976
998
  // src/providers/b1Church/B1ChurchAuth.ts
977
- async function buildB1AuthUrl(config, appBase, redirectUri, codeVerifier, state) {
978
- const codeChallenge = await generateCodeChallenge(codeVerifier);
999
+ function buildB1AuthUrlFromChallenge(config, appBase, redirectUri, codeChallenge, state) {
979
1000
  const oauthParams = new URLSearchParams({
980
1001
  response_type: "code",
981
1002
  client_id: config.clientId,
@@ -983,10 +1004,13 @@ async function buildB1AuthUrl(config, appBase, redirectUri, codeVerifier, state)
983
1004
  scope: config.scopes.join(" "),
984
1005
  code_challenge: codeChallenge,
985
1006
  code_challenge_method: "S256",
986
- state: state || ""
1007
+ state
987
1008
  });
988
- const url = `${appBase}/oauth?${oauthParams.toString()}`;
989
- return { url, challengeMethod: "S256" };
1009
+ return `${appBase}/oauth?${oauthParams.toString()}`;
1010
+ }
1011
+ async function buildB1AuthUrl(config, appBase, redirectUri, codeVerifier, state) {
1012
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
1013
+ return { url: buildB1AuthUrlFromChallenge(config, appBase, redirectUri, codeChallenge, state || ""), challengeMethod: "S256" };
990
1014
  }
991
1015
  async function exchangeCodeForTokensWithPKCE(config, code, redirectUri, codeVerifier) {
992
1016
  try {
@@ -1218,6 +1242,7 @@ function collectFilesFromNode(node) {
1218
1242
  id: node.id,
1219
1243
  name: node.label || "",
1220
1244
  url: node.downloadUrl,
1245
+ fileType: instructionItemMediaType(node),
1221
1246
  seconds: node.seconds || 10
1222
1247
  });
1223
1248
  }
@@ -1283,6 +1308,9 @@ function findFirstThumbnail(items) {
1283
1308
  }
1284
1309
  return void 0;
1285
1310
  }
1311
+ function isLessonsChurchContent(plan) {
1312
+ return !plan.providerId || plan.providerId === "lessonschurch";
1313
+ }
1286
1314
  function isExternalProviderItem(item) {
1287
1315
  if (!item.providerId || item.providerId === "b1church") return false;
1288
1316
  if (item.providerPath) return true;
@@ -1309,10 +1337,16 @@ var B1ChurchProvider = class extends BaseProvider {
1309
1337
  this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
1310
1338
  this.planTypeId = null;
1311
1339
  }
1340
+ generateCodeVerifier() {
1341
+ return generateCodeVerifier();
1342
+ }
1312
1343
  async buildAuthUrl(codeVerifier, redirectUri, state) {
1313
1344
  return buildB1AuthUrl(this.config, this.appBase, redirectUri, codeVerifier, state);
1314
1345
  }
1315
- async exchangeCodeForTokensWithPKCE(code, redirectUri, codeVerifier) {
1346
+ buildAuthUrlFromChallenge(codeChallenge, redirectUri, state) {
1347
+ return buildB1AuthUrlFromChallenge(this.config, this.appBase, redirectUri, codeChallenge, state);
1348
+ }
1349
+ async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
1316
1350
  return exchangeCodeForTokensWithPKCE(this.config, code, redirectUri, codeVerifier);
1317
1351
  }
1318
1352
  async exchangeCodeForTokensWithSecret(code, redirectUri, clientSecret) {
@@ -1448,7 +1482,7 @@ var B1ChurchProvider = class extends BaseProvider {
1448
1482
  const planFolder = plans.find((p) => p.id === planId);
1449
1483
  if (!planFolder) return null;
1450
1484
  const churchId = planFolder.churchId;
1451
- const venueId = planFolder.contentId;
1485
+ const venueId = isLessonsChurchContent(planFolder) ? planFolder.contentId : void 0;
1452
1486
  const planTitle = planFolder.name || "Plan";
1453
1487
  if (!churchId) {
1454
1488
  console.warn("[B1Church getInstructions] planFolder missing churchId:", planFolder.id);
@@ -1557,7 +1591,7 @@ var B1ChurchProvider = class extends BaseProvider {
1557
1591
  return null;
1558
1592
  }
1559
1593
  const churchId = planFolder.churchId;
1560
- const venueId = planFolder.contentId;
1594
+ const venueId = isLessonsChurchContent(planFolder) ? planFolder.contentId : void 0;
1561
1595
  if (!churchId) {
1562
1596
  console.warn("[B1Church] getPlaylist: planFolder missing churchId:", planFolder.id);
1563
1597
  return null;
@@ -1581,7 +1615,7 @@ var B1ChurchProvider = class extends BaseProvider {
1581
1615
  const venueFeed = venueId ? await fetchVenueFeed(venueId) : null;
1582
1616
  const files = [];
1583
1617
  for (const sectionItem of planItems) {
1584
- for (const child of sectionItem.children || []) {
1618
+ for (const child of sectionItem.children?.length ? sectionItem.children : [sectionItem]) {
1585
1619
  const childItemType = child.itemType;
1586
1620
  const isSectionType = childItemType === "section" || childItemType === "lessonSection" || childItemType === "providerSection";
1587
1621
  const canExpandLocally = isSectionType && venueFeed && child.relatedId;
@@ -1897,7 +1931,8 @@ var PlanningCenterProvider = class extends BaseProvider {
1897
1931
  this.logos = { light: "https://www.planningcenter.com/icons/icon-512x512.png", dark: "https://www.planningcenter.com/icons/icon-512x512.png" };
1898
1932
  this.config = { id: "planningcenter", name: "Planning Center", apiBase: "https://api.planningcenteronline.com", oauthBase: "https://api.planningcenteronline.com/oauth", clientId: "", scopes: ["services"] };
1899
1933
  this.requiresAuth = true;
1900
- this.authTypes = ["oauth_pkce"];
1934
+ // No client-side auth flow: clientId is unset and no PKCE methods exist. Auth tokens must be supplied externally (server-side).
1935
+ this.authTypes = [];
1901
1936
  // Plan items come back without media URLs, so playlist/instructions are not actually supported yet.
1902
1937
  this.capabilities = { browse: true, playlist: false, instructions: false, mediaLicensing: false };
1903
1938
  }
@@ -16360,7 +16395,7 @@ function getAvailableProviders(ids) {
16360
16395
  }
16361
16396
 
16362
16397
  // src/index.ts
16363
- var VERSION = true ? "0.5.1" : "dev";
16398
+ var VERSION = true ? "0.6.0" : "dev";
16364
16399
  // Annotate the CommonJS export names for ESM import in node:
16365
16400
  0 && (module.exports = {
16366
16401
  APlayProvider,