@classytic/social 0.1.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/CHANGELOG.md +65 -0
- package/LICENSE +21 -0
- package/README.md +368 -0
- package/dist/base-Bw7e52V8.mjs +246 -0
- package/dist/base-Bw7e52V8.mjs.map +1 -0
- package/dist/base-DBtKFiSX.d.mts +226 -0
- package/dist/base-DBtKFiSX.d.mts.map +1 -0
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/client/index.d.mts +44 -0
- package/dist/client/index.d.mts.map +1 -0
- package/dist/client/index.mjs +154 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/common/index.d.mts +3 -0
- package/dist/common/index.mjs +7 -0
- package/dist/contracts-Cdwa4zlg.d.mts +121 -0
- package/dist/contracts-Cdwa4zlg.d.mts.map +1 -0
- package/dist/contracts-lCa069IK.mjs +221 -0
- package/dist/contracts-lCa069IK.mjs.map +1 -0
- package/dist/env-Bl0cwwjC.mjs +955 -0
- package/dist/env-Bl0cwwjC.mjs.map +1 -0
- package/dist/env-DxOZHf0p.d.mts +394 -0
- package/dist/env-DxOZHf0p.d.mts.map +1 -0
- package/dist/errors-Cm6LeKf7.mjs +32 -0
- package/dist/errors-Cm6LeKf7.mjs.map +1 -0
- package/dist/facebook-l_4CghaA.mjs +95 -0
- package/dist/facebook-l_4CghaA.mjs.map +1 -0
- package/dist/http-DpcLSR1M.mjs +197 -0
- package/dist/http-DpcLSR1M.mjs.map +1 -0
- package/dist/index.d.mts +42 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +71 -0
- package/dist/index.mjs.map +1 -0
- package/dist/instagram-BGaeUFU2.mjs +90 -0
- package/dist/instagram-BGaeUFU2.mjs.map +1 -0
- package/dist/linkedin-70whtVKa.mjs +101 -0
- package/dist/linkedin-70whtVKa.mjs.map +1 -0
- package/dist/meta-D3vcJU1c.mjs +126 -0
- package/dist/meta-D3vcJU1c.mjs.map +1 -0
- package/dist/pkce-jq5II68b.mjs +72 -0
- package/dist/pkce-jq5II68b.mjs.map +1 -0
- package/dist/polling-DZ1apXtA.mjs +25 -0
- package/dist/polling-DZ1apXtA.mjs.map +1 -0
- package/dist/providers/facebook.d.mts +135 -0
- package/dist/providers/facebook.d.mts.map +1 -0
- package/dist/providers/facebook.mjs +450 -0
- package/dist/providers/facebook.mjs.map +1 -0
- package/dist/providers/instagram.d.mts +122 -0
- package/dist/providers/instagram.d.mts.map +1 -0
- package/dist/providers/instagram.mjs +496 -0
- package/dist/providers/instagram.mjs.map +1 -0
- package/dist/providers/linkedin.d.mts +145 -0
- package/dist/providers/linkedin.d.mts.map +1 -0
- package/dist/providers/linkedin.mjs +574 -0
- package/dist/providers/linkedin.mjs.map +1 -0
- package/dist/providers/reddit.d.mts +102 -0
- package/dist/providers/reddit.d.mts.map +1 -0
- package/dist/providers/reddit.mjs +657 -0
- package/dist/providers/reddit.mjs.map +1 -0
- package/dist/providers/telegram.d.mts +139 -0
- package/dist/providers/telegram.d.mts.map +1 -0
- package/dist/providers/telegram.mjs +517 -0
- package/dist/providers/telegram.mjs.map +1 -0
- package/dist/providers/tiktok.d.mts +116 -0
- package/dist/providers/tiktok.d.mts.map +1 -0
- package/dist/providers/tiktok.mjs +676 -0
- package/dist/providers/tiktok.mjs.map +1 -0
- package/dist/providers/twitter.d.mts +150 -0
- package/dist/providers/twitter.d.mts.map +1 -0
- package/dist/providers/twitter.mjs +628 -0
- package/dist/providers/twitter.mjs.map +1 -0
- package/dist/providers/whatsapp.d.mts +79 -0
- package/dist/providers/whatsapp.d.mts.map +1 -0
- package/dist/providers/whatsapp.mjs +376 -0
- package/dist/providers/whatsapp.mjs.map +1 -0
- package/dist/providers/youtube.d.mts +153 -0
- package/dist/providers/youtube.d.mts.map +1 -0
- package/dist/providers/youtube.mjs +902 -0
- package/dist/providers/youtube.mjs.map +1 -0
- package/dist/reddit-B10kS4Se.mjs +126 -0
- package/dist/reddit-B10kS4Se.mjs.map +1 -0
- package/dist/schemas/index.d.mts +819 -0
- package/dist/schemas/index.d.mts.map +1 -0
- package/dist/schemas/index.mjs +31 -0
- package/dist/schemas/index.mjs.map +1 -0
- package/dist/security-BXhfebWm.d.mts +338 -0
- package/dist/security-BXhfebWm.d.mts.map +1 -0
- package/dist/shared-Fvc6xQku.mjs +100 -0
- package/dist/shared-Fvc6xQku.mjs.map +1 -0
- package/dist/telegram-FaUHpZgB.mjs +107 -0
- package/dist/telegram-FaUHpZgB.mjs.map +1 -0
- package/dist/tiktok-B_bMk4G-.mjs +94 -0
- package/dist/tiktok-B_bMk4G-.mjs.map +1 -0
- package/dist/twitter-BC22zfuc.mjs +98 -0
- package/dist/twitter-BC22zfuc.mjs.map +1 -0
- package/dist/types-BFE4psYI.d.mts +102 -0
- package/dist/types-BFE4psYI.d.mts.map +1 -0
- package/dist/types-Bv27tcT0.d.mts +230 -0
- package/dist/types-Bv27tcT0.d.mts.map +1 -0
- package/dist/types-BwkKyqpi.d.mts +253 -0
- package/dist/types-BwkKyqpi.d.mts.map +1 -0
- package/dist/types-CJrHMDV9.mjs +27 -0
- package/dist/types-CJrHMDV9.mjs.map +1 -0
- package/dist/types-ClbVc2rc.d.mts +117 -0
- package/dist/types-ClbVc2rc.d.mts.map +1 -0
- package/dist/types-D91N16Ym.d.mts +242 -0
- package/dist/types-D91N16Ym.d.mts.map +1 -0
- package/dist/types-DfLp_ibQ.d.mts +178 -0
- package/dist/types-DfLp_ibQ.d.mts.map +1 -0
- package/dist/types-DfjDgEoJ.d.mts +88 -0
- package/dist/types-DfjDgEoJ.d.mts.map +1 -0
- package/dist/types-Dp5Z9VBr.mjs +23 -0
- package/dist/types-Dp5Z9VBr.mjs.map +1 -0
- package/dist/types-hriBJTsU.d.mts +129 -0
- package/dist/types-hriBJTsU.d.mts.map +1 -0
- package/dist/types-rn6UuLL8.d.mts +184 -0
- package/dist/types-rn6UuLL8.d.mts.map +1 -0
- package/dist/whatsapp-CFp7ryR4.mjs +101 -0
- package/dist/whatsapp-CFp7ryR4.mjs.map +1 -0
- package/dist/youtube-Bs0fdY7H.mjs +98 -0
- package/dist/youtube-Bs0fdY7H.mjs.map +1 -0
- package/package.json +148 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-DQk6qfdC.mjs";
|
|
2
|
+
import { i as OAuthTokensSchema, r as NonEmptyString } from "./shared-Fvc6xQku.mjs";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
//#region src/schemas/linkedin.ts
|
|
6
|
+
/**
|
|
7
|
+
* LinkedIn zod v4 schemas.
|
|
8
|
+
*/
|
|
9
|
+
var linkedin_exports = /* @__PURE__ */ __exportAll({
|
|
10
|
+
LinkedInArticlePostSchema: () => LinkedInArticlePostSchema,
|
|
11
|
+
LinkedInCredentialDataSchema: () => LinkedInCredentialDataSchema,
|
|
12
|
+
LinkedInCredentialsSchema: () => LinkedInCredentialsSchema,
|
|
13
|
+
LinkedInImagePostSchema: () => LinkedInImagePostSchema,
|
|
14
|
+
LinkedInLifecycleState: () => LinkedInLifecycleState,
|
|
15
|
+
LinkedInTextPostSchema: () => LinkedInTextPostSchema,
|
|
16
|
+
LinkedInUrn: () => LinkedInUrn,
|
|
17
|
+
LinkedInVideoUploadSchema: () => LinkedInVideoUploadSchema,
|
|
18
|
+
LinkedInVisibility: () => LinkedInVisibility,
|
|
19
|
+
linkedinCapabilities: () => linkedinCapabilities,
|
|
20
|
+
linkedinInfo: () => linkedinInfo
|
|
21
|
+
});
|
|
22
|
+
const LinkedInCredentialsSchema = z.object({
|
|
23
|
+
clientId: NonEmptyString.describe("LinkedIn app client ID"),
|
|
24
|
+
clientSecret: NonEmptyString.describe("LinkedIn app client secret"),
|
|
25
|
+
redirectUri: z.url().optional()
|
|
26
|
+
});
|
|
27
|
+
const LinkedInCredentialDataSchema = LinkedInCredentialsSchema.extend({ oauthTokenData: z.union([z.string(), OAuthTokensSchema]).optional() });
|
|
28
|
+
const LinkedInVisibility = z.enum([
|
|
29
|
+
"PUBLIC",
|
|
30
|
+
"CONNECTIONS",
|
|
31
|
+
"LOGGED_IN",
|
|
32
|
+
"CONTAINER"
|
|
33
|
+
]);
|
|
34
|
+
const LinkedInLifecycleState = z.enum([
|
|
35
|
+
"DRAFT",
|
|
36
|
+
"PUBLISHED",
|
|
37
|
+
"PUBLISHED_EDITED"
|
|
38
|
+
]);
|
|
39
|
+
const LinkedInUrn = z.string().regex(/^urn:li:(person|organization|share|ugcPost):[A-Za-z0-9_-]+$/, "Must be a LinkedIn URN (e.g., urn:li:person:abc123)");
|
|
40
|
+
const LinkedInTextPostSchema = z.object({
|
|
41
|
+
authorUrn: LinkedInUrn,
|
|
42
|
+
text: z.string().min(1).max(3e3),
|
|
43
|
+
visibility: LinkedInVisibility.optional().default("PUBLIC")
|
|
44
|
+
});
|
|
45
|
+
const LinkedInArticlePostSchema = z.object({
|
|
46
|
+
authorUrn: LinkedInUrn,
|
|
47
|
+
text: z.string().max(3e3).optional(),
|
|
48
|
+
url: z.url(),
|
|
49
|
+
title: z.string().max(400).optional(),
|
|
50
|
+
description: z.string().max(4096).optional(),
|
|
51
|
+
visibility: LinkedInVisibility.optional().default("PUBLIC")
|
|
52
|
+
});
|
|
53
|
+
const LinkedInImagePostSchema = z.object({
|
|
54
|
+
authorUrn: LinkedInUrn,
|
|
55
|
+
text: z.string().max(3e3),
|
|
56
|
+
imageBuffer: z.instanceof(Uint8Array).optional().describe("Raw image bytes — provider downloads from imageUrl if absent"),
|
|
57
|
+
imageUrl: z.url().optional(),
|
|
58
|
+
visibility: LinkedInVisibility.optional().default("PUBLIC")
|
|
59
|
+
}).refine((d) => d.imageBuffer || d.imageUrl, { message: "Provide either imageBuffer or imageUrl" });
|
|
60
|
+
const LinkedInVideoUploadSchema = z.object({
|
|
61
|
+
authorUrn: LinkedInUrn,
|
|
62
|
+
text: z.string().max(3e3).optional(),
|
|
63
|
+
videoUrl: z.url(),
|
|
64
|
+
visibility: LinkedInVisibility.optional().default("PUBLIC")
|
|
65
|
+
});
|
|
66
|
+
const linkedinCapabilities = {
|
|
67
|
+
auth: "oauth2",
|
|
68
|
+
posting: true,
|
|
69
|
+
upload: true,
|
|
70
|
+
messaging: false,
|
|
71
|
+
scheduling: false,
|
|
72
|
+
deletion: true,
|
|
73
|
+
listing: false,
|
|
74
|
+
analytics: true,
|
|
75
|
+
environments: false,
|
|
76
|
+
pkce: false
|
|
77
|
+
};
|
|
78
|
+
const linkedinInfo = {
|
|
79
|
+
name: "linkedin",
|
|
80
|
+
displayName: "LinkedIn",
|
|
81
|
+
scopes: [
|
|
82
|
+
"openid",
|
|
83
|
+
"profile",
|
|
84
|
+
"email",
|
|
85
|
+
"w_member_social",
|
|
86
|
+
"w_organization_social"
|
|
87
|
+
],
|
|
88
|
+
rateLimits: { perDay: 100 },
|
|
89
|
+
fileLimits: {
|
|
90
|
+
image: { maxSizeBytes: 5 * 1024 * 1024 },
|
|
91
|
+
video: {
|
|
92
|
+
maxSizeBytes: 5 * 1024 ** 3,
|
|
93
|
+
maxDurationSec: 1800
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
docsUrl: "https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/posts-api"
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
//#endregion
|
|
100
|
+
export { linkedin_exports as i, linkedinCapabilities as n, linkedinInfo as r, LinkedInCredentialsSchema as t };
|
|
101
|
+
//# sourceMappingURL=linkedin-70whtVKa.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linkedin-70whtVKa.mjs","names":[],"sources":["../src/schemas/linkedin.ts"],"sourcesContent":["/**\n * LinkedIn zod v4 schemas.\n */\n\nimport { z } from 'zod';\nimport type { ProviderCapabilities } from '../common/contracts.js';\nimport { NonEmptyString, OAuthTokensSchema } from './shared.js';\n\n// ─── Credentials ────────────────────────────────────────────────────────────\n\nexport const LinkedInCredentialsSchema = z.object({\n clientId: NonEmptyString.describe('LinkedIn app client ID'),\n clientSecret: NonEmptyString.describe('LinkedIn app client secret'),\n redirectUri: z.url().optional(),\n});\nexport type LinkedInCredentialsInput = z.infer<typeof LinkedInCredentialsSchema>;\n\nexport const LinkedInCredentialDataSchema = LinkedInCredentialsSchema.extend({\n oauthTokenData: z.union([z.string(), OAuthTokensSchema]).optional(),\n});\n\n// ─── Post visibility / lifecycle ────────────────────────────────────────────\n\nexport const LinkedInVisibility = z.enum(['PUBLIC', 'CONNECTIONS', 'LOGGED_IN', 'CONTAINER']);\nexport const LinkedInLifecycleState = z.enum(['DRAFT', 'PUBLISHED', 'PUBLISHED_EDITED']);\n\n// LinkedIn URN — `urn:li:person:xxx` or `urn:li:organization:xxx`.\nexport const LinkedInUrn = z.string().regex(\n /^urn:li:(person|organization|share|ugcPost):[A-Za-z0-9_-]+$/,\n 'Must be a LinkedIn URN (e.g., urn:li:person:abc123)',\n);\n\n// ─── Post creation ──────────────────────────────────────────────────────────\n\nexport const LinkedInTextPostSchema = z.object({\n authorUrn: LinkedInUrn,\n text: z.string().min(1).max(3000),\n visibility: LinkedInVisibility.optional().default('PUBLIC'),\n});\n\nexport const LinkedInArticlePostSchema = z.object({\n authorUrn: LinkedInUrn,\n text: z.string().max(3000).optional(),\n url: z.url(),\n title: z.string().max(400).optional(),\n description: z.string().max(4096).optional(),\n visibility: LinkedInVisibility.optional().default('PUBLIC'),\n});\n\nexport const LinkedInImagePostSchema = z.object({\n authorUrn: LinkedInUrn,\n text: z.string().max(3000),\n imageBuffer: z.instanceof(Uint8Array).optional()\n .describe('Raw image bytes — provider downloads from imageUrl if absent'),\n imageUrl: z.url().optional(),\n visibility: LinkedInVisibility.optional().default('PUBLIC'),\n}).refine(d => d.imageBuffer || d.imageUrl, {\n message: 'Provide either imageBuffer or imageUrl',\n});\n\nexport const LinkedInVideoUploadSchema = z.object({\n authorUrn: LinkedInUrn,\n text: z.string().max(3000).optional(),\n videoUrl: z.url(),\n visibility: LinkedInVisibility.optional().default('PUBLIC'),\n});\n\n// ─── Capabilities ───────────────────────────────────────────────────────────\n\nexport const linkedinCapabilities: ProviderCapabilities = {\n auth: 'oauth2',\n posting: true,\n upload: true,\n messaging: false,\n scheduling: false,\n deletion: true,\n listing: false,\n analytics: true,\n environments: false,\n pkce: false,\n};\n\nexport const linkedinInfo = {\n name: 'linkedin' as const,\n displayName: 'LinkedIn',\n scopes: ['openid', 'profile', 'email', 'w_member_social', 'w_organization_social'],\n rateLimits: { perDay: 100 },\n fileLimits: {\n image: { maxSizeBytes: 5 * 1024 * 1024 },\n video: { maxSizeBytes: 5 * 1024 ** 3, maxDurationSec: 30 * 60 },\n },\n docsUrl: 'https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/posts-api',\n} as const;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAUA,MAAa,4BAA4B,EAAE,OAAO;CAChD,UAAU,eAAe,SAAS,yBAAyB;CAC3D,cAAc,eAAe,SAAS,6BAA6B;CACnE,aAAa,EAAE,KAAK,CAAC,UAAU;CAChC,CAAC;AAGF,MAAa,+BAA+B,0BAA0B,OAAO,EAC3E,gBAAgB,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,kBAAkB,CAAC,CAAC,UAAU,EACpE,CAAC;AAIF,MAAa,qBAAqB,EAAE,KAAK;CAAC;CAAU;CAAe;CAAa;CAAY,CAAC;AAC7F,MAAa,yBAAyB,EAAE,KAAK;CAAC;CAAS;CAAa;CAAmB,CAAC;AAGxF,MAAa,cAAc,EAAE,QAAQ,CAAC,MACpC,+DACA,sDACD;AAID,MAAa,yBAAyB,EAAE,OAAO;CAC7C,WAAW;CACX,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,IAAK;CACjC,YAAY,mBAAmB,UAAU,CAAC,QAAQ,SAAS;CAC5D,CAAC;AAEF,MAAa,4BAA4B,EAAE,OAAO;CAChD,WAAW;CACX,MAAM,EAAE,QAAQ,CAAC,IAAI,IAAK,CAAC,UAAU;CACrC,KAAK,EAAE,KAAK;CACZ,OAAO,EAAE,QAAQ,CAAC,IAAI,IAAI,CAAC,UAAU;CACrC,aAAa,EAAE,QAAQ,CAAC,IAAI,KAAK,CAAC,UAAU;CAC5C,YAAY,mBAAmB,UAAU,CAAC,QAAQ,SAAS;CAC5D,CAAC;AAEF,MAAa,0BAA0B,EAAE,OAAO;CAC9C,WAAW;CACX,MAAM,EAAE,QAAQ,CAAC,IAAI,IAAK;CAC1B,aAAa,EAAE,WAAW,WAAW,CAAC,UAAU,CAC7C,SAAS,+DAA+D;CAC3E,UAAU,EAAE,KAAK,CAAC,UAAU;CAC5B,YAAY,mBAAmB,UAAU,CAAC,QAAQ,SAAS;CAC5D,CAAC,CAAC,QAAO,MAAK,EAAE,eAAe,EAAE,UAAU,EAC1C,SAAS,0CACV,CAAC;AAEF,MAAa,4BAA4B,EAAE,OAAO;CAChD,WAAW;CACX,MAAM,EAAE,QAAQ,CAAC,IAAI,IAAK,CAAC,UAAU;CACrC,UAAU,EAAE,KAAK;CACjB,YAAY,mBAAmB,UAAU,CAAC,QAAQ,SAAS;CAC5D,CAAC;AAIF,MAAa,uBAA6C;CACxD,MAAM;CACN,SAAS;CACT,QAAQ;CACR,WAAW;CACX,YAAY;CACZ,UAAU;CACV,SAAS;CACT,WAAW;CACX,cAAc;CACd,MAAM;CACP;AAED,MAAa,eAAe;CAC1B,MAAM;CACN,aAAa;CACb,QAAQ;EAAC;EAAU;EAAW;EAAS;EAAmB;EAAwB;CAClF,YAAY,EAAE,QAAQ,KAAK;CAC3B,YAAY;EACV,OAAO,EAAE,cAAc,IAAI,OAAO,MAAM;EACxC,OAAO;GAAE,cAAc,IAAI,QAAQ;GAAG,gBAAgB;GAAS;EAChE;CACD,SAAS;CACV"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { t as SocialError } from "./errors-Cm6LeKf7.mjs";
|
|
2
|
+
import { t as httpRequest } from "./http-DpcLSR1M.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/common/meta.ts
|
|
5
|
+
/**
|
|
6
|
+
* Shared Meta Graph API constants for Facebook, Instagram, and WhatsApp.
|
|
7
|
+
* Update META_GRAPH_VERSION here to upgrade all three providers at once.
|
|
8
|
+
*
|
|
9
|
+
* @see packages/social/wiki/facebook.md
|
|
10
|
+
* @see packages/social/wiki/instagram.md
|
|
11
|
+
* @see packages/social/wiki/whatsapp.md
|
|
12
|
+
*/
|
|
13
|
+
const META_GRAPH_VERSION = "v25.0";
|
|
14
|
+
const META_GRAPH_BASE = `https://graph.facebook.com/${META_GRAPH_VERSION}`;
|
|
15
|
+
const META_GRAPH_VIDEO_BASE = `https://graph-video.facebook.com/${META_GRAPH_VERSION}`;
|
|
16
|
+
const META_AUTH_URL = `https://www.facebook.com/${META_GRAPH_VERSION}/dialog/oauth`;
|
|
17
|
+
const META_TOKEN_URL = `https://graph.facebook.com/${META_GRAPH_VERSION}/oauth/access_token`;
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/common/oauth/meta.ts
|
|
21
|
+
const FB_TOKEN_URL_TEMPLATE = (version) => `https://graph.facebook.com/${version}/oauth/access_token`;
|
|
22
|
+
const IG_REFRESH_URL = "https://graph.instagram.com/refresh_access_token";
|
|
23
|
+
/**
|
|
24
|
+
* Two-step Meta token exchange: code → short-lived → long-lived.
|
|
25
|
+
*
|
|
26
|
+
* @param provider — `'facebook'` or `'instagram'` (used for error attribution).
|
|
27
|
+
* @returns Long-lived OAuth tokens. `refresh_token` is set to `access_token`
|
|
28
|
+
* because Meta uses the access token itself for refresh.
|
|
29
|
+
*/
|
|
30
|
+
async function metaExchangeLongLived(provider, params) {
|
|
31
|
+
const tokenUrl = FB_TOKEN_URL_TEMPLATE(params.graphVersion);
|
|
32
|
+
const { data: shortData } = await httpRequest(provider, {
|
|
33
|
+
method: "GET",
|
|
34
|
+
url: tokenUrl,
|
|
35
|
+
query: {
|
|
36
|
+
client_id: params.clientId,
|
|
37
|
+
client_secret: params.clientSecret,
|
|
38
|
+
redirect_uri: params.redirectUri,
|
|
39
|
+
code: params.code
|
|
40
|
+
},
|
|
41
|
+
parseError: parseGraphError
|
|
42
|
+
});
|
|
43
|
+
const shortToken = shortData.access_token;
|
|
44
|
+
if (!shortToken) throw new SocialError(provider, "Meta token exchange returned no access_token", { statusCode: 502 });
|
|
45
|
+
const { data: longData } = await httpRequest(provider, {
|
|
46
|
+
method: "GET",
|
|
47
|
+
url: tokenUrl,
|
|
48
|
+
query: {
|
|
49
|
+
grant_type: "fb_exchange_token",
|
|
50
|
+
client_id: params.clientId,
|
|
51
|
+
client_secret: params.clientSecret,
|
|
52
|
+
fb_exchange_token: shortToken
|
|
53
|
+
},
|
|
54
|
+
parseError: parseGraphError
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
access_token: longData.access_token,
|
|
58
|
+
refresh_token: longData.access_token,
|
|
59
|
+
expires_in: longData.expires_in,
|
|
60
|
+
token_type: longData.token_type ?? "Bearer"
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Refresh a long-lived Facebook user token.
|
|
65
|
+
* Page tokens derived from the long-lived user token never expire.
|
|
66
|
+
*/
|
|
67
|
+
async function metaRefreshLongLivedFacebook(params) {
|
|
68
|
+
const { data } = await httpRequest("facebook", {
|
|
69
|
+
method: "GET",
|
|
70
|
+
url: FB_TOKEN_URL_TEMPLATE(params.graphVersion),
|
|
71
|
+
query: {
|
|
72
|
+
grant_type: "fb_exchange_token",
|
|
73
|
+
client_id: params.clientId,
|
|
74
|
+
client_secret: params.clientSecret,
|
|
75
|
+
fb_exchange_token: params.refreshToken
|
|
76
|
+
},
|
|
77
|
+
parseError: parseGraphError
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
access_token: data.access_token,
|
|
81
|
+
refresh_token: data.access_token,
|
|
82
|
+
expires_in: data.expires_in,
|
|
83
|
+
token_type: data.token_type ?? "Bearer"
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Refresh a long-lived Instagram token (must be ≥24h old, not yet expired).
|
|
88
|
+
*/
|
|
89
|
+
async function metaRefreshLongLivedInstagram(params) {
|
|
90
|
+
const { data } = await httpRequest("instagram", {
|
|
91
|
+
method: "GET",
|
|
92
|
+
url: IG_REFRESH_URL,
|
|
93
|
+
query: {
|
|
94
|
+
grant_type: "ig_refresh_token",
|
|
95
|
+
access_token: params.refreshToken
|
|
96
|
+
},
|
|
97
|
+
parseError: parseGraphError
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
access_token: data.access_token,
|
|
101
|
+
refresh_token: data.access_token,
|
|
102
|
+
expires_in: data.expires_in,
|
|
103
|
+
token_type: data.token_type ?? "Bearer"
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Parser for Graph API error envelope: `{ error: { message, code, error_subcode, type, fbtrace_id } }`.
|
|
108
|
+
*/
|
|
109
|
+
function parseGraphError(raw) {
|
|
110
|
+
if (!raw || typeof raw !== "object") return null;
|
|
111
|
+
const err = raw.error;
|
|
112
|
+
if (!err) return null;
|
|
113
|
+
return {
|
|
114
|
+
message: err.message || `Graph API error (${err.code ?? "unknown"})`,
|
|
115
|
+
errorCode: err.code ?? null,
|
|
116
|
+
extras: {
|
|
117
|
+
errorSubcode: err.error_subcode,
|
|
118
|
+
errorType: err.type,
|
|
119
|
+
fbtraceId: err.fbtrace_id
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
//#endregion
|
|
125
|
+
export { parseGraphError as a, META_GRAPH_VERSION as c, metaRefreshLongLivedInstagram as i, META_GRAPH_VIDEO_BASE as l, metaExchangeLongLived as n, META_AUTH_URL as o, metaRefreshLongLivedFacebook as r, META_GRAPH_BASE as s, IG_REFRESH_URL as t, META_TOKEN_URL as u };
|
|
126
|
+
//# sourceMappingURL=meta-D3vcJU1c.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta-D3vcJU1c.mjs","names":[],"sources":["../src/common/meta.ts","../src/common/oauth/meta.ts"],"sourcesContent":["/**\n * Shared Meta Graph API constants for Facebook, Instagram, and WhatsApp.\n * Update META_GRAPH_VERSION here to upgrade all three providers at once.\n *\n * @see packages/social/wiki/facebook.md\n * @see packages/social/wiki/instagram.md\n * @see packages/social/wiki/whatsapp.md\n */\n\nexport const META_GRAPH_VERSION = 'v25.0';\n\nexport const META_GRAPH_BASE = `https://graph.facebook.com/${META_GRAPH_VERSION}`;\nexport const META_GRAPH_VIDEO_BASE = `https://graph-video.facebook.com/${META_GRAPH_VERSION}`;\nexport const META_AUTH_URL = `https://www.facebook.com/${META_GRAPH_VERSION}/dialog/oauth`;\nexport const META_TOKEN_URL = `https://graph.facebook.com/${META_GRAPH_VERSION}/oauth/access_token`;\n","/**\n * Meta (Facebook / Instagram) OAuth helpers.\n *\n * Meta's token flow is non-standard:\n * - Step 1: code → short-lived token (~1 hour)\n * - Step 2: short-lived → long-lived token (~60 days)\n * - Refresh: re-exchange the long-lived token (FB) or hit graph.instagram.com (IG)\n *\n * Both Facebook and Instagram share Step 1; the only difference is the refresh URL.\n */\n\nimport type { OAuthTokens } from '../../base.js';\nimport { httpRequest } from '../http.js';\nimport { SocialError } from '../../errors.js';\n\nconst FB_TOKEN_URL_TEMPLATE = (version: string) =>\n `https://graph.facebook.com/${version}/oauth/access_token`;\n\nexport const IG_REFRESH_URL = 'https://graph.instagram.com/refresh_access_token';\n\nexport interface MetaExchangeParams {\n graphVersion: string;\n clientId: string;\n clientSecret: string;\n redirectUri: string;\n code: string;\n}\n\n/**\n * Two-step Meta token exchange: code → short-lived → long-lived.\n *\n * @param provider — `'facebook'` or `'instagram'` (used for error attribution).\n * @returns Long-lived OAuth tokens. `refresh_token` is set to `access_token`\n * because Meta uses the access token itself for refresh.\n */\nexport async function metaExchangeLongLived(\n provider: 'facebook' | 'instagram',\n params: MetaExchangeParams,\n): Promise<OAuthTokens> {\n const tokenUrl = FB_TOKEN_URL_TEMPLATE(params.graphVersion);\n\n // Step 1: code → short-lived token\n const { data: shortData } = await httpRequest<Record<string, unknown>>(provider, {\n method: 'GET',\n url: tokenUrl,\n query: {\n client_id: params.clientId,\n client_secret: params.clientSecret,\n redirect_uri: params.redirectUri,\n code: params.code,\n },\n parseError: parseGraphError,\n });\n\n const shortToken = shortData.access_token as string | undefined;\n if (!shortToken) {\n throw new SocialError(provider, 'Meta token exchange returned no access_token', { statusCode: 502 });\n }\n\n // Step 2: short-lived → long-lived\n const { data: longData } = await httpRequest<Record<string, unknown>>(provider, {\n method: 'GET',\n url: tokenUrl,\n query: {\n grant_type: 'fb_exchange_token',\n client_id: params.clientId,\n client_secret: params.clientSecret,\n fb_exchange_token: shortToken,\n },\n parseError: parseGraphError,\n });\n\n return {\n access_token: longData.access_token as string,\n refresh_token: longData.access_token as string,\n expires_in: longData.expires_in as number | undefined,\n token_type: (longData.token_type as string) ?? 'Bearer',\n };\n}\n\n/**\n * Refresh a long-lived Facebook user token.\n * Page tokens derived from the long-lived user token never expire.\n */\nexport async function metaRefreshLongLivedFacebook(params: {\n graphVersion: string;\n clientId: string;\n clientSecret: string;\n refreshToken: string;\n}): Promise<OAuthTokens> {\n const tokenUrl = FB_TOKEN_URL_TEMPLATE(params.graphVersion);\n const { data } = await httpRequest<Record<string, unknown>>('facebook', {\n method: 'GET',\n url: tokenUrl,\n query: {\n grant_type: 'fb_exchange_token',\n client_id: params.clientId,\n client_secret: params.clientSecret,\n fb_exchange_token: params.refreshToken,\n },\n parseError: parseGraphError,\n });\n return {\n access_token: data.access_token as string,\n refresh_token: data.access_token as string,\n expires_in: data.expires_in as number | undefined,\n token_type: (data.token_type as string) ?? 'Bearer',\n };\n}\n\n/**\n * Refresh a long-lived Instagram token (must be ≥24h old, not yet expired).\n */\nexport async function metaRefreshLongLivedInstagram(params: {\n refreshToken: string;\n}): Promise<OAuthTokens> {\n const { data } = await httpRequest<Record<string, unknown>>('instagram', {\n method: 'GET',\n url: IG_REFRESH_URL,\n query: {\n grant_type: 'ig_refresh_token',\n access_token: params.refreshToken,\n },\n parseError: parseGraphError,\n });\n return {\n access_token: data.access_token as string,\n refresh_token: data.access_token as string,\n expires_in: data.expires_in as number | undefined,\n token_type: (data.token_type as string) ?? 'Bearer',\n };\n}\n\n/**\n * Parser for Graph API error envelope: `{ error: { message, code, error_subcode, type, fbtrace_id } }`.\n */\nexport function parseGraphError(raw: unknown): ReturnType<NonNullable<Parameters<typeof httpRequest>[1]['parseError']>> {\n if (!raw || typeof raw !== 'object') return null;\n const r = raw as Record<string, unknown>;\n const err = r.error as Record<string, unknown> | undefined;\n if (!err) return null;\n return {\n message: (err.message as string) || `Graph API error (${err.code ?? 'unknown'})`,\n errorCode: (err.code as number | string | null) ?? null,\n extras: {\n errorSubcode: err.error_subcode,\n errorType: err.type,\n fbtraceId: err.fbtrace_id,\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;AASA,MAAa,qBAAqB;AAElC,MAAa,kBAAkB,8BAA8B;AAC7D,MAAa,wBAAwB,oCAAoC;AACzE,MAAa,gBAAgB,4BAA4B,mBAAmB;AAC5E,MAAa,iBAAiB,8BAA8B,mBAAmB;;;;ACC/E,MAAM,yBAAyB,YAC7B,8BAA8B,QAAQ;AAExC,MAAa,iBAAiB;;;;;;;;AAiB9B,eAAsB,sBACpB,UACA,QACsB;CACtB,MAAM,WAAW,sBAAsB,OAAO,aAAa;CAG3D,MAAM,EAAE,MAAM,cAAc,MAAM,YAAqC,UAAU;EAC/E,QAAQ;EACR,KAAK;EACL,OAAO;GACL,WAAW,OAAO;GAClB,eAAe,OAAO;GACtB,cAAc,OAAO;GACrB,MAAM,OAAO;GACd;EACD,YAAY;EACb,CAAC;CAEF,MAAM,aAAa,UAAU;AAC7B,KAAI,CAAC,WACH,OAAM,IAAI,YAAY,UAAU,gDAAgD,EAAE,YAAY,KAAK,CAAC;CAItG,MAAM,EAAE,MAAM,aAAa,MAAM,YAAqC,UAAU;EAC9E,QAAQ;EACR,KAAK;EACL,OAAO;GACL,YAAY;GACZ,WAAW,OAAO;GAClB,eAAe,OAAO;GACtB,mBAAmB;GACpB;EACD,YAAY;EACb,CAAC;AAEF,QAAO;EACL,cAAc,SAAS;EACvB,eAAe,SAAS;EACxB,YAAY,SAAS;EACrB,YAAa,SAAS,cAAyB;EAChD;;;;;;AAOH,eAAsB,6BAA6B,QAK1B;CAEvB,MAAM,EAAE,SAAS,MAAM,YAAqC,YAAY;EACtE,QAAQ;EACR,KAHe,sBAAsB,OAAO,aAAa;EAIzD,OAAO;GACL,YAAY;GACZ,WAAW,OAAO;GAClB,eAAe,OAAO;GACtB,mBAAmB,OAAO;GAC3B;EACD,YAAY;EACb,CAAC;AACF,QAAO;EACL,cAAc,KAAK;EACnB,eAAe,KAAK;EACpB,YAAY,KAAK;EACjB,YAAa,KAAK,cAAyB;EAC5C;;;;;AAMH,eAAsB,8BAA8B,QAE3B;CACvB,MAAM,EAAE,SAAS,MAAM,YAAqC,aAAa;EACvE,QAAQ;EACR,KAAK;EACL,OAAO;GACL,YAAY;GACZ,cAAc,OAAO;GACtB;EACD,YAAY;EACb,CAAC;AACF,QAAO;EACL,cAAc,KAAK;EACnB,eAAe,KAAK;EACpB,YAAY,KAAK;EACjB,YAAa,KAAK,cAAyB;EAC5C;;;;;AAMH,SAAgB,gBAAgB,KAAwF;AACtH,KAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;CAE5C,MAAM,MADI,IACI;AACd,KAAI,CAAC,IAAK,QAAO;AACjB,QAAO;EACL,SAAU,IAAI,WAAsB,oBAAoB,IAAI,QAAQ,UAAU;EAC9E,WAAY,IAAI,QAAmC;EACnD,QAAQ;GACN,cAAc,IAAI;GAClB,WAAW,IAAI;GACf,WAAW,IAAI;GAChB;EACF"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
//#region src/common/oauth/pkce.ts
|
|
2
|
+
/**
|
|
3
|
+
* PKCE (Proof Key for Code Exchange) helpers for OAuth 2.0.
|
|
4
|
+
*
|
|
5
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7636
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Generate a cryptographically random code verifier.
|
|
9
|
+
* Length: 43 characters (RFC 7636 §4.1 allows 43-128).
|
|
10
|
+
*/
|
|
11
|
+
function generateCodeVerifier(length = 64) {
|
|
12
|
+
const bytes = new Uint8Array(length);
|
|
13
|
+
crypto.getRandomValues(bytes);
|
|
14
|
+
return base64UrlEncode(bytes).slice(0, length);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Generate the SHA-256 challenge for a verifier (PKCE method `S256`).
|
|
18
|
+
*/
|
|
19
|
+
async function generateCodeChallenge(verifier) {
|
|
20
|
+
const encoder = new TextEncoder();
|
|
21
|
+
const digest = await crypto.subtle.digest("SHA-256", encoder.encode(verifier));
|
|
22
|
+
return base64UrlEncode(new Uint8Array(digest));
|
|
23
|
+
}
|
|
24
|
+
function base64UrlEncode(bytes) {
|
|
25
|
+
let bin = "";
|
|
26
|
+
for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
|
|
27
|
+
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Default in-memory verifier store with TTL-based eviction.
|
|
31
|
+
*
|
|
32
|
+
* **Note:** This is a single-process default. In multi-instance deployments,
|
|
33
|
+
* persist verifiers alongside `state` (e.g., in a session, Redis, or DB) and
|
|
34
|
+
* pass them back to `exchangeCode`. Do NOT rely on this store across servers.
|
|
35
|
+
*/
|
|
36
|
+
var PkceStore = class {
|
|
37
|
+
entries = /* @__PURE__ */ new Map();
|
|
38
|
+
constructor(ttlMs = 600 * 1e3) {
|
|
39
|
+
this.ttlMs = ttlMs;
|
|
40
|
+
}
|
|
41
|
+
set(state, verifier) {
|
|
42
|
+
this.evictExpired();
|
|
43
|
+
this.entries.set(state, {
|
|
44
|
+
verifier,
|
|
45
|
+
expiresAt: Date.now() + this.ttlMs
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/** Returns and removes the verifier (single-use semantics). */
|
|
49
|
+
take(state) {
|
|
50
|
+
this.evictExpired();
|
|
51
|
+
const entry = this.entries.get(state);
|
|
52
|
+
if (!entry) return void 0;
|
|
53
|
+
this.entries.delete(state);
|
|
54
|
+
if (Date.now() > entry.expiresAt) return void 0;
|
|
55
|
+
return entry.verifier;
|
|
56
|
+
}
|
|
57
|
+
delete(state) {
|
|
58
|
+
this.entries.delete(state);
|
|
59
|
+
}
|
|
60
|
+
size() {
|
|
61
|
+
this.evictExpired();
|
|
62
|
+
return this.entries.size;
|
|
63
|
+
}
|
|
64
|
+
evictExpired() {
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
for (const [k, v] of this.entries) if (now > v.expiresAt) this.entries.delete(k);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
export { generateCodeChallenge as n, generateCodeVerifier as r, PkceStore as t };
|
|
72
|
+
//# sourceMappingURL=pkce-jq5II68b.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce-jq5II68b.mjs","names":[],"sources":["../src/common/oauth/pkce.ts"],"sourcesContent":["/**\n * PKCE (Proof Key for Code Exchange) helpers for OAuth 2.0.\n *\n * @see https://datatracker.ietf.org/doc/html/rfc7636\n */\n\n/**\n * Generate a cryptographically random code verifier.\n * Length: 43 characters (RFC 7636 §4.1 allows 43-128).\n */\nexport function generateCodeVerifier(length = 64): string {\n const bytes = new Uint8Array(length);\n crypto.getRandomValues(bytes);\n return base64UrlEncode(bytes).slice(0, length);\n}\n\n/**\n * Generate the SHA-256 challenge for a verifier (PKCE method `S256`).\n */\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const digest = await crypto.subtle.digest('SHA-256', encoder.encode(verifier));\n return base64UrlEncode(new Uint8Array(digest));\n}\n\nfunction base64UrlEncode(bytes: Uint8Array): string {\n let bin = '';\n for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]!);\n return btoa(bin).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/**\n * Default in-memory verifier store with TTL-based eviction.\n *\n * **Note:** This is a single-process default. In multi-instance deployments,\n * persist verifiers alongside `state` (e.g., in a session, Redis, or DB) and\n * pass them back to `exchangeCode`. Do NOT rely on this store across servers.\n */\nexport class PkceStore {\n private entries = new Map<string, { verifier: string; expiresAt: number }>();\n\n constructor(private ttlMs: number = 10 * 60 * 1000) {}\n\n set(state: string, verifier: string): void {\n this.evictExpired();\n this.entries.set(state, { verifier, expiresAt: Date.now() + this.ttlMs });\n }\n\n /** Returns and removes the verifier (single-use semantics). */\n take(state: string): string | undefined {\n this.evictExpired();\n const entry = this.entries.get(state);\n if (!entry) return undefined;\n this.entries.delete(state);\n if (Date.now() > entry.expiresAt) return undefined;\n return entry.verifier;\n }\n\n delete(state: string): void {\n this.entries.delete(state);\n }\n\n size(): number {\n this.evictExpired();\n return this.entries.size;\n }\n\n private evictExpired(): void {\n const now = Date.now();\n for (const [k, v] of this.entries) {\n if (now > v.expiresAt) this.entries.delete(k);\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAUA,SAAgB,qBAAqB,SAAS,IAAY;CACxD,MAAM,QAAQ,IAAI,WAAW,OAAO;AACpC,QAAO,gBAAgB,MAAM;AAC7B,QAAO,gBAAgB,MAAM,CAAC,MAAM,GAAG,OAAO;;;;;AAMhD,eAAsB,sBAAsB,UAAmC;CAC7E,MAAM,UAAU,IAAI,aAAa;CACjC,MAAM,SAAS,MAAM,OAAO,OAAO,OAAO,WAAW,QAAQ,OAAO,SAAS,CAAC;AAC9E,QAAO,gBAAgB,IAAI,WAAW,OAAO,CAAC;;AAGhD,SAAS,gBAAgB,OAA2B;CAClD,IAAI,MAAM;AACV,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,IAAK,QAAO,OAAO,aAAa,MAAM,GAAI;AAC5E,QAAO,KAAK,IAAI,CAAC,QAAQ,OAAO,IAAI,CAAC,QAAQ,OAAO,IAAI,CAAC,QAAQ,OAAO,GAAG;;;;;;;;;AAU7E,IAAa,YAAb,MAAuB;CACrB,AAAQ,0BAAU,IAAI,KAAsD;CAE5E,YAAY,AAAQ,QAAgB,MAAU,KAAM;EAAhC;;CAEpB,IAAI,OAAe,UAAwB;AACzC,OAAK,cAAc;AACnB,OAAK,QAAQ,IAAI,OAAO;GAAE;GAAU,WAAW,KAAK,KAAK,GAAG,KAAK;GAAO,CAAC;;;CAI3E,KAAK,OAAmC;AACtC,OAAK,cAAc;EACnB,MAAM,QAAQ,KAAK,QAAQ,IAAI,MAAM;AACrC,MAAI,CAAC,MAAO,QAAO;AACnB,OAAK,QAAQ,OAAO,MAAM;AAC1B,MAAI,KAAK,KAAK,GAAG,MAAM,UAAW,QAAO;AACzC,SAAO,MAAM;;CAGf,OAAO,OAAqB;AAC1B,OAAK,QAAQ,OAAO,MAAM;;CAG5B,OAAe;AACb,OAAK,cAAc;AACnB,SAAO,KAAK,QAAQ;;CAGtB,AAAQ,eAAqB;EAC3B,MAAM,MAAM,KAAK,KAAK;AACtB,OAAK,MAAM,CAAC,GAAG,MAAM,KAAK,QACxB,KAAI,MAAM,EAAE,UAAW,MAAK,QAAQ,OAAO,EAAE"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/utils/polling.ts
|
|
2
|
+
/**
|
|
3
|
+
* Poll an async function until a completion or failure condition is met.
|
|
4
|
+
*
|
|
5
|
+
* @returns The data from `fn()` when `isComplete` returns true, or null on timeout.
|
|
6
|
+
* @throws Whatever `getError` returns (wrapped in Error if string).
|
|
7
|
+
*/
|
|
8
|
+
async function pollUntilComplete({ fn, isComplete, getError, maxAttempts = 30, intervalMs = 5e3, label = "polling" }) {
|
|
9
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
10
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
11
|
+
const data = await fn(attempt);
|
|
12
|
+
if (isComplete(data)) return data;
|
|
13
|
+
const failure = getError(data);
|
|
14
|
+
if (failure) {
|
|
15
|
+
if (failure instanceof Error) throw failure;
|
|
16
|
+
throw new Error(failure);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
console.warn(`[${label}] Status polling timed out after ${maxAttempts} attempts`);
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
export { pollUntilComplete as t };
|
|
25
|
+
//# sourceMappingURL=polling-DZ1apXtA.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"polling-DZ1apXtA.mjs","names":[],"sources":["../src/utils/polling.ts"],"sourcesContent":["/**\n * Generic Polling Utility\n * =======================\n * Reusable poll-until-complete pattern for platform API status checks.\n *\n * Used by Instagram (container status) and TikTok (publish status).\n *\n * Usage:\n * import { pollUntilComplete } from '../utils/polling.js';\n *\n * const result = await pollUntilComplete({\n * fn: () => fetchStatus(id),\n * isComplete: (data) => data.status === 'FINISHED',\n * getError: (data) => data.status === 'FAILED' ? data.reason : null,\n * label: 'Instagram container',\n * });\n */\n\nexport interface PollOptions<T> {\n /** Async function to call each iteration. Receives attempt index. */\n fn: (attempt: number) => Promise<T>;\n /** Predicate: return true when the operation is complete. */\n isComplete: (data: T) => boolean;\n /** Return an Error, string message, or null to keep polling. */\n getError: (data: T) => Error | string | null;\n /** Max polling iterations before timeout (default 30). */\n maxAttempts?: number;\n /** Delay between polls in milliseconds (default 5000). */\n intervalMs?: number;\n /** Label for timeout warning logs (default 'polling'). */\n label?: string;\n}\n\n/**\n * Poll an async function until a completion or failure condition is met.\n *\n * @returns The data from `fn()` when `isComplete` returns true, or null on timeout.\n * @throws Whatever `getError` returns (wrapped in Error if string).\n */\nexport async function pollUntilComplete<T>({\n fn,\n isComplete,\n getError,\n maxAttempts = 30,\n intervalMs = 5000,\n label = 'polling',\n}: PollOptions<T>): Promise<T | null> {\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n await new Promise(resolve => setTimeout(resolve, intervalMs));\n\n const data = await fn(attempt);\n\n if (isComplete(data)) {\n return data;\n }\n\n const failure = getError(data);\n if (failure) {\n if (failure instanceof Error) throw failure;\n throw new Error(failure);\n }\n\n // Still in progress -- keep polling\n }\n\n // Timeout -- not necessarily a failure, the operation may still be processing\n console.warn(`[${label}] Status polling timed out after ${maxAttempts} attempts`);\n return null;\n}\n"],"mappings":";;;;;;;AAuCA,eAAsB,kBAAqB,EACzC,IACA,YACA,UACA,cAAc,IACd,aAAa,KACb,QAAQ,aAC4B;AACpC,MAAK,IAAI,UAAU,GAAG,UAAU,aAAa,WAAW;AACtD,QAAM,IAAI,SAAQ,YAAW,WAAW,SAAS,WAAW,CAAC;EAE7D,MAAM,OAAO,MAAM,GAAG,QAAQ;AAE9B,MAAI,WAAW,KAAK,CAClB,QAAO;EAGT,MAAM,UAAU,SAAS,KAAK;AAC9B,MAAI,SAAS;AACX,OAAI,mBAAmB,MAAO,OAAM;AACpC,SAAM,IAAI,MAAM,QAAQ;;;AAO5B,SAAQ,KAAK,IAAI,MAAM,mCAAmC,YAAY,WAAW;AACjF,QAAO"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { d as ProviderMetadata, g as UploadResult, i as CredentialField, l as ProviderConfig, o as OAuthTokens, r as AuthUrlOptions, s as PlatformProvider } from "../base-DBtKFiSX.mjs";
|
|
2
|
+
import { a as CreatePostOptions, c as FacebookCredentialData, d as FacebookPage, f as FacebookPost, h as GraphApiErrorResponse, i as CreatePhotoPostResult, l as FacebookCredentials, m as FacebookUploadParams, n as CreateLinkPostResult, o as CreatePostResult, p as FacebookTestResult, r as CreatePhotoPostOptions, s as FacebookAccountInfo, t as CreateLinkPostOptions, u as FacebookInsightsPeriod } from "../types-ClbVc2rc.mjs";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
//#region src/providers/facebook/index.d.ts
|
|
6
|
+
declare class FacebookProvider extends PlatformProvider {
|
|
7
|
+
defaultRedirectUri: string;
|
|
8
|
+
scopes: string[];
|
|
9
|
+
constructor(cfg?: ProviderConfig);
|
|
10
|
+
/**
|
|
11
|
+
* Unified Graph API request — used by all GET / POST / DELETE call sites.
|
|
12
|
+
* Delegates to the shared `httpRequest` helper for retry, timeout, and 429 handling.
|
|
13
|
+
*
|
|
14
|
+
* @param method HTTP method
|
|
15
|
+
* @param path Graph API path (e.g. `/me/accounts`)
|
|
16
|
+
* @param accessToken Access token (sent as `access_token` query for GET/DELETE; body field for POST)
|
|
17
|
+
* @param body POST body (merged with `access_token`); ignored for GET/DELETE
|
|
18
|
+
* @param query Extra query params for GET
|
|
19
|
+
* @param baseUrl Optional base URL override (video uploads use graph-video.facebook.com)
|
|
20
|
+
*/
|
|
21
|
+
private _graph;
|
|
22
|
+
private _graphGet;
|
|
23
|
+
private _graphPost;
|
|
24
|
+
private _graphDelete;
|
|
25
|
+
/**
|
|
26
|
+
* Get Facebook OAuth authorization URL
|
|
27
|
+
*/
|
|
28
|
+
getAuthUrl(state: string, credentials?: Partial<FacebookCredentials>, _options?: AuthUrlOptions): string;
|
|
29
|
+
/**
|
|
30
|
+
* Exchange authorization code for tokens (two-step).
|
|
31
|
+
*
|
|
32
|
+
* Step 1: code → short-lived token (1 hour)
|
|
33
|
+
* Step 2: short-lived → long-lived token (60 days)
|
|
34
|
+
*/
|
|
35
|
+
exchangeCode(code: string, credentials?: Partial<FacebookCredentials>): Promise<OAuthTokens>;
|
|
36
|
+
/**
|
|
37
|
+
* Refresh long-lived user token. Facebook long-lived tokens can be refreshed
|
|
38
|
+
* before expiry by re-exchanging them via `fb_exchange_token`.
|
|
39
|
+
*/
|
|
40
|
+
refreshToken(refreshToken: string, credentials?: Partial<FacebookCredentials>): Promise<OAuthTokens>;
|
|
41
|
+
/**
|
|
42
|
+
* Get user's Facebook Pages with page-level access tokens
|
|
43
|
+
* Each page has its own never-expiring token (when derived from long-lived user token)
|
|
44
|
+
*
|
|
45
|
+
* @param userAccessToken - Long-lived user access token
|
|
46
|
+
* @returns List of pages with { id, name, access_token, category, ... }
|
|
47
|
+
*/
|
|
48
|
+
getPages(userAccessToken: string): Promise<FacebookPage[]>;
|
|
49
|
+
/**
|
|
50
|
+
* Get account information -- returns the first managed page
|
|
51
|
+
*/
|
|
52
|
+
getAccountInfo(accessToken: string): Promise<FacebookAccountInfo>;
|
|
53
|
+
/**
|
|
54
|
+
* Test credential validity
|
|
55
|
+
*/
|
|
56
|
+
testCredential(credentialData: Record<string, any>): Promise<FacebookTestResult>;
|
|
57
|
+
/**
|
|
58
|
+
* Get the page-level access token for a specific page
|
|
59
|
+
* Page tokens derived from long-lived user tokens never expire.
|
|
60
|
+
*
|
|
61
|
+
* @param userAccessToken - Long-lived user access token
|
|
62
|
+
* @param pageId - Facebook Page ID
|
|
63
|
+
* @returns Page access token
|
|
64
|
+
*/
|
|
65
|
+
getPageAccessToken(userAccessToken: string, pageId: string): Promise<string>;
|
|
66
|
+
/**
|
|
67
|
+
* Publish a text post to a Facebook Page
|
|
68
|
+
* @param pageAccessToken - Page-level access token
|
|
69
|
+
* @param pageId - Facebook Page ID
|
|
70
|
+
* @param message - Post text
|
|
71
|
+
* @param options
|
|
72
|
+
*/
|
|
73
|
+
createPost(pageAccessToken: string, pageId: string, message: string, options?: CreatePostOptions): Promise<CreatePostResult>;
|
|
74
|
+
/**
|
|
75
|
+
* Publish a link/article share to a Facebook Page
|
|
76
|
+
* @param pageAccessToken
|
|
77
|
+
* @param pageId
|
|
78
|
+
* @param link - URL to share
|
|
79
|
+
* @param options
|
|
80
|
+
*/
|
|
81
|
+
createLinkPost(pageAccessToken: string, pageId: string, link: string, options?: CreateLinkPostOptions): Promise<CreateLinkPostResult>;
|
|
82
|
+
/**
|
|
83
|
+
* Publish a photo to a Facebook Page (via public URL)
|
|
84
|
+
* @param pageAccessToken
|
|
85
|
+
* @param pageId
|
|
86
|
+
* @param photoUrl - Publicly accessible photo URL
|
|
87
|
+
* @param options
|
|
88
|
+
*/
|
|
89
|
+
createPhotoPost(pageAccessToken: string, pageId: string, photoUrl: string, options?: CreatePhotoPostOptions): Promise<CreatePhotoPostResult>;
|
|
90
|
+
/**
|
|
91
|
+
* Upload a video to a Facebook Page (via public URL)
|
|
92
|
+
* Uses graph-video.facebook.com for video uploads.
|
|
93
|
+
*/
|
|
94
|
+
uploadVideo(params: FacebookUploadParams): Promise<UploadResult>;
|
|
95
|
+
/**
|
|
96
|
+
* Get a post by ID
|
|
97
|
+
* @param pageAccessToken
|
|
98
|
+
* @param postId
|
|
99
|
+
* @param fields - Comma-separated fields
|
|
100
|
+
*/
|
|
101
|
+
getPost(pageAccessToken: string, postId: string, fields?: string): Promise<any>;
|
|
102
|
+
/**
|
|
103
|
+
* Update a post's message
|
|
104
|
+
* @param pageAccessToken
|
|
105
|
+
* @param postId
|
|
106
|
+
* @param message - New message text
|
|
107
|
+
*/
|
|
108
|
+
updatePost(pageAccessToken: string, postId: string, message: string): Promise<any>;
|
|
109
|
+
/**
|
|
110
|
+
* Delete a post
|
|
111
|
+
* @param pageAccessToken
|
|
112
|
+
* @param postId
|
|
113
|
+
*/
|
|
114
|
+
deletePost(pageAccessToken: string, postId: string): Promise<any>;
|
|
115
|
+
/**
|
|
116
|
+
* Get page feed (recent posts)
|
|
117
|
+
* @param pageAccessToken
|
|
118
|
+
* @param pageId
|
|
119
|
+
* @param limit
|
|
120
|
+
*/
|
|
121
|
+
getPageFeed(pageAccessToken: string, pageId: string, limit?: number): Promise<any>;
|
|
122
|
+
/**
|
|
123
|
+
* Get page insights (basic analytics)
|
|
124
|
+
* @param pageAccessToken
|
|
125
|
+
* @param pageId
|
|
126
|
+
* @param period - 'day' | 'week' | 'days_28'
|
|
127
|
+
*/
|
|
128
|
+
getPageInsights(pageAccessToken: string, pageId: string, period?: 'day' | 'week' | 'days_28'): Promise<any>;
|
|
129
|
+
getCredentialZodSchema(): z.ZodType;
|
|
130
|
+
getCredentialSchema(): CredentialField[];
|
|
131
|
+
getMetadata(): ProviderMetadata;
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
export { type CreateLinkPostOptions, type CreateLinkPostResult, type CreatePhotoPostOptions, type CreatePhotoPostResult, type CreatePostOptions, type CreatePostResult, type FacebookAccountInfo, type FacebookCredentialData, type FacebookCredentials, type FacebookInsightsPeriod, type FacebookPage, type FacebookPost, FacebookProvider, type FacebookTestResult, type FacebookUploadParams, type GraphApiErrorResponse };
|
|
135
|
+
//# sourceMappingURL=facebook.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"facebook.d.mts","names":[],"sources":["../../src/providers/facebook/index.ts"],"mappings":";;;;;cAiGa,gBAAA,SAAyB,gBAAA;EAC7B,kBAAA;EACA,MAAA;cAEK,GAAA,GAAK,cAAA;EAkUgC;;;;;;;;;;;EAAA,QAlSnC,MAAA;EAAA,QAsBA,SAAA;EAAA,QAGA,UAAA;EAAA,QAGA,YAAA;;;;EASd,UAAA,CAAW,KAAA,UAAe,WAAA,GAAa,OAAA,CAAQ,mBAAA,GAA2B,QAAA,GAAW,cAAA;EAfvE;;;;;;EAiCR,YAAA,CAAa,IAAA,UAAc,WAAA,GAAa,OAAA,CAAQ,mBAAA,IAA4B,OAAA,CAAQ,WAAA;EAlBhE;;;;EAgCpB,YAAA,CAAa,YAAA,UAAsB,WAAA,GAAa,OAAA,CAAQ,mBAAA,IAA4B,OAAA,CAAQ,WAAA;EAdpD;;;;;;;EAgCxC,QAAA,CAAS,eAAA,WAA0B,OAAA,CAAQ,YAAA;EAlBa;;;EA8BxD,cAAA,CAAe,WAAA,WAAsB,OAAA,CAAQ,mBAAA;EAZ7C;;;EA2CA,cAAA,CAAe,cAAA,EAAgB,MAAA,gBAAsB,OAAA,CAAQ,kBAAA;EA/B7D;;;;;;;;EAgFA,kBAAA,CAAmB,eAAA,UAAyB,MAAA,WAAiB,OAAA;EAA7D;;;;;;;EAuBA,UAAA,CACJ,eAAA,UACA,MAAA,UACA,OAAA,UACA,OAAA,GAAS,iBAAA,GACR,OAAA,CAAQ,gBAAA;EADA;;;;;;;EA0BL,cAAA,CACJ,eAAA,UACA,MAAA,UACA,IAAA,UACA,OAAA,GAAS,qBAAA,GACR,OAAA,CAAQ,oBAAA;EADA;;;;;;;EA0BL,eAAA,CACJ,eAAA,UACA,MAAA,UACA,QAAA,UACA,OAAA,GAAS,sBAAA,GACR,OAAA,CAAQ,qBAAA;EADA;;;;EAwBL,WAAA,CAAY,MAAA,EAAQ,oBAAA,GAAuB,OAAA,CAAQ,YAAA;EAA/B;;;;;;EAmEpB,OAAA,CACJ,eAAA,UACA,MAAA,UACA,MAAA,YACC,OAAA;EADD;;;;;;EAWI,UAAA,CAAW,eAAA,UAAyB,MAAA,UAAgB,OAAA,WAAkB,OAAA;EAStE;;;;;EAAA,UAAA,CAAW,eAAA,UAAyB,MAAA,WAAiB,OAAA;EAUhB;;;;;;EAArC,WAAA,CAAY,eAAA,UAAyB,MAAA,UAAgB,KAAA,YAAqB,OAAA;EAkB7E;;;;;;EAJG,eAAA,CACJ,eAAA,UACA,MAAA,UACA,MAAA,gCACC,OAAA;EAaH,sBAAA,CAAA,GAA0B,CAAA,CAAE,OAAA;EAI5B,mBAAA,CAAA,GAAuB,eAAA;EAmBvB,WAAA,CAAA,GAAe,gBAAA;AAAA"}
|