@churchapps/content-providers 0.9.3 → 0.10.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
@@ -116,6 +116,11 @@ interface FeedFileInterface {
116
116
  seconds?: number;
117
117
  fileType?: string;
118
118
  thumbnail?: string;
119
+ bytes?: number;
120
+ }
121
+ interface FeedDownloadInterface {
122
+ name?: string;
123
+ files?: FeedFileInterface[];
119
124
  }
120
125
  interface FeedActionInterface {
121
126
  id?: string;
@@ -135,6 +140,7 @@ interface FeedVenueInterface {
135
140
  lessonName?: string;
136
141
  lessonDescription?: string;
137
142
  lessonImage?: string;
143
+ downloads?: FeedDownloadInterface[];
138
144
  sections?: FeedSectionInterface[];
139
145
  }
140
146
  interface InstructionItem {
@@ -150,9 +156,18 @@ interface InstructionItem {
150
156
  thumbnail?: string;
151
157
  mediaType?: "video" | "image" | "audio";
152
158
  }
159
+ /** An extra file the teacher downloads/prints (zip, PDF, leader guide) — never part of the presented playlist. */
160
+ interface ContentDownload {
161
+ id?: string;
162
+ title: string;
163
+ url: string;
164
+ fileType?: string;
165
+ bytes?: number;
166
+ }
153
167
  interface Instructions {
154
168
  name?: string;
155
169
  items: InstructionItem[];
170
+ downloads?: ContentDownload[];
156
171
  }
157
172
  interface MessageFileInterface {
158
173
  id?: string;
@@ -263,10 +278,13 @@ declare function toAuthData(data: Record<string, unknown>, fallbacks?: {
263
278
  }): ContentProviderAuthData & {
264
279
  planTypeId?: string;
265
280
  };
281
+ declare function authIsExpired(auth: ContentProviderAuthData | null | undefined, nowMs?: number): boolean;
266
282
  declare class TokenHelper {
283
+ private static inflight;
267
284
  isAuthValid(auth: ContentProviderAuthData | null | undefined): boolean;
268
285
  isTokenExpired(auth: ContentProviderAuthData): boolean;
269
286
  refreshToken(config: ContentProviderConfig, auth: ContentProviderAuthData): Promise<ContentProviderAuthData | null>;
287
+ private doRefresh;
270
288
  }
271
289
 
272
290
  declare class DeviceFlowHelper {
@@ -377,6 +395,8 @@ declare class LessonsChurchProvider extends BaseProvider {
377
395
  private getAddOnFiles;
378
396
  private getAddOnCategories;
379
397
  private getAddOnsByCategory;
398
+ /** Flatten feed download bundles into printable files, keeping playlist media (videos already in the run-of-show) out. */
399
+ private convertFeedDownloads;
380
400
  getInstructions(path: string, _auth?: ContentProviderAuthData | null): Promise<Instructions | null>;
381
401
  }
382
402
 
@@ -586,4 +606,4 @@ declare function getAvailableProviders(ids?: string[]): ProviderInfo[];
586
606
  */
587
607
  declare const VERSION: string;
588
608
 
589
- export { APlayProvider, type AuthType, B1ChurchProvider, BaseProvider, BibleProjectProvider, CbnProvider, type ContentFile, type ContentFolder, type ContentItem, type ContentProviderAuthData, type ContentProviderConfig, type CurrentPlan, type DeviceAuthorizationResponse, DeviceFlowHelper, type DeviceFlowPollResult, type DeviceFlowState, DropboxProvider, type EndpointValue, type EndpointsConfig, type FeedActionInterface, type FeedFileInterface, type FeedSectionInterface, type FeedVenueInterface, HighVoltageKidsProvider, type IProvider, type InstructionItem, type Instructions, JesusFilmProvider, LessonsChurchProvider, LifeChurchProvider, type MediaLicenseResult, type MediaLicenseStatus, type MessageFileInterface, OAuthHelper, type Plan, type PlanPresentation, type PlanSection, type PlanTimelineItem, PlanningCenterProvider, type ProviderCapabilities, type ProviderInfo, type ProviderLogos, SignPresenterProvider, TokenHelper, VERSION, type VenueActionInterface, type VenueActionsResponseInterface, type VenueSectionActionsInterface, getAllProviders, getAvailableProviders, getProvider, getProviderConfig, isContentFile, isContentFolder, navigateToPath, registerProvider, setProviderSecret, toAuthData };
609
+ export { APlayProvider, type AuthType, B1ChurchProvider, BaseProvider, BibleProjectProvider, CbnProvider, type ContentDownload, type ContentFile, type ContentFolder, type ContentItem, type ContentProviderAuthData, type ContentProviderConfig, type CurrentPlan, type DeviceAuthorizationResponse, DeviceFlowHelper, type DeviceFlowPollResult, type DeviceFlowState, DropboxProvider, type EndpointValue, type EndpointsConfig, type FeedActionInterface, type FeedDownloadInterface, type FeedFileInterface, type FeedSectionInterface, type FeedVenueInterface, HighVoltageKidsProvider, type IProvider, type InstructionItem, type Instructions, JesusFilmProvider, LessonsChurchProvider, LifeChurchProvider, type MediaLicenseResult, type MediaLicenseStatus, type MessageFileInterface, OAuthHelper, type Plan, type PlanPresentation, type PlanSection, type PlanTimelineItem, PlanningCenterProvider, type ProviderCapabilities, type ProviderInfo, type ProviderLogos, SignPresenterProvider, TokenHelper, VERSION, type VenueActionInterface, type VenueActionsResponseInterface, type VenueSectionActionsInterface, authIsExpired, getAllProviders, getAvailableProviders, getProvider, getProviderConfig, isContentFile, isContentFolder, navigateToPath, registerProvider, setProviderSecret, toAuthData };
package/dist/index.d.ts CHANGED
@@ -116,6 +116,11 @@ interface FeedFileInterface {
116
116
  seconds?: number;
117
117
  fileType?: string;
118
118
  thumbnail?: string;
119
+ bytes?: number;
120
+ }
121
+ interface FeedDownloadInterface {
122
+ name?: string;
123
+ files?: FeedFileInterface[];
119
124
  }
120
125
  interface FeedActionInterface {
121
126
  id?: string;
@@ -135,6 +140,7 @@ interface FeedVenueInterface {
135
140
  lessonName?: string;
136
141
  lessonDescription?: string;
137
142
  lessonImage?: string;
143
+ downloads?: FeedDownloadInterface[];
138
144
  sections?: FeedSectionInterface[];
139
145
  }
140
146
  interface InstructionItem {
@@ -150,9 +156,18 @@ interface InstructionItem {
150
156
  thumbnail?: string;
151
157
  mediaType?: "video" | "image" | "audio";
152
158
  }
159
+ /** An extra file the teacher downloads/prints (zip, PDF, leader guide) — never part of the presented playlist. */
160
+ interface ContentDownload {
161
+ id?: string;
162
+ title: string;
163
+ url: string;
164
+ fileType?: string;
165
+ bytes?: number;
166
+ }
153
167
  interface Instructions {
154
168
  name?: string;
155
169
  items: InstructionItem[];
170
+ downloads?: ContentDownload[];
156
171
  }
157
172
  interface MessageFileInterface {
158
173
  id?: string;
@@ -263,10 +278,13 @@ declare function toAuthData(data: Record<string, unknown>, fallbacks?: {
263
278
  }): ContentProviderAuthData & {
264
279
  planTypeId?: string;
265
280
  };
281
+ declare function authIsExpired(auth: ContentProviderAuthData | null | undefined, nowMs?: number): boolean;
266
282
  declare class TokenHelper {
283
+ private static inflight;
267
284
  isAuthValid(auth: ContentProviderAuthData | null | undefined): boolean;
268
285
  isTokenExpired(auth: ContentProviderAuthData): boolean;
269
286
  refreshToken(config: ContentProviderConfig, auth: ContentProviderAuthData): Promise<ContentProviderAuthData | null>;
287
+ private doRefresh;
270
288
  }
271
289
 
272
290
  declare class DeviceFlowHelper {
@@ -377,6 +395,8 @@ declare class LessonsChurchProvider extends BaseProvider {
377
395
  private getAddOnFiles;
378
396
  private getAddOnCategories;
379
397
  private getAddOnsByCategory;
398
+ /** Flatten feed download bundles into printable files, keeping playlist media (videos already in the run-of-show) out. */
399
+ private convertFeedDownloads;
380
400
  getInstructions(path: string, _auth?: ContentProviderAuthData | null): Promise<Instructions | null>;
381
401
  }
382
402
 
@@ -586,4 +606,4 @@ declare function getAvailableProviders(ids?: string[]): ProviderInfo[];
586
606
  */
587
607
  declare const VERSION: string;
588
608
 
589
- export { APlayProvider, type AuthType, B1ChurchProvider, BaseProvider, BibleProjectProvider, CbnProvider, type ContentFile, type ContentFolder, type ContentItem, type ContentProviderAuthData, type ContentProviderConfig, type CurrentPlan, type DeviceAuthorizationResponse, DeviceFlowHelper, type DeviceFlowPollResult, type DeviceFlowState, DropboxProvider, type EndpointValue, type EndpointsConfig, type FeedActionInterface, type FeedFileInterface, type FeedSectionInterface, type FeedVenueInterface, HighVoltageKidsProvider, type IProvider, type InstructionItem, type Instructions, JesusFilmProvider, LessonsChurchProvider, LifeChurchProvider, type MediaLicenseResult, type MediaLicenseStatus, type MessageFileInterface, OAuthHelper, type Plan, type PlanPresentation, type PlanSection, type PlanTimelineItem, PlanningCenterProvider, type ProviderCapabilities, type ProviderInfo, type ProviderLogos, SignPresenterProvider, TokenHelper, VERSION, type VenueActionInterface, type VenueActionsResponseInterface, type VenueSectionActionsInterface, getAllProviders, getAvailableProviders, getProvider, getProviderConfig, isContentFile, isContentFolder, navigateToPath, registerProvider, setProviderSecret, toAuthData };
609
+ export { APlayProvider, type AuthType, B1ChurchProvider, BaseProvider, BibleProjectProvider, CbnProvider, type ContentDownload, type ContentFile, type ContentFolder, type ContentItem, type ContentProviderAuthData, type ContentProviderConfig, type CurrentPlan, type DeviceAuthorizationResponse, DeviceFlowHelper, type DeviceFlowPollResult, type DeviceFlowState, DropboxProvider, type EndpointValue, type EndpointsConfig, type FeedActionInterface, type FeedDownloadInterface, type FeedFileInterface, type FeedSectionInterface, type FeedVenueInterface, HighVoltageKidsProvider, type IProvider, type InstructionItem, type Instructions, JesusFilmProvider, LessonsChurchProvider, LifeChurchProvider, type MediaLicenseResult, type MediaLicenseStatus, type MessageFileInterface, OAuthHelper, type Plan, type PlanPresentation, type PlanSection, type PlanTimelineItem, PlanningCenterProvider, type ProviderCapabilities, type ProviderInfo, type ProviderLogos, SignPresenterProvider, TokenHelper, VERSION, type VenueActionInterface, type VenueActionsResponseInterface, type VenueSectionActionsInterface, authIsExpired, getAllProviders, getAvailableProviders, getProvider, getProviderConfig, isContentFile, isContentFolder, navigateToPath, registerProvider, setProviderSecret, toAuthData };
package/dist/index.js CHANGED
@@ -31,18 +31,32 @@ function toAuthData(data, fallbacks) {
31
31
  ...planTypeId ? { planTypeId } : {}
32
32
  };
33
33
  }
34
- var TokenHelper = class {
34
+ function authIsExpired(auth, nowMs = Date.now()) {
35
+ if (!auth?.created_at || !auth.expires_in) return true;
36
+ const lifetimeMs = auth.expires_in * 1e3;
37
+ const expiresAt = (auth.created_at + auth.expires_in) * 1e3;
38
+ const bufferMs = Math.min(5 * 60 * 1e3, Math.floor(lifetimeMs / 10));
39
+ return nowMs > expiresAt - bufferMs;
40
+ }
41
+ var _TokenHelper = class _TokenHelper {
35
42
  isAuthValid(auth) {
36
- if (!auth) return false;
37
- return !this.isTokenExpired(auth);
43
+ return !authIsExpired(auth);
38
44
  }
39
45
  isTokenExpired(auth) {
40
- if (!auth.created_at || !auth.expires_in) return true;
41
- const expiresAt = (auth.created_at + auth.expires_in) * 1e3;
42
- return Date.now() > expiresAt - 5 * 60 * 1e3;
46
+ return authIsExpired(auth);
43
47
  }
44
48
  async refreshToken(config, auth) {
45
49
  if (!auth.refresh_token) return null;
50
+ const key = `${config.oauthBase}|${config.clientId}|${auth.refresh_token}`;
51
+ const existing = _TokenHelper.inflight.get(key);
52
+ if (existing) return existing;
53
+ const pending = this.doRefresh(config, auth).finally(() => {
54
+ _TokenHelper.inflight.delete(key);
55
+ });
56
+ _TokenHelper.inflight.set(key, pending);
57
+ return pending;
58
+ }
59
+ async doRefresh(config, auth) {
46
60
  try {
47
61
  const params = new URLSearchParams({
48
62
  grant_type: "refresh_token",
@@ -63,6 +77,8 @@ var TokenHelper = class {
63
77
  }
64
78
  }
65
79
  };
80
+ _TokenHelper.inflight = /* @__PURE__ */ new Map();
81
+ var TokenHelper = _TokenHelper;
66
82
 
67
83
  // src/helpers/OAuthHelper.ts
68
84
  function generateCodeVerifier() {
@@ -270,6 +286,12 @@ function isMediaFile(filename) {
270
286
  const lower = filename.toLowerCase();
271
287
  return [...VIDEO_EXTENSIONS, ...IMAGE_EXTENSIONS, ...AUDIO_EXTENSIONS].some((ext) => lower.endsWith(ext));
272
288
  }
289
+ function isPlaylistMedia(url, fileType, name) {
290
+ if (fileType?.startsWith("video/") || fileType?.startsWith("audio/") || fileType === "video" || fileType === "audio") return true;
291
+ const haystack = `${url} ${name || ""}`.toLowerCase();
292
+ if (haystack.includes("stream.mux.com") || haystack.includes("externalvideos/download")) return true;
293
+ return [...VIDEO_EXTENSIONS, ...AUDIO_EXTENSIONS].some((ext) => haystack.includes(ext));
294
+ }
273
295
  function createFolder(id, title, path, thumbnail, isLeaf) {
274
296
  return { type: "folder", id, title, path, thumbnail, isLeaf };
275
297
  }
@@ -943,6 +965,18 @@ var LessonsChurchProvider = class extends BaseProvider {
943
965
  const filtered = allAddOns.filter((a) => a.category === decodedCategory);
944
966
  return filtered.map((addOn) => createFolder(addOn.id, addOn.name, `${currentPath}/${addOn.id}`, addOn.image, true));
945
967
  }
968
+ /** Flatten feed download bundles into printable files, keeping playlist media (videos already in the run-of-show) out. */
969
+ convertFeedDownloads(bundles) {
970
+ const result = [];
971
+ for (const bundle of bundles || []) {
972
+ const files = (bundle.files || []).filter((f) => f.url && !isPlaylistMedia(f.url, f.fileType, f.name));
973
+ for (const file of files) {
974
+ const title = (files.length === 1 ? bundle.name || file.name : file.name || bundle.name) || "Download";
975
+ result.push({ id: file.id, title, url: file.url, fileType: file.fileType, bytes: file.bytes });
976
+ }
977
+ }
978
+ return result;
979
+ }
946
980
  async getInstructions(path, _auth) {
947
981
  const venueId = getSegment(path, 4);
948
982
  if (venueId) {
@@ -954,7 +988,8 @@ var LessonsChurchProvider = class extends BaseProvider {
954
988
  if (!planItemsResponse) return null;
955
989
  const lessonImage = feedResponse?.lessonImage;
956
990
  const sectionActionsMap = buildSectionActionsMap(actionsResponse, lessonImage, feedResponse);
957
- return { name: planItemsResponse.venueName, items: (planItemsResponse.items || []).map((item) => processInstructionItem(item, sectionActionsMap, lessonImage)) };
991
+ const downloads = this.convertFeedDownloads(feedResponse?.downloads);
992
+ return { name: planItemsResponse.venueName, items: (planItemsResponse.items || []).map((item) => processInstructionItem(item, sectionActionsMap, lessonImage)), downloads: downloads.length > 0 ? downloads : void 0 };
958
993
  }
959
994
  const { segments } = parsePath(path);
960
995
  if (segments[0] === "addons" && segments.length === 3) {
@@ -17724,7 +17759,10 @@ var GoCurriculumProvider = class extends BaseProvider {
17724
17759
  if (!files) return null;
17725
17760
  const collection = (await this.getData()).catalog.find((c) => c.id === segments[0]);
17726
17761
  const lesson = collection?.lessons.find((l) => l.id === segments[1]);
17727
- return filesToInstructions(lesson?.name || "Lesson", files);
17762
+ const instructions = filesToInstructions(lesson?.name || "Lesson", files);
17763
+ const downloads = (lesson?.resources || []).filter((r) => r.url && !isPlaylistMedia(r.url, void 0, r.file)).map((r) => ({ id: slugify(r.file || r.title), title: r.title || r.file, url: r.url }));
17764
+ if (downloads.length > 0) instructions.downloads = downloads;
17765
+ return instructions;
17728
17766
  }
17729
17767
  generateCodeVerifier() {
17730
17768
  return this.oauthHelper.generateCodeVerifier();
@@ -17971,7 +18009,7 @@ function getAvailableProviders(ids) {
17971
18009
  }
17972
18010
 
17973
18011
  // src/index.ts
17974
- var VERSION = true ? "0.9.3" : "dev";
18012
+ var VERSION = true ? "0.10.0" : "dev";
17975
18013
  export {
17976
18014
  APlayProvider,
17977
18015
  B1ChurchProvider,
@@ -17989,6 +18027,7 @@ export {
17989
18027
  SignPresenterProvider,
17990
18028
  TokenHelper,
17991
18029
  VERSION,
18030
+ authIsExpired,
17992
18031
  getAllProviders,
17993
18032
  getAvailableProviders,
17994
18033
  getProvider,