@churchapps/content-providers 0.5.0 → 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/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: state || ""
106
+ state
104
107
  });
105
- return { url: `${config.oauthBase}/authorize?${params.toString()}`, challengeMethod: "S256" };
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: detectMediaType(item.downloadUrl), url: item.downloadUrl, downloadUrl: item.downloadUrl, seconds: item.seconds, thumbnail: item.thumbnail });
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
@@ -546,13 +565,15 @@ var SignPresenterProvider = class extends BaseProvider {
546
565
  const instructions = filesToInstructions(title, files, { id: playlistId + "-section", label: title });
547
566
  return { name: title, items: [{ id: playlistId + "-header", itemType: "header", label: title, children: instructions.items }] };
548
567
  }
549
- // Auth methods
550
568
  generateCodeVerifier() {
551
569
  return this.oauthHelper.generateCodeVerifier();
552
570
  }
553
571
  async buildAuthUrl(codeVerifier, redirectUri, state) {
554
572
  return this.oauthHelper.buildAuthUrl(this.config, codeVerifier, redirectUri, state || this.id);
555
573
  }
574
+ buildAuthUrlFromChallenge(codeChallenge, redirectUri, state) {
575
+ return this.oauthHelper.buildAuthUrlFromChallenge(this.config, codeChallenge, redirectUri, state);
576
+ }
556
577
  async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
557
578
  return this.oauthHelper.exchangeCodeForTokens(this.config, this.id, code, codeVerifier, redirectUri);
558
579
  }
@@ -925,8 +946,7 @@ function registerProvider(provider) {
925
946
  }
926
947
 
927
948
  // src/providers/b1Church/B1ChurchAuth.ts
928
- async function buildB1AuthUrl(config, appBase, redirectUri, codeVerifier, state) {
929
- const codeChallenge = await generateCodeChallenge(codeVerifier);
949
+ function buildB1AuthUrlFromChallenge(config, appBase, redirectUri, codeChallenge, state) {
930
950
  const oauthParams = new URLSearchParams({
931
951
  response_type: "code",
932
952
  client_id: config.clientId,
@@ -934,10 +954,13 @@ async function buildB1AuthUrl(config, appBase, redirectUri, codeVerifier, state)
934
954
  scope: config.scopes.join(" "),
935
955
  code_challenge: codeChallenge,
936
956
  code_challenge_method: "S256",
937
- state: state || ""
957
+ state
938
958
  });
939
- const url = `${appBase}/oauth?${oauthParams.toString()}`;
940
- return { url, challengeMethod: "S256" };
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" };
941
964
  }
942
965
  async function exchangeCodeForTokensWithPKCE(config, code, redirectUri, codeVerifier) {
943
966
  try {
@@ -1169,6 +1192,7 @@ function collectFilesFromNode(node) {
1169
1192
  id: node.id,
1170
1193
  name: node.label || "",
1171
1194
  url: node.downloadUrl,
1195
+ fileType: instructionItemMediaType(node),
1172
1196
  seconds: node.seconds || 10
1173
1197
  });
1174
1198
  }
@@ -1234,6 +1258,9 @@ function findFirstThumbnail(items) {
1234
1258
  }
1235
1259
  return void 0;
1236
1260
  }
1261
+ function isLessonsChurchContent(plan) {
1262
+ return !plan.providerId || plan.providerId === "lessonschurch";
1263
+ }
1237
1264
  function isExternalProviderItem(item) {
1238
1265
  if (!item.providerId || item.providerId === "b1church") return false;
1239
1266
  if (item.providerPath) return true;
@@ -1260,10 +1287,16 @@ var B1ChurchProvider = class extends BaseProvider {
1260
1287
  this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
1261
1288
  this.planTypeId = null;
1262
1289
  }
1290
+ generateCodeVerifier() {
1291
+ return generateCodeVerifier();
1292
+ }
1263
1293
  async buildAuthUrl(codeVerifier, redirectUri, state) {
1264
1294
  return buildB1AuthUrl(this.config, this.appBase, redirectUri, codeVerifier, state);
1265
1295
  }
1266
- async exchangeCodeForTokensWithPKCE(code, redirectUri, codeVerifier) {
1296
+ buildAuthUrlFromChallenge(codeChallenge, redirectUri, state) {
1297
+ return buildB1AuthUrlFromChallenge(this.config, this.appBase, redirectUri, codeChallenge, state);
1298
+ }
1299
+ async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
1267
1300
  return exchangeCodeForTokensWithPKCE(this.config, code, redirectUri, codeVerifier);
1268
1301
  }
1269
1302
  async exchangeCodeForTokensWithSecret(code, redirectUri, clientSecret) {
@@ -1399,7 +1432,7 @@ var B1ChurchProvider = class extends BaseProvider {
1399
1432
  const planFolder = plans.find((p) => p.id === planId);
1400
1433
  if (!planFolder) return null;
1401
1434
  const churchId = planFolder.churchId;
1402
- const venueId = planFolder.contentId;
1435
+ const venueId = isLessonsChurchContent(planFolder) ? planFolder.contentId : void 0;
1403
1436
  const planTitle = planFolder.name || "Plan";
1404
1437
  if (!churchId) {
1405
1438
  console.warn("[B1Church getInstructions] planFolder missing churchId:", planFolder.id);
@@ -1508,7 +1541,7 @@ var B1ChurchProvider = class extends BaseProvider {
1508
1541
  return null;
1509
1542
  }
1510
1543
  const churchId = planFolder.churchId;
1511
- const venueId = planFolder.contentId;
1544
+ const venueId = isLessonsChurchContent(planFolder) ? planFolder.contentId : void 0;
1512
1545
  if (!churchId) {
1513
1546
  console.warn("[B1Church] getPlaylist: planFolder missing churchId:", planFolder.id);
1514
1547
  return null;
@@ -1532,7 +1565,7 @@ var B1ChurchProvider = class extends BaseProvider {
1532
1565
  const venueFeed = venueId ? await fetchVenueFeed(venueId) : null;
1533
1566
  const files = [];
1534
1567
  for (const sectionItem of planItems) {
1535
- for (const child of sectionItem.children || []) {
1568
+ for (const child of sectionItem.children?.length ? sectionItem.children : [sectionItem]) {
1536
1569
  const childItemType = child.itemType;
1537
1570
  const isSectionType = childItemType === "section" || childItemType === "lessonSection" || childItemType === "providerSection";
1538
1571
  const canExpandLocally = isSectionType && venueFeed && child.relatedId;
@@ -1663,7 +1696,6 @@ var DropboxProvider = class extends BaseProvider {
1663
1696
  mediaLicensing: false
1664
1697
  };
1665
1698
  }
1666
- // -- Pagination helper --
1667
1699
  dropboxPost(endpoint, body, auth) {
1668
1700
  if (!auth) {
1669
1701
  console.error("[Dropbox] No auth token provided");
@@ -1697,7 +1729,6 @@ var DropboxProvider = class extends BaseProvider {
1697
1729
  }
1698
1730
  return allEntries;
1699
1731
  }
1700
- // -- File link helpers --
1701
1732
  async getSharedLink(filePath, auth) {
1702
1733
  if (!auth) return null;
1703
1734
  try {
@@ -1753,7 +1784,6 @@ var DropboxProvider = class extends BaseProvider {
1753
1784
  );
1754
1785
  return results.filter((r) => r.viewUrl || r.downloadUrl).map((r) => fileEntryToContentFile(r.entry, r.viewUrl || r.downloadUrl, r.downloadUrl));
1755
1786
  }
1756
- // -- Leaf detection --
1757
1787
  async checkIsLeaf(folderPath, auth) {
1758
1788
  const response = await this.dropboxPost(
1759
1789
  "/2/files/list_folder",
@@ -1763,7 +1793,6 @@ var DropboxProvider = class extends BaseProvider {
1763
1793
  if (!response) return false;
1764
1794
  return !response.entries.some((e) => e[".tag"] === "folder");
1765
1795
  }
1766
- // -- IProvider methods --
1767
1796
  async browse(path, auth) {
1768
1797
  const dropboxPath = path && path !== "/" ? path.startsWith("/") ? path : "/" + path : "";
1769
1798
  const allEntries = await this.listAllEntries(dropboxPath, auth);
@@ -1775,7 +1804,6 @@ var DropboxProvider = class extends BaseProvider {
1775
1804
  const fileItems = await this.resolveFileLinks(mediaFiles, auth);
1776
1805
  return [...folderItems, ...fileItems];
1777
1806
  }
1778
- /** Resolve a folder path to playable files (list + media filter + link resolution). */
1779
1807
  async resolveMediaFiles(path, auth) {
1780
1808
  if (!path || path === "/") return null;
1781
1809
  const dropboxPath = path.startsWith("/") ? path : "/" + path;
@@ -1797,7 +1825,6 @@ var DropboxProvider = class extends BaseProvider {
1797
1825
  const folderName = segments[segments.length - 1] || "Dropbox";
1798
1826
  return filesToInstructions(folderName, resolved.files, { id: "dropbox-section", label: folderName });
1799
1827
  }
1800
- // -- Auth methods --
1801
1828
  generateCodeVerifier() {
1802
1829
  return this.oauthHelper.generateCodeVerifier();
1803
1830
  }
@@ -1854,7 +1881,8 @@ var PlanningCenterProvider = class extends BaseProvider {
1854
1881
  this.logos = { light: "https://www.planningcenter.com/icons/icon-512x512.png", dark: "https://www.planningcenter.com/icons/icon-512x512.png" };
1855
1882
  this.config = { id: "planningcenter", name: "Planning Center", apiBase: "https://api.planningcenteronline.com", oauthBase: "https://api.planningcenteronline.com/oauth", clientId: "", scopes: ["services"] };
1856
1883
  this.requiresAuth = true;
1857
- this.authTypes = ["oauth_pkce"];
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 = [];
1858
1886
  // Plan items come back without media URLs, so playlist/instructions are not actually supported yet.
1859
1887
  this.capabilities = { browse: true, playlist: false, instructions: false, mediaLicensing: false };
1860
1888
  }
@@ -16317,7 +16345,7 @@ function getAvailableProviders(ids) {
16317
16345
  }
16318
16346
 
16319
16347
  // src/index.ts
16320
- var VERSION = true ? "0.5.0" : "dev";
16348
+ var VERSION = true ? "0.6.0" : "dev";
16321
16349
  export {
16322
16350
  APlayProvider,
16323
16351
  B1ChurchProvider,