@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/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
- exchangeCodeForTokensWithPKCE(code: string, redirectUri: string, codeVerifier: string): Promise<ContentProviderAuthData | null>;
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
- exchangeCodeForTokensWithPKCE(code: string, redirectUri: string, codeVerifier: string): Promise<ContentProviderAuthData | null>;
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: 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
@@ -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
- async function buildB1AuthUrl(config, appBase, redirectUri, codeVerifier, state) {
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: state || ""
957
+ state
937
958
  });
938
- const url = `${appBase}/oauth?${oauthParams.toString()}`;
939
- 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" };
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
- 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) {
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
- 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 = [];
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.5.1" : "dev";
16348
+ var VERSION = true ? "0.6.0" : "dev";
16314
16349
  export {
16315
16350
  APlayProvider,
16316
16351
  B1ChurchProvider,