@churchapps/content-provider-helper 0.0.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/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/index.cjs +11908 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +733 -0
- package/dist/index.d.ts +733 -0
- package/dist/index.js +11866 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,733 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth token data returned after successful authentication.
|
|
3
|
+
*/
|
|
4
|
+
interface ContentProviderAuthData {
|
|
5
|
+
/** The access token for API requests */
|
|
6
|
+
access_token: string;
|
|
7
|
+
/** The refresh token for obtaining new access tokens */
|
|
8
|
+
refresh_token: string;
|
|
9
|
+
/** Token type, typically "Bearer" */
|
|
10
|
+
token_type: string;
|
|
11
|
+
/** Unix timestamp (seconds) when the token was created */
|
|
12
|
+
created_at: number;
|
|
13
|
+
/** Token lifetime in seconds */
|
|
14
|
+
expires_in: number;
|
|
15
|
+
/** Space-separated list of granted scopes */
|
|
16
|
+
scope: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Configuration for a content provider's API and OAuth settings.
|
|
20
|
+
*/
|
|
21
|
+
interface ContentProviderConfig {
|
|
22
|
+
/** Unique identifier for the provider */
|
|
23
|
+
id: string;
|
|
24
|
+
/** Display name of the provider */
|
|
25
|
+
name: string;
|
|
26
|
+
/** Base URL for API requests */
|
|
27
|
+
apiBase: string;
|
|
28
|
+
/** Base URL for OAuth endpoints */
|
|
29
|
+
oauthBase: string;
|
|
30
|
+
/** OAuth client ID */
|
|
31
|
+
clientId: string;
|
|
32
|
+
/** OAuth scopes to request */
|
|
33
|
+
scopes: string[];
|
|
34
|
+
/** Whether the provider supports device flow authentication */
|
|
35
|
+
supportsDeviceFlow?: boolean;
|
|
36
|
+
/** Endpoint path for device authorization (relative to oauthBase) */
|
|
37
|
+
deviceAuthEndpoint?: string;
|
|
38
|
+
/** API endpoint paths - can be static strings or functions that generate paths */
|
|
39
|
+
endpoints: Record<string, string | ((...args: string[]) => string)>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Response from the device authorization endpoint (RFC 8628).
|
|
43
|
+
*/
|
|
44
|
+
interface DeviceAuthorizationResponse {
|
|
45
|
+
/** Device verification code for polling */
|
|
46
|
+
device_code: string;
|
|
47
|
+
/** User code to display for manual entry */
|
|
48
|
+
user_code: string;
|
|
49
|
+
/** URL where user should authenticate */
|
|
50
|
+
verification_uri: string;
|
|
51
|
+
/** Complete URL with user code pre-filled */
|
|
52
|
+
verification_uri_complete?: string;
|
|
53
|
+
/** Seconds until the codes expire */
|
|
54
|
+
expires_in: number;
|
|
55
|
+
/** Minimum polling interval in seconds */
|
|
56
|
+
interval?: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Current state of a device flow authentication process.
|
|
60
|
+
*/
|
|
61
|
+
interface DeviceFlowState {
|
|
62
|
+
/**
|
|
63
|
+
* Current status of the device flow.
|
|
64
|
+
* - `loading`: Initiating device flow
|
|
65
|
+
* - `awaiting_user`: Waiting for user to authenticate
|
|
66
|
+
* - `polling`: Polling for token
|
|
67
|
+
* - `success`: Authentication completed
|
|
68
|
+
* - `error`: An error occurred
|
|
69
|
+
* - `expired`: Device code expired
|
|
70
|
+
*/
|
|
71
|
+
status: 'loading' | 'awaiting_user' | 'polling' | 'success' | 'error' | 'expired';
|
|
72
|
+
/** Device authorization response data */
|
|
73
|
+
deviceAuth?: DeviceAuthorizationResponse;
|
|
74
|
+
/** Error message if status is 'error' */
|
|
75
|
+
error?: string;
|
|
76
|
+
/** Number of poll attempts made */
|
|
77
|
+
pollCount?: number;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Authentication type supported by a provider.
|
|
81
|
+
* - `none`: No authentication required (public API)
|
|
82
|
+
* - `oauth_pkce`: OAuth 2.0 with PKCE
|
|
83
|
+
* - `device_flow`: OAuth 2.0 Device Authorization Flow (RFC 8628)
|
|
84
|
+
* - `form_login`: Form-based login (username/password) with session cookies
|
|
85
|
+
*/
|
|
86
|
+
type AuthType = 'none' | 'oauth_pkce' | 'device_flow' | 'form_login';
|
|
87
|
+
/**
|
|
88
|
+
* Provider logo URLs for light and dark themes.
|
|
89
|
+
*/
|
|
90
|
+
interface ProviderLogos {
|
|
91
|
+
/** Logo URL for light theme backgrounds */
|
|
92
|
+
light: string;
|
|
93
|
+
/** Logo URL for dark theme backgrounds */
|
|
94
|
+
dark: string;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Information about a content provider.
|
|
98
|
+
*/
|
|
99
|
+
interface ProviderInfo {
|
|
100
|
+
/** Unique identifier for the provider */
|
|
101
|
+
id: string;
|
|
102
|
+
/** Display name of the provider */
|
|
103
|
+
name: string;
|
|
104
|
+
/** Provider logos */
|
|
105
|
+
logos: ProviderLogos;
|
|
106
|
+
/** Whether the provider is fully implemented */
|
|
107
|
+
implemented: boolean;
|
|
108
|
+
/** Whether the provider requires authentication */
|
|
109
|
+
requiresAuth: boolean;
|
|
110
|
+
/** Supported authentication types */
|
|
111
|
+
authTypes: AuthType[];
|
|
112
|
+
/** Provider capabilities */
|
|
113
|
+
capabilities: ProviderCapabilities;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* A folder in the content hierarchy. Can be navigated into to retrieve child items.
|
|
117
|
+
*/
|
|
118
|
+
interface ContentFolder {
|
|
119
|
+
/** Discriminator for type narrowing. Always `'folder'` */
|
|
120
|
+
type: 'folder';
|
|
121
|
+
/** Unique identifier for this folder */
|
|
122
|
+
id: string;
|
|
123
|
+
/** Display title */
|
|
124
|
+
title: string;
|
|
125
|
+
/** Optional thumbnail/cover image URL */
|
|
126
|
+
image?: string;
|
|
127
|
+
/** Provider-specific data for navigation (e.g., level, parentId) */
|
|
128
|
+
providerData?: Record<string, unknown>;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* A playable media file (video or image).
|
|
132
|
+
*/
|
|
133
|
+
interface ContentFile {
|
|
134
|
+
/** Discriminator for type narrowing. Always `'file'` */
|
|
135
|
+
type: 'file';
|
|
136
|
+
/** Unique identifier for this file */
|
|
137
|
+
id: string;
|
|
138
|
+
/** Display title */
|
|
139
|
+
title: string;
|
|
140
|
+
/**
|
|
141
|
+
* Media type of the file.
|
|
142
|
+
* - `video`: Video content (.mp4, .webm, .m3u8, etc.)
|
|
143
|
+
* - `image`: Image content (.jpg, .png, etc.)
|
|
144
|
+
*/
|
|
145
|
+
mediaType: 'video' | 'image';
|
|
146
|
+
/** Optional preview/cover image URL */
|
|
147
|
+
image?: string;
|
|
148
|
+
/** URL to the media file */
|
|
149
|
+
url: string;
|
|
150
|
+
/**
|
|
151
|
+
* Optional URL for embedded preview/player.
|
|
152
|
+
* - For iframe-based providers (e.g., Lessons.church): an embed URL
|
|
153
|
+
* - For direct media providers: same as url, or omitted to use url directly
|
|
154
|
+
*/
|
|
155
|
+
embedUrl?: string;
|
|
156
|
+
/** Mux playback ID for Mux-hosted videos */
|
|
157
|
+
muxPlaybackId?: string;
|
|
158
|
+
/** Decryption key for encrypted media (provider-specific) */
|
|
159
|
+
decryptionKey?: string;
|
|
160
|
+
/** Provider-specific media ID for tracking/licensing */
|
|
161
|
+
mediaId?: string;
|
|
162
|
+
/** URL to ping after 30+ seconds of playback (licensing requirement) */
|
|
163
|
+
pingbackUrl?: string;
|
|
164
|
+
/** Provider-specific data (e.g., duration, loop settings) */
|
|
165
|
+
providerData?: Record<string, unknown>;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* A content item - either a folder or a file.
|
|
169
|
+
*/
|
|
170
|
+
type ContentItem = ContentFolder | ContentFile;
|
|
171
|
+
/**
|
|
172
|
+
* Type guard to check if a ContentItem is a ContentFolder.
|
|
173
|
+
*/
|
|
174
|
+
declare function isContentFolder(item: ContentItem): item is ContentFolder;
|
|
175
|
+
/**
|
|
176
|
+
* Type guard to check if a ContentItem is a ContentFile.
|
|
177
|
+
*/
|
|
178
|
+
declare function isContentFile(item: ContentItem): item is ContentFile;
|
|
179
|
+
/**
|
|
180
|
+
* Result from polling the device flow token endpoint.
|
|
181
|
+
* - `ContentProviderAuthData`: Authentication succeeded
|
|
182
|
+
* - `{ error, shouldSlowDown }`: Still pending or should slow down
|
|
183
|
+
* - `null`: Authentication failed or expired
|
|
184
|
+
*/
|
|
185
|
+
type DeviceFlowPollResult = ContentProviderAuthData | {
|
|
186
|
+
error: string;
|
|
187
|
+
shouldSlowDown?: boolean;
|
|
188
|
+
} | null;
|
|
189
|
+
/**
|
|
190
|
+
* A presentation within a plan section (e.g., a song, video, or activity).
|
|
191
|
+
*/
|
|
192
|
+
interface PlanPresentation {
|
|
193
|
+
/** Unique identifier */
|
|
194
|
+
id: string;
|
|
195
|
+
/** Display name */
|
|
196
|
+
name: string;
|
|
197
|
+
/**
|
|
198
|
+
* Type of action/presentation.
|
|
199
|
+
* - `play`: Main playable content
|
|
200
|
+
* - `add-on`: Supplementary content
|
|
201
|
+
* - `other`: Non-playable item (song lyrics, notes, etc.)
|
|
202
|
+
*/
|
|
203
|
+
actionType: 'play' | 'add-on' | 'other';
|
|
204
|
+
/** Media files associated with this presentation */
|
|
205
|
+
files: ContentFile[];
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* A section within a plan containing multiple presentations.
|
|
209
|
+
*/
|
|
210
|
+
interface PlanSection {
|
|
211
|
+
/** Unique identifier */
|
|
212
|
+
id: string;
|
|
213
|
+
/** Section name (e.g., "Worship", "Message", "Closing") */
|
|
214
|
+
name: string;
|
|
215
|
+
/** Presentations within this section */
|
|
216
|
+
presentations: PlanPresentation[];
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* A complete plan/service with sections and presentations.
|
|
220
|
+
* Returned by `getPresentations()`.
|
|
221
|
+
*/
|
|
222
|
+
interface Plan {
|
|
223
|
+
/** Unique identifier */
|
|
224
|
+
id: string;
|
|
225
|
+
/** Plan name */
|
|
226
|
+
name: string;
|
|
227
|
+
/** Optional description */
|
|
228
|
+
description?: string;
|
|
229
|
+
/** Optional cover image URL */
|
|
230
|
+
image?: string;
|
|
231
|
+
/** Ordered sections in the plan */
|
|
232
|
+
sections: PlanSection[];
|
|
233
|
+
/** Flat list of all files in the plan */
|
|
234
|
+
allFiles: ContentFile[];
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* A file within a venue feed.
|
|
238
|
+
*/
|
|
239
|
+
interface FeedFileInterface {
|
|
240
|
+
id?: string;
|
|
241
|
+
name?: string;
|
|
242
|
+
url?: string;
|
|
243
|
+
streamUrl?: string;
|
|
244
|
+
seconds?: number;
|
|
245
|
+
fileType?: string;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* An action within a venue feed section.
|
|
249
|
+
*/
|
|
250
|
+
interface FeedActionInterface {
|
|
251
|
+
id?: string;
|
|
252
|
+
actionType?: string;
|
|
253
|
+
content?: string;
|
|
254
|
+
files?: FeedFileInterface[];
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* A section within a venue feed.
|
|
258
|
+
*/
|
|
259
|
+
interface FeedSectionInterface {
|
|
260
|
+
id?: string;
|
|
261
|
+
name?: string;
|
|
262
|
+
actions?: FeedActionInterface[];
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Complete venue feed data from Lessons.church API.
|
|
266
|
+
*/
|
|
267
|
+
interface FeedVenueInterface {
|
|
268
|
+
id?: string;
|
|
269
|
+
lessonId?: string;
|
|
270
|
+
name?: string;
|
|
271
|
+
lessonName?: string;
|
|
272
|
+
lessonDescription?: string;
|
|
273
|
+
lessonImage?: string;
|
|
274
|
+
sections?: FeedSectionInterface[];
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* An item in the instruction hierarchy.
|
|
278
|
+
*/
|
|
279
|
+
interface InstructionItem {
|
|
280
|
+
/** Unique identifier */
|
|
281
|
+
id?: string;
|
|
282
|
+
/** Type of instruction item */
|
|
283
|
+
itemType?: string;
|
|
284
|
+
/** ID of related content */
|
|
285
|
+
relatedId?: string;
|
|
286
|
+
/** Display label */
|
|
287
|
+
label?: string;
|
|
288
|
+
/** Description or notes */
|
|
289
|
+
description?: string;
|
|
290
|
+
/** Duration in seconds */
|
|
291
|
+
seconds?: number;
|
|
292
|
+
/** Child instruction items */
|
|
293
|
+
children?: InstructionItem[];
|
|
294
|
+
/** URL for embedded content viewer */
|
|
295
|
+
embedUrl?: string;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Instructions/run sheet for a venue.
|
|
299
|
+
* Returned by `getInstructions()` and `getExpandedInstructions()`.
|
|
300
|
+
*/
|
|
301
|
+
interface Instructions {
|
|
302
|
+
/** Name of the venue */
|
|
303
|
+
venueName?: string;
|
|
304
|
+
/** Hierarchical list of instruction items */
|
|
305
|
+
items: InstructionItem[];
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* An action within a venue section.
|
|
309
|
+
*/
|
|
310
|
+
interface VenueActionInterface {
|
|
311
|
+
id?: string;
|
|
312
|
+
name?: string;
|
|
313
|
+
actionType?: string;
|
|
314
|
+
seconds?: number;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* A section with its actions.
|
|
318
|
+
*/
|
|
319
|
+
interface VenueSectionActionsInterface {
|
|
320
|
+
id?: string;
|
|
321
|
+
name?: string;
|
|
322
|
+
actions?: VenueActionInterface[];
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Response containing venue actions organized by section.
|
|
326
|
+
*/
|
|
327
|
+
interface VenueActionsResponseInterface {
|
|
328
|
+
venueName?: string;
|
|
329
|
+
sections?: VenueSectionActionsInterface[];
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Capabilities supported by a content provider.
|
|
333
|
+
*/
|
|
334
|
+
interface ProviderCapabilities {
|
|
335
|
+
/** Whether the provider supports browsing content hierarchy */
|
|
336
|
+
browse: boolean;
|
|
337
|
+
/** Whether `getPresentations()` returns structured plan data */
|
|
338
|
+
presentations: boolean;
|
|
339
|
+
/** Whether `getPlaylist()` returns a flat list of media files */
|
|
340
|
+
playlist: boolean;
|
|
341
|
+
/** Whether `getInstructions()` returns instruction data */
|
|
342
|
+
instructions: boolean;
|
|
343
|
+
/** Whether `getExpandedInstructions()` returns expanded instruction data */
|
|
344
|
+
expandedInstructions: boolean;
|
|
345
|
+
/** Whether `checkMediaLicense()` returns license information */
|
|
346
|
+
mediaLicensing: boolean;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* License status for media content.
|
|
350
|
+
*/
|
|
351
|
+
type MediaLicenseStatus = 'valid' | 'expired' | 'not_licensed' | 'unknown';
|
|
352
|
+
/**
|
|
353
|
+
* Response from `checkMediaLicense()`.
|
|
354
|
+
*/
|
|
355
|
+
interface MediaLicenseResult {
|
|
356
|
+
/** The media ID that was checked */
|
|
357
|
+
mediaId: string;
|
|
358
|
+
/** Current license status */
|
|
359
|
+
status: MediaLicenseStatus;
|
|
360
|
+
/** Human-readable message about the license */
|
|
361
|
+
message?: string;
|
|
362
|
+
/** When the license expires (ISO 8601 string or Unix timestamp) */
|
|
363
|
+
expiresAt?: string | number;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Detects whether a URL points to a video or image file.
|
|
368
|
+
* @param url - The URL to check
|
|
369
|
+
* @param explicitType - Optional explicit type from API response (e.g., 'video', 'image')
|
|
370
|
+
* @returns 'video' or 'image'
|
|
371
|
+
*/
|
|
372
|
+
declare function detectMediaType(url: string, explicitType?: string): 'video' | 'image';
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Abstract base class for content providers.
|
|
376
|
+
* Extend this class to create a custom provider.
|
|
377
|
+
*
|
|
378
|
+
* ## Main Methods (for consumers)
|
|
379
|
+
*
|
|
380
|
+
* ### Content Browsing
|
|
381
|
+
* - `browse(folder?, auth?)` - Browse content hierarchy (root if no folder, or folder contents)
|
|
382
|
+
*
|
|
383
|
+
* ### Structured Data
|
|
384
|
+
* - `getPresentations(folder, auth?)` - Get structured plan with sections and presentations
|
|
385
|
+
* - `getInstructions(folder, auth?)` - Get instruction/run sheet data
|
|
386
|
+
* - `getExpandedInstructions(folder, auth?)` - Get expanded instructions with actions
|
|
387
|
+
*
|
|
388
|
+
* ### Provider Info
|
|
389
|
+
* - `requiresAuth()` - Whether authentication is needed
|
|
390
|
+
* - `getCapabilities()` - What features the provider supports
|
|
391
|
+
* - `getAuthTypes()` - Supported authentication methods
|
|
392
|
+
*
|
|
393
|
+
* ### Authentication (OAuth 2.0 PKCE)
|
|
394
|
+
* - `buildAuthUrl(codeVerifier, redirectUri)` - Build OAuth authorization URL
|
|
395
|
+
* - `exchangeCodeForTokens(code, codeVerifier, redirectUri)` - Exchange code for tokens
|
|
396
|
+
* - `refreshToken(auth)` - Refresh an expired access token
|
|
397
|
+
* - `isAuthValid(auth)` - Check if auth data is still valid
|
|
398
|
+
*
|
|
399
|
+
* ### Device Flow Authentication (RFC 8628)
|
|
400
|
+
* - `supportsDeviceFlow()` - Whether device flow is supported
|
|
401
|
+
* - `initiateDeviceFlow()` - Start device authorization
|
|
402
|
+
* - `pollDeviceFlowToken(deviceCode)` - Poll for token after user authorizes
|
|
403
|
+
*/
|
|
404
|
+
declare abstract class ContentProvider {
|
|
405
|
+
/** Unique identifier for the provider (e.g., 'lessonschurch', 'aplay') */
|
|
406
|
+
abstract readonly id: string;
|
|
407
|
+
/** Display name of the provider */
|
|
408
|
+
abstract readonly name: string;
|
|
409
|
+
/** Provider logos for light and dark themes */
|
|
410
|
+
abstract readonly logos: ProviderLogos;
|
|
411
|
+
/** Provider configuration including API endpoints and OAuth settings */
|
|
412
|
+
abstract readonly config: ContentProviderConfig;
|
|
413
|
+
/**
|
|
414
|
+
* Browse the content hierarchy. If folder is null/undefined, returns root-level items.
|
|
415
|
+
* If folder is provided, returns items within that folder.
|
|
416
|
+
* @param folder - Optional folder to browse into (null/undefined for root)
|
|
417
|
+
* @param auth - Optional authentication data
|
|
418
|
+
* @returns Array of content items (folders and/or files)
|
|
419
|
+
*/
|
|
420
|
+
abstract browse(folder?: ContentFolder | null, auth?: ContentProviderAuthData | null): Promise<ContentItem[]>;
|
|
421
|
+
/**
|
|
422
|
+
* Get a structured plan with sections and presentations for a folder.
|
|
423
|
+
* @param folder - The folder to get presentations for (typically a venue or playlist)
|
|
424
|
+
* @param auth - Optional authentication data
|
|
425
|
+
* @returns Plan object with sections, presentations, and files, or null if not supported
|
|
426
|
+
*/
|
|
427
|
+
abstract getPresentations(folder: ContentFolder, auth?: ContentProviderAuthData | null): Promise<Plan | null>;
|
|
428
|
+
/**
|
|
429
|
+
* Get a flat list of media files (playlist) for a folder.
|
|
430
|
+
* Override in subclass if the provider supports playlists.
|
|
431
|
+
* @param _folder - The folder to get playlist for (typically a venue or playlist folder)
|
|
432
|
+
* @param _auth - Optional authentication data
|
|
433
|
+
* @param _resolution - Optional resolution hint for video quality
|
|
434
|
+
* @returns Array of ContentFile objects, or null if not supported
|
|
435
|
+
*/
|
|
436
|
+
getPlaylist(_folder: ContentFolder, _auth?: ContentProviderAuthData | null, _resolution?: number): Promise<ContentFile[] | null>;
|
|
437
|
+
/**
|
|
438
|
+
* Get instruction/run sheet data for a folder.
|
|
439
|
+
* Override in subclass if the provider supports instructions.
|
|
440
|
+
* @param _folder - The folder to get instructions for
|
|
441
|
+
* @param _auth - Optional authentication data
|
|
442
|
+
* @returns Instructions object, or null if not supported
|
|
443
|
+
*/
|
|
444
|
+
getInstructions(_folder: ContentFolder, _auth?: ContentProviderAuthData | null): Promise<Instructions | null>;
|
|
445
|
+
/**
|
|
446
|
+
* Get expanded instruction data with actions for a folder.
|
|
447
|
+
* Override in subclass if the provider supports expanded instructions.
|
|
448
|
+
* @param _folder - The folder to get expanded instructions for
|
|
449
|
+
* @param _auth - Optional authentication data
|
|
450
|
+
* @returns Instructions object with expanded action data, or null if not supported
|
|
451
|
+
*/
|
|
452
|
+
getExpandedInstructions(_folder: ContentFolder, _auth?: ContentProviderAuthData | null): Promise<Instructions | null>;
|
|
453
|
+
/**
|
|
454
|
+
* Check if this provider requires authentication.
|
|
455
|
+
* @returns true if authentication is required
|
|
456
|
+
*/
|
|
457
|
+
requiresAuth(): boolean;
|
|
458
|
+
/**
|
|
459
|
+
* Get the capabilities supported by this provider.
|
|
460
|
+
* Override in subclass to indicate supported features.
|
|
461
|
+
* @returns ProviderCapabilities object
|
|
462
|
+
*/
|
|
463
|
+
getCapabilities(): ProviderCapabilities;
|
|
464
|
+
/**
|
|
465
|
+
* Check the license status for a specific media item.
|
|
466
|
+
* Override in subclass if the provider requires license validation.
|
|
467
|
+
* @param _mediaId - The media ID to check
|
|
468
|
+
* @param _auth - Optional authentication data
|
|
469
|
+
* @returns MediaLicenseResult object, or null if not supported
|
|
470
|
+
*/
|
|
471
|
+
checkMediaLicense(_mediaId: string, _auth?: ContentProviderAuthData | null): Promise<MediaLicenseResult | null>;
|
|
472
|
+
/**
|
|
473
|
+
* Get the authentication types supported by this provider.
|
|
474
|
+
* @returns Array of supported AuthType values ('none', 'oauth_pkce', 'device_flow')
|
|
475
|
+
*/
|
|
476
|
+
getAuthTypes(): AuthType[];
|
|
477
|
+
/**
|
|
478
|
+
* Check if the provided auth data is still valid (not expired).
|
|
479
|
+
* @param auth - Authentication data to validate
|
|
480
|
+
* @returns true if auth is valid and not expired
|
|
481
|
+
*/
|
|
482
|
+
isAuthValid(auth: ContentProviderAuthData | null | undefined): boolean;
|
|
483
|
+
/**
|
|
484
|
+
* Check if a token is expired (with 5-minute buffer).
|
|
485
|
+
* @param auth - Authentication data to check
|
|
486
|
+
* @returns true if token is expired or will expire within 5 minutes
|
|
487
|
+
*/
|
|
488
|
+
isTokenExpired(auth: ContentProviderAuthData): boolean;
|
|
489
|
+
/**
|
|
490
|
+
* Generate a random code verifier for PKCE.
|
|
491
|
+
* @returns A 64-character random string
|
|
492
|
+
*/
|
|
493
|
+
generateCodeVerifier(): string;
|
|
494
|
+
/**
|
|
495
|
+
* Generate a code challenge from a code verifier using SHA-256.
|
|
496
|
+
* @param verifier - The code verifier string
|
|
497
|
+
* @returns Base64url-encoded SHA-256 hash of the verifier
|
|
498
|
+
*/
|
|
499
|
+
generateCodeChallenge(verifier: string): Promise<string>;
|
|
500
|
+
/**
|
|
501
|
+
* Build the OAuth authorization URL for PKCE flow.
|
|
502
|
+
* @param codeVerifier - The code verifier (store this for token exchange)
|
|
503
|
+
* @param redirectUri - The redirect URI to return to after authorization
|
|
504
|
+
* @param state - Optional state parameter for CSRF protection (defaults to provider ID)
|
|
505
|
+
* @returns Object with authorization URL and challenge method
|
|
506
|
+
*/
|
|
507
|
+
buildAuthUrl(codeVerifier: string, redirectUri: string, state?: string): Promise<{
|
|
508
|
+
url: string;
|
|
509
|
+
challengeMethod: string;
|
|
510
|
+
}>;
|
|
511
|
+
/**
|
|
512
|
+
* Exchange an authorization code for access and refresh tokens.
|
|
513
|
+
* @param code - The authorization code from the callback
|
|
514
|
+
* @param codeVerifier - The original code verifier used to generate the challenge
|
|
515
|
+
* @param redirectUri - The redirect URI (must match the one used in buildAuthUrl)
|
|
516
|
+
* @returns Authentication data, or null if exchange failed
|
|
517
|
+
*/
|
|
518
|
+
exchangeCodeForTokens(code: string, codeVerifier: string, redirectUri: string): Promise<ContentProviderAuthData | null>;
|
|
519
|
+
/**
|
|
520
|
+
* Refresh an expired access token using the refresh token.
|
|
521
|
+
* @param auth - The current authentication data (must include refresh_token)
|
|
522
|
+
* @returns New authentication data, or null if refresh failed
|
|
523
|
+
*/
|
|
524
|
+
refreshToken(auth: ContentProviderAuthData): Promise<ContentProviderAuthData | null>;
|
|
525
|
+
/**
|
|
526
|
+
* Check if this provider supports device flow authentication.
|
|
527
|
+
* @returns true if device flow is supported
|
|
528
|
+
*/
|
|
529
|
+
supportsDeviceFlow(): boolean;
|
|
530
|
+
/**
|
|
531
|
+
* Initiate the device authorization flow (RFC 8628).
|
|
532
|
+
* @returns Device authorization response with user_code and verification_uri, or null if not supported
|
|
533
|
+
*/
|
|
534
|
+
initiateDeviceFlow(): Promise<DeviceAuthorizationResponse | null>;
|
|
535
|
+
/**
|
|
536
|
+
* Poll for a token after user has authorized the device.
|
|
537
|
+
* @param deviceCode - The device_code from initiateDeviceFlow response
|
|
538
|
+
* @returns Auth data if successful, error object if pending/slow_down, or null if failed/expired
|
|
539
|
+
*/
|
|
540
|
+
pollDeviceFlowToken(deviceCode: string): Promise<DeviceFlowPollResult>;
|
|
541
|
+
/**
|
|
542
|
+
* Calculate the delay between device flow poll attempts.
|
|
543
|
+
* @param baseInterval - Base interval in seconds (default: 5)
|
|
544
|
+
* @param slowDownCount - Number of slow_down responses received
|
|
545
|
+
* @returns Delay in milliseconds
|
|
546
|
+
*/
|
|
547
|
+
calculatePollDelay(baseInterval?: number, slowDownCount?: number): number;
|
|
548
|
+
/**
|
|
549
|
+
* Create authorization headers for API requests.
|
|
550
|
+
* @param auth - Authentication data
|
|
551
|
+
* @returns Headers object with Authorization header, or null if no auth
|
|
552
|
+
*/
|
|
553
|
+
protected createAuthHeaders(auth: ContentProviderAuthData | null | undefined): Record<string, string> | null;
|
|
554
|
+
/**
|
|
555
|
+
* Make an authenticated API request.
|
|
556
|
+
* @param path - API endpoint path (appended to config.apiBase)
|
|
557
|
+
* @param auth - Optional authentication data
|
|
558
|
+
* @param method - HTTP method (default: 'GET')
|
|
559
|
+
* @param body - Optional request body (for POST requests)
|
|
560
|
+
* @returns Parsed JSON response, or null if request failed
|
|
561
|
+
*/
|
|
562
|
+
protected apiRequest<T>(path: string, auth?: ContentProviderAuthData | null, method?: 'GET' | 'POST', body?: unknown): Promise<T | null>;
|
|
563
|
+
/**
|
|
564
|
+
* Helper to create a ContentFolder object.
|
|
565
|
+
* @param id - Unique identifier
|
|
566
|
+
* @param title - Display title
|
|
567
|
+
* @param image - Optional image URL
|
|
568
|
+
* @param providerData - Optional provider-specific data
|
|
569
|
+
* @returns ContentFolder object
|
|
570
|
+
*/
|
|
571
|
+
protected createFolder(id: string, title: string, image?: string, providerData?: Record<string, unknown>): ContentFolder;
|
|
572
|
+
/**
|
|
573
|
+
* Helper to create a ContentFile object with automatic media type detection.
|
|
574
|
+
* @param id - Unique identifier
|
|
575
|
+
* @param title - Display title
|
|
576
|
+
* @param url - Media URL
|
|
577
|
+
* @param options - Optional properties (mediaType, image, muxPlaybackId, providerData)
|
|
578
|
+
* @returns ContentFile object
|
|
579
|
+
*/
|
|
580
|
+
protected createFile(id: string, title: string, url: string, options?: {
|
|
581
|
+
mediaType?: 'video' | 'image';
|
|
582
|
+
image?: string;
|
|
583
|
+
muxPlaybackId?: string;
|
|
584
|
+
providerData?: Record<string, unknown>;
|
|
585
|
+
}): ContentFile;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
declare class APlayProvider extends ContentProvider {
|
|
589
|
+
readonly id = "aplay";
|
|
590
|
+
readonly name = "APlay";
|
|
591
|
+
readonly logos: ProviderLogos;
|
|
592
|
+
readonly config: ContentProviderConfig;
|
|
593
|
+
getCapabilities(): ProviderCapabilities;
|
|
594
|
+
browse(folder?: ContentFolder | null, auth?: ContentProviderAuthData | null): Promise<ContentItem[]>;
|
|
595
|
+
private getProductFolders;
|
|
596
|
+
private getLibraryFolders;
|
|
597
|
+
private getMediaFiles;
|
|
598
|
+
getPresentations(folder: ContentFolder, auth?: ContentProviderAuthData | null): Promise<Plan | null>;
|
|
599
|
+
/**
|
|
600
|
+
* Check the license status for a specific media item.
|
|
601
|
+
* Returns license information including pingback URL if licensed.
|
|
602
|
+
*/
|
|
603
|
+
checkMediaLicense(mediaId: string, auth?: ContentProviderAuthData | null): Promise<MediaLicenseResult | null>;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
declare class SignPresenterProvider extends ContentProvider {
|
|
607
|
+
readonly id = "signpresenter";
|
|
608
|
+
readonly name = "SignPresenter";
|
|
609
|
+
readonly logos: ProviderLogos;
|
|
610
|
+
readonly config: ContentProviderConfig;
|
|
611
|
+
getCapabilities(): ProviderCapabilities;
|
|
612
|
+
browse(folder?: ContentFolder | null, auth?: ContentProviderAuthData | null): Promise<ContentItem[]>;
|
|
613
|
+
private getMessages;
|
|
614
|
+
getPresentations(folder: ContentFolder, auth?: ContentProviderAuthData | null): Promise<Plan | null>;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
declare class LessonsChurchProvider extends ContentProvider {
|
|
618
|
+
readonly id = "lessonschurch";
|
|
619
|
+
readonly name = "Lessons.church";
|
|
620
|
+
readonly logos: ProviderLogos;
|
|
621
|
+
readonly config: ContentProviderConfig;
|
|
622
|
+
requiresAuth(): boolean;
|
|
623
|
+
getCapabilities(): ProviderCapabilities;
|
|
624
|
+
getPlaylist(folder: ContentFolder, _auth?: ContentProviderAuthData | null, resolution?: number): Promise<ContentFile[] | null>;
|
|
625
|
+
protected apiRequest<T>(path: string): Promise<T | null>;
|
|
626
|
+
browse(folder?: ContentFolder | null, _auth?: ContentProviderAuthData | null, resolution?: number): Promise<ContentItem[]>;
|
|
627
|
+
private getPrograms;
|
|
628
|
+
private getStudies;
|
|
629
|
+
private getLessons;
|
|
630
|
+
private getVenues;
|
|
631
|
+
private getPlaylistFiles;
|
|
632
|
+
private getAddOnCategories;
|
|
633
|
+
private getAddOnsByCategory;
|
|
634
|
+
private convertAddOnToFile;
|
|
635
|
+
getPresentations(folder: ContentFolder, _auth?: ContentProviderAuthData | null, resolution?: number): Promise<Plan | null>;
|
|
636
|
+
getInstructions(folder: ContentFolder, _auth?: ContentProviderAuthData | null): Promise<Instructions | null>;
|
|
637
|
+
getExpandedInstructions(folder: ContentFolder, _auth?: ContentProviderAuthData | null): Promise<Instructions | null>;
|
|
638
|
+
private getEmbedUrl;
|
|
639
|
+
private convertVenueToPlan;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
declare class B1ChurchProvider extends ContentProvider {
|
|
643
|
+
readonly id = "b1church";
|
|
644
|
+
readonly name = "B1.Church";
|
|
645
|
+
readonly logos: ProviderLogos;
|
|
646
|
+
readonly config: ContentProviderConfig;
|
|
647
|
+
private appBase;
|
|
648
|
+
requiresAuth(): boolean;
|
|
649
|
+
getCapabilities(): ProviderCapabilities;
|
|
650
|
+
buildAuthUrl(_codeVerifier: string, redirectUri: string, state?: string): Promise<{
|
|
651
|
+
url: string;
|
|
652
|
+
challengeMethod: string;
|
|
653
|
+
}>;
|
|
654
|
+
exchangeCodeForTokensWithSecret(code: string, redirectUri: string, clientSecret: string): Promise<ContentProviderAuthData | null>;
|
|
655
|
+
refreshTokenWithSecret(authData: ContentProviderAuthData, clientSecret: string): Promise<ContentProviderAuthData | null>;
|
|
656
|
+
initiateDeviceFlow(): Promise<DeviceAuthorizationResponse | null>;
|
|
657
|
+
pollDeviceFlowToken(deviceCode: string): Promise<DeviceFlowPollResult>;
|
|
658
|
+
/**
|
|
659
|
+
* Browse content hierarchy:
|
|
660
|
+
* - Root: List of ministries (groups with "ministry" tag)
|
|
661
|
+
* - Ministry: List of plan types
|
|
662
|
+
* - PlanType: List of plans (leaf nodes)
|
|
663
|
+
*
|
|
664
|
+
* Plans are leaf nodes - use getPresentations(), getPlaylist(), getInstructions()
|
|
665
|
+
* to get plan content.
|
|
666
|
+
*/
|
|
667
|
+
browse(folder?: ContentFolder | null, authData?: ContentProviderAuthData | null): Promise<ContentItem[]>;
|
|
668
|
+
getPresentations(folder: ContentFolder, authData?: ContentProviderAuthData | null): Promise<Plan | null>;
|
|
669
|
+
getInstructions(folder: ContentFolder, authData?: ContentProviderAuthData | null): Promise<Instructions | null>;
|
|
670
|
+
getPlaylist(folder: ContentFolder, authData?: ContentProviderAuthData | null): Promise<ContentFile[]>;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
declare class PlanningCenterProvider extends ContentProvider {
|
|
674
|
+
readonly id = "planningcenter";
|
|
675
|
+
readonly name = "Planning Center";
|
|
676
|
+
readonly logos: ProviderLogos;
|
|
677
|
+
readonly config: ContentProviderConfig;
|
|
678
|
+
private readonly ONE_WEEK_MS;
|
|
679
|
+
requiresAuth(): boolean;
|
|
680
|
+
getCapabilities(): ProviderCapabilities;
|
|
681
|
+
browse(folder?: ContentFolder | null, auth?: ContentProviderAuthData | null): Promise<ContentItem[]>;
|
|
682
|
+
private getPlans;
|
|
683
|
+
private getPlanItems;
|
|
684
|
+
getPresentations(folder: ContentFolder, auth?: ContentProviderAuthData | null): Promise<Plan | null>;
|
|
685
|
+
private convertToPresentation;
|
|
686
|
+
private convertSongToPresentation;
|
|
687
|
+
private convertMediaToPresentation;
|
|
688
|
+
private formatDate;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
declare class BibleProjectProvider extends ContentProvider {
|
|
692
|
+
readonly id = "bibleproject";
|
|
693
|
+
readonly name = "The Bible Project";
|
|
694
|
+
readonly logos: ProviderLogos;
|
|
695
|
+
readonly config: ContentProviderConfig;
|
|
696
|
+
private data;
|
|
697
|
+
requiresAuth(): boolean;
|
|
698
|
+
getCapabilities(): ProviderCapabilities;
|
|
699
|
+
browse(folder?: ContentFolder | null, _auth?: ContentProviderAuthData | null): Promise<ContentItem[]>;
|
|
700
|
+
getPresentations(_folder: ContentFolder, _auth?: ContentProviderAuthData | null): Promise<Plan | null>;
|
|
701
|
+
private getLessonFolders;
|
|
702
|
+
private slugify;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Get a provider by ID.
|
|
707
|
+
*/
|
|
708
|
+
declare function getProvider(providerId: string): ContentProvider | null;
|
|
709
|
+
/**
|
|
710
|
+
* Get all registered providers.
|
|
711
|
+
*/
|
|
712
|
+
declare function getAllProviders(): ContentProvider[];
|
|
713
|
+
/**
|
|
714
|
+
* Register a custom provider.
|
|
715
|
+
*/
|
|
716
|
+
declare function registerProvider(provider: ContentProvider): void;
|
|
717
|
+
/**
|
|
718
|
+
* Get provider configuration by ID (for backward compatibility).
|
|
719
|
+
*/
|
|
720
|
+
declare function getProviderConfig(providerId: string): ContentProviderConfig | null;
|
|
721
|
+
/**
|
|
722
|
+
* Get list of available providers with their info including logos and auth types.
|
|
723
|
+
* Includes both implemented providers and coming soon providers.
|
|
724
|
+
*/
|
|
725
|
+
declare function getAvailableProviders(): ProviderInfo[];
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* @churchapps/content-provider-helper
|
|
729
|
+
* Helper classes for interacting with third party content providers
|
|
730
|
+
*/
|
|
731
|
+
declare const VERSION = "0.0.1";
|
|
732
|
+
|
|
733
|
+
export { APlayProvider, type AuthType, B1ChurchProvider, BibleProjectProvider, type ContentFile, type ContentFolder, type ContentItem, ContentProvider, type ContentProviderAuthData, type ContentProviderConfig, type DeviceAuthorizationResponse, type DeviceFlowPollResult, type DeviceFlowState, type FeedActionInterface, type FeedFileInterface, type FeedSectionInterface, type FeedVenueInterface, type InstructionItem, type Instructions, LessonsChurchProvider, type MediaLicenseResult, type MediaLicenseStatus, type Plan, type PlanPresentation, type PlanSection, PlanningCenterProvider, type ProviderCapabilities, type ProviderInfo, type ProviderLogos, SignPresenterProvider, VERSION, type VenueActionInterface, type VenueActionsResponseInterface, type VenueSectionActionsInterface, detectMediaType, getAllProviders, getAvailableProviders, getProvider, getProviderConfig, isContentFile, isContentFolder, registerProvider };
|