@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/README.md +8 -2
- package/dist/index.cjs +49 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -109
- package/dist/index.d.ts +31 -109
- package/dist/index.js +49 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
//
|
|
64
|
-
//
|
|
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
|
|
156
|
+
state
|
|
154
157
|
});
|
|
155
|
-
return
|
|
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:
|
|
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
|
|
@@ -596,13 +615,15 @@ var SignPresenterProvider = class extends BaseProvider {
|
|
|
596
615
|
const instructions = filesToInstructions(title, files, { id: playlistId + "-section", label: title });
|
|
597
616
|
return { name: title, items: [{ id: playlistId + "-header", itemType: "header", label: title, children: instructions.items }] };
|
|
598
617
|
}
|
|
599
|
-
// Auth methods
|
|
600
618
|
generateCodeVerifier() {
|
|
601
619
|
return this.oauthHelper.generateCodeVerifier();
|
|
602
620
|
}
|
|
603
621
|
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
604
622
|
return this.oauthHelper.buildAuthUrl(this.config, codeVerifier, redirectUri, state || this.id);
|
|
605
623
|
}
|
|
624
|
+
buildAuthUrlFromChallenge(codeChallenge, redirectUri, state) {
|
|
625
|
+
return this.oauthHelper.buildAuthUrlFromChallenge(this.config, codeChallenge, redirectUri, state);
|
|
626
|
+
}
|
|
606
627
|
async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
|
|
607
628
|
return this.oauthHelper.exchangeCodeForTokens(this.config, this.id, code, codeVerifier, redirectUri);
|
|
608
629
|
}
|
|
@@ -975,8 +996,7 @@ function registerProvider(provider) {
|
|
|
975
996
|
}
|
|
976
997
|
|
|
977
998
|
// src/providers/b1Church/B1ChurchAuth.ts
|
|
978
|
-
|
|
979
|
-
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
999
|
+
function buildB1AuthUrlFromChallenge(config, appBase, redirectUri, codeChallenge, state) {
|
|
980
1000
|
const oauthParams = new URLSearchParams({
|
|
981
1001
|
response_type: "code",
|
|
982
1002
|
client_id: config.clientId,
|
|
@@ -984,10 +1004,13 @@ async function buildB1AuthUrl(config, appBase, redirectUri, codeVerifier, state)
|
|
|
984
1004
|
scope: config.scopes.join(" "),
|
|
985
1005
|
code_challenge: codeChallenge,
|
|
986
1006
|
code_challenge_method: "S256",
|
|
987
|
-
state
|
|
1007
|
+
state
|
|
988
1008
|
});
|
|
989
|
-
|
|
990
|
-
|
|
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" };
|
|
991
1014
|
}
|
|
992
1015
|
async function exchangeCodeForTokensWithPKCE(config, code, redirectUri, codeVerifier) {
|
|
993
1016
|
try {
|
|
@@ -1219,6 +1242,7 @@ function collectFilesFromNode(node) {
|
|
|
1219
1242
|
id: node.id,
|
|
1220
1243
|
name: node.label || "",
|
|
1221
1244
|
url: node.downloadUrl,
|
|
1245
|
+
fileType: instructionItemMediaType(node),
|
|
1222
1246
|
seconds: node.seconds || 10
|
|
1223
1247
|
});
|
|
1224
1248
|
}
|
|
@@ -1284,6 +1308,9 @@ function findFirstThumbnail(items) {
|
|
|
1284
1308
|
}
|
|
1285
1309
|
return void 0;
|
|
1286
1310
|
}
|
|
1311
|
+
function isLessonsChurchContent(plan) {
|
|
1312
|
+
return !plan.providerId || plan.providerId === "lessonschurch";
|
|
1313
|
+
}
|
|
1287
1314
|
function isExternalProviderItem(item) {
|
|
1288
1315
|
if (!item.providerId || item.providerId === "b1church") return false;
|
|
1289
1316
|
if (item.providerPath) return true;
|
|
@@ -1310,10 +1337,16 @@ var B1ChurchProvider = class extends BaseProvider {
|
|
|
1310
1337
|
this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
|
|
1311
1338
|
this.planTypeId = null;
|
|
1312
1339
|
}
|
|
1340
|
+
generateCodeVerifier() {
|
|
1341
|
+
return generateCodeVerifier();
|
|
1342
|
+
}
|
|
1313
1343
|
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
1314
1344
|
return buildB1AuthUrl(this.config, this.appBase, redirectUri, codeVerifier, state);
|
|
1315
1345
|
}
|
|
1316
|
-
|
|
1346
|
+
buildAuthUrlFromChallenge(codeChallenge, redirectUri, state) {
|
|
1347
|
+
return buildB1AuthUrlFromChallenge(this.config, this.appBase, redirectUri, codeChallenge, state);
|
|
1348
|
+
}
|
|
1349
|
+
async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
|
|
1317
1350
|
return exchangeCodeForTokensWithPKCE(this.config, code, redirectUri, codeVerifier);
|
|
1318
1351
|
}
|
|
1319
1352
|
async exchangeCodeForTokensWithSecret(code, redirectUri, clientSecret) {
|
|
@@ -1449,7 +1482,7 @@ var B1ChurchProvider = class extends BaseProvider {
|
|
|
1449
1482
|
const planFolder = plans.find((p) => p.id === planId);
|
|
1450
1483
|
if (!planFolder) return null;
|
|
1451
1484
|
const churchId = planFolder.churchId;
|
|
1452
|
-
const venueId = planFolder.contentId;
|
|
1485
|
+
const venueId = isLessonsChurchContent(planFolder) ? planFolder.contentId : void 0;
|
|
1453
1486
|
const planTitle = planFolder.name || "Plan";
|
|
1454
1487
|
if (!churchId) {
|
|
1455
1488
|
console.warn("[B1Church getInstructions] planFolder missing churchId:", planFolder.id);
|
|
@@ -1558,7 +1591,7 @@ var B1ChurchProvider = class extends BaseProvider {
|
|
|
1558
1591
|
return null;
|
|
1559
1592
|
}
|
|
1560
1593
|
const churchId = planFolder.churchId;
|
|
1561
|
-
const venueId = planFolder.contentId;
|
|
1594
|
+
const venueId = isLessonsChurchContent(planFolder) ? planFolder.contentId : void 0;
|
|
1562
1595
|
if (!churchId) {
|
|
1563
1596
|
console.warn("[B1Church] getPlaylist: planFolder missing churchId:", planFolder.id);
|
|
1564
1597
|
return null;
|
|
@@ -1582,7 +1615,7 @@ var B1ChurchProvider = class extends BaseProvider {
|
|
|
1582
1615
|
const venueFeed = venueId ? await fetchVenueFeed(venueId) : null;
|
|
1583
1616
|
const files = [];
|
|
1584
1617
|
for (const sectionItem of planItems) {
|
|
1585
|
-
for (const child of sectionItem.children
|
|
1618
|
+
for (const child of sectionItem.children?.length ? sectionItem.children : [sectionItem]) {
|
|
1586
1619
|
const childItemType = child.itemType;
|
|
1587
1620
|
const isSectionType = childItemType === "section" || childItemType === "lessonSection" || childItemType === "providerSection";
|
|
1588
1621
|
const canExpandLocally = isSectionType && venueFeed && child.relatedId;
|
|
@@ -1713,7 +1746,6 @@ var DropboxProvider = class extends BaseProvider {
|
|
|
1713
1746
|
mediaLicensing: false
|
|
1714
1747
|
};
|
|
1715
1748
|
}
|
|
1716
|
-
// -- Pagination helper --
|
|
1717
1749
|
dropboxPost(endpoint, body, auth) {
|
|
1718
1750
|
if (!auth) {
|
|
1719
1751
|
console.error("[Dropbox] No auth token provided");
|
|
@@ -1747,7 +1779,6 @@ var DropboxProvider = class extends BaseProvider {
|
|
|
1747
1779
|
}
|
|
1748
1780
|
return allEntries;
|
|
1749
1781
|
}
|
|
1750
|
-
// -- File link helpers --
|
|
1751
1782
|
async getSharedLink(filePath, auth) {
|
|
1752
1783
|
if (!auth) return null;
|
|
1753
1784
|
try {
|
|
@@ -1803,7 +1834,6 @@ var DropboxProvider = class extends BaseProvider {
|
|
|
1803
1834
|
);
|
|
1804
1835
|
return results.filter((r) => r.viewUrl || r.downloadUrl).map((r) => fileEntryToContentFile(r.entry, r.viewUrl || r.downloadUrl, r.downloadUrl));
|
|
1805
1836
|
}
|
|
1806
|
-
// -- Leaf detection --
|
|
1807
1837
|
async checkIsLeaf(folderPath, auth) {
|
|
1808
1838
|
const response = await this.dropboxPost(
|
|
1809
1839
|
"/2/files/list_folder",
|
|
@@ -1813,7 +1843,6 @@ var DropboxProvider = class extends BaseProvider {
|
|
|
1813
1843
|
if (!response) return false;
|
|
1814
1844
|
return !response.entries.some((e) => e[".tag"] === "folder");
|
|
1815
1845
|
}
|
|
1816
|
-
// -- IProvider methods --
|
|
1817
1846
|
async browse(path, auth) {
|
|
1818
1847
|
const dropboxPath = path && path !== "/" ? path.startsWith("/") ? path : "/" + path : "";
|
|
1819
1848
|
const allEntries = await this.listAllEntries(dropboxPath, auth);
|
|
@@ -1825,7 +1854,6 @@ var DropboxProvider = class extends BaseProvider {
|
|
|
1825
1854
|
const fileItems = await this.resolveFileLinks(mediaFiles, auth);
|
|
1826
1855
|
return [...folderItems, ...fileItems];
|
|
1827
1856
|
}
|
|
1828
|
-
/** Resolve a folder path to playable files (list + media filter + link resolution). */
|
|
1829
1857
|
async resolveMediaFiles(path, auth) {
|
|
1830
1858
|
if (!path || path === "/") return null;
|
|
1831
1859
|
const dropboxPath = path.startsWith("/") ? path : "/" + path;
|
|
@@ -1847,7 +1875,6 @@ var DropboxProvider = class extends BaseProvider {
|
|
|
1847
1875
|
const folderName = segments[segments.length - 1] || "Dropbox";
|
|
1848
1876
|
return filesToInstructions(folderName, resolved.files, { id: "dropbox-section", label: folderName });
|
|
1849
1877
|
}
|
|
1850
|
-
// -- Auth methods --
|
|
1851
1878
|
generateCodeVerifier() {
|
|
1852
1879
|
return this.oauthHelper.generateCodeVerifier();
|
|
1853
1880
|
}
|
|
@@ -1904,7 +1931,8 @@ var PlanningCenterProvider = class extends BaseProvider {
|
|
|
1904
1931
|
this.logos = { light: "https://www.planningcenter.com/icons/icon-512x512.png", dark: "https://www.planningcenter.com/icons/icon-512x512.png" };
|
|
1905
1932
|
this.config = { id: "planningcenter", name: "Planning Center", apiBase: "https://api.planningcenteronline.com", oauthBase: "https://api.planningcenteronline.com/oauth", clientId: "", scopes: ["services"] };
|
|
1906
1933
|
this.requiresAuth = true;
|
|
1907
|
-
|
|
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 = [];
|
|
1908
1936
|
// Plan items come back without media URLs, so playlist/instructions are not actually supported yet.
|
|
1909
1937
|
this.capabilities = { browse: true, playlist: false, instructions: false, mediaLicensing: false };
|
|
1910
1938
|
}
|
|
@@ -16367,7 +16395,7 @@ function getAvailableProviders(ids) {
|
|
|
16367
16395
|
}
|
|
16368
16396
|
|
|
16369
16397
|
// src/index.ts
|
|
16370
|
-
var VERSION = true ? "0.
|
|
16398
|
+
var VERSION = true ? "0.6.0" : "dev";
|
|
16371
16399
|
// Annotate the CommonJS export names for ESM import in node:
|
|
16372
16400
|
0 && (module.exports = {
|
|
16373
16401
|
APlayProvider,
|