@venturekit-pro/social 0.0.20 → 0.0.22
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 +36 -8
- package/dist/adapters/facebook.d.ts +25 -2
- package/dist/adapters/facebook.d.ts.map +1 -1
- package/dist/adapters/facebook.js +156 -35
- package/dist/adapters/facebook.js.map +1 -1
- package/dist/adapters/linkedin.d.ts +26 -6
- package/dist/adapters/linkedin.d.ts.map +1 -1
- package/dist/adapters/linkedin.js +170 -49
- package/dist/adapters/linkedin.js.map +1 -1
- package/dist/http.d.ts +11 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +12 -3
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/media.d.ts +60 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +169 -0
- package/dist/media.js.map +1 -0
- package/dist/types.d.ts +5 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +10 -5
package/README.md
CHANGED
|
@@ -107,14 +107,42 @@ and passes it per call.
|
|
|
107
107
|
|
|
108
108
|
## Media staging
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
110
|
+
The adapters handle image uploads automatically — callers pass a plain
|
|
111
|
+
image URL (presigned S3, CDN, etc.) in `SocialMedia.url` and the
|
|
112
|
+
adapter does the rest:
|
|
113
|
+
|
|
114
|
+
- **Facebook** — downloads the image in-process and uploads the bytes
|
|
115
|
+
via multipart form data (`source` field) to `/<page-id>/photos`.
|
|
116
|
+
This works even when the URL is a private/presigned link that Meta's
|
|
117
|
+
servers can't reach (e.g. local dev MinIO, private VPC S3 without a
|
|
118
|
+
CDN).
|
|
119
|
+
- **LinkedIn** — if `media.url` is already a LinkedIn asset URN
|
|
120
|
+
(`urn:li:digitalmediaAsset:…`), uses it directly (backward compat for
|
|
121
|
+
callers that pre-stage). Otherwise, performs LinkedIn's two-step
|
|
122
|
+
staging automatically: `registerUpload` → PUT bytes → asset URN.
|
|
123
|
+
Images in unsupported formats (e.g. WebP) are converted to JPEG
|
|
124
|
+
before upload — this requires the optional `sharp` peer dependency
|
|
125
|
+
(`pnpm add sharp`).
|
|
126
|
+
- **Instagram** — passes the image URL to Meta's `/media` endpoint
|
|
127
|
+
(Meta fetches it server-side). A publicly reachable URL or CDN is
|
|
128
|
+
required.
|
|
129
|
+
- **X** — the caller stages the upload via X's chunk-upload endpoint
|
|
130
|
+
and passes the resulting `media_id_string` as `url`.
|
|
131
|
+
|
|
132
|
+
### Notes on byte uploads
|
|
133
|
+
|
|
134
|
+
- The upload format is decided from the payload's **magic number**, not
|
|
135
|
+
from `SocialMedia.mimeType` — stale catalog metadata (a WebP recorded
|
|
136
|
+
as `image/jpeg`) would otherwise skip conversion and be rejected by
|
|
137
|
+
the vendor. `mimeType` is only used when the format isn't recognised.
|
|
138
|
+
- Downloads are capped at the platform's `maxMediaSizeBytes` and time
|
|
139
|
+
out after 30s. Oversized payloads raise `SocialValidationError`
|
|
140
|
+
(permanent); transport failures raise `SocialPublishError`.
|
|
141
|
+
- Only `http(s)` media URLs are accepted. Because the **worker** now
|
|
142
|
+
fetches the URL rather than the vendor, callers that let end users
|
|
143
|
+
supply arbitrary media URLs must block internal targets (link-local,
|
|
144
|
+
RFC1918, cloud metadata endpoints) themselves — the package has no
|
|
145
|
+
way to know what is internal for a given deployment.
|
|
118
146
|
|
|
119
147
|
## Extending with a custom platform
|
|
120
148
|
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
* Facebook (Pages) adapter — Meta Graph API.
|
|
3
3
|
*
|
|
4
4
|
* Endpoints:
|
|
5
|
-
* - Text post:
|
|
6
|
-
* -
|
|
5
|
+
* - Text post: `POST /v18.0/<page-id>/feed`
|
|
6
|
+
* - Single photo: `POST /v18.0/<page-id>/photos`
|
|
7
|
+
* - Multi-photo: `POST /v18.0/<page-id>/photos` (published=false)
|
|
8
|
+
* per photo, then `POST /v18.0/<page-id>/feed`
|
|
9
|
+
* with `attached_media` referencing all photo ids.
|
|
7
10
|
*
|
|
8
11
|
* Auth: Page access token via `?access_token=…` OR
|
|
9
12
|
* `Authorization: Bearer …`. We use the header form so the
|
|
@@ -12,6 +15,26 @@
|
|
|
12
15
|
* `credentials.authorRef` carries the Page id (e.g. `'page_12345'`).
|
|
13
16
|
* We strip the `'page_'` prefix when forming URLs — same convention
|
|
14
17
|
* the Instagram adapter uses.
|
|
18
|
+
*
|
|
19
|
+
* Photo posts upload the image bytes directly via multipart form data
|
|
20
|
+
* (`source` field) rather than passing a public URL (`url` field) that
|
|
21
|
+
* Meta's servers would have to fetch. This works in every environment
|
|
22
|
+
* — including local dev (MinIO on localhost) and prod without a CDN —
|
|
23
|
+
* because the adapter downloads the image in-process (where it CAN
|
|
24
|
+
* reach a presigned / private URL) and pushes the bytes to Meta.
|
|
25
|
+
*
|
|
26
|
+
* Multi-photo posts (2–10 images) create an album-style feed post:
|
|
27
|
+
* each image is pre-uploaded to `/<page-id>/photos` with
|
|
28
|
+
* `published=false`, then a single `/<page-id>/feed` post references
|
|
29
|
+
* all uploaded photo ids via the `attached_media` field. This is the
|
|
30
|
+
* only Graph API flow that produces a multi-image post on a Page.
|
|
31
|
+
*
|
|
32
|
+
* Meta's /photos endpoint accepts JPEG and PNG only. The format is
|
|
33
|
+
* read from the downloaded bytes rather than `media.mimeType`, and
|
|
34
|
+
* anything else (e.g. WebP, which the CMS uses for blog covers) is
|
|
35
|
+
* converted to JPEG before upload via the optional `sharp` peer
|
|
36
|
+
* dependency. The constraints allow WebP so validation doesn't block
|
|
37
|
+
* the post before the adapter can convert it.
|
|
15
38
|
*/
|
|
16
39
|
import type { SocialAdapter } from '../adapter.js';
|
|
17
40
|
import type { SocialPlatformConstraints } from '../types.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"facebook.d.ts","sourceRoot":"","sources":["../../src/adapters/facebook.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"facebook.d.ts","sourceRoot":"","sources":["../../src/adapters/facebook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAIV,yBAAyB,EAE1B,MAAM,aAAa,CAAC;AAmBrB,eAAO,MAAM,oBAAoB,EAAE,yBAelC,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC,yBAAyB,CAAC,CAAC;CAClD;AAED,wBAAgB,qBAAqB,CACnC,MAAM,GAAE,qBAA0B,GACjC,aAAa,CA0Hf"}
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
* Facebook (Pages) adapter — Meta Graph API.
|
|
3
3
|
*
|
|
4
4
|
* Endpoints:
|
|
5
|
-
* - Text post:
|
|
6
|
-
* -
|
|
5
|
+
* - Text post: `POST /v18.0/<page-id>/feed`
|
|
6
|
+
* - Single photo: `POST /v18.0/<page-id>/photos`
|
|
7
|
+
* - Multi-photo: `POST /v18.0/<page-id>/photos` (published=false)
|
|
8
|
+
* per photo, then `POST /v18.0/<page-id>/feed`
|
|
9
|
+
* with `attached_media` referencing all photo ids.
|
|
7
10
|
*
|
|
8
11
|
* Auth: Page access token via `?access_token=…` OR
|
|
9
12
|
* `Authorization: Bearer …`. We use the header form so the
|
|
@@ -12,18 +15,49 @@
|
|
|
12
15
|
* `credentials.authorRef` carries the Page id (e.g. `'page_12345'`).
|
|
13
16
|
* We strip the `'page_'` prefix when forming URLs — same convention
|
|
14
17
|
* the Instagram adapter uses.
|
|
18
|
+
*
|
|
19
|
+
* Photo posts upload the image bytes directly via multipart form data
|
|
20
|
+
* (`source` field) rather than passing a public URL (`url` field) that
|
|
21
|
+
* Meta's servers would have to fetch. This works in every environment
|
|
22
|
+
* — including local dev (MinIO on localhost) and prod without a CDN —
|
|
23
|
+
* because the adapter downloads the image in-process (where it CAN
|
|
24
|
+
* reach a presigned / private URL) and pushes the bytes to Meta.
|
|
25
|
+
*
|
|
26
|
+
* Multi-photo posts (2–10 images) create an album-style feed post:
|
|
27
|
+
* each image is pre-uploaded to `/<page-id>/photos` with
|
|
28
|
+
* `published=false`, then a single `/<page-id>/feed` post references
|
|
29
|
+
* all uploaded photo ids via the `attached_media` field. This is the
|
|
30
|
+
* only Graph API flow that produces a multi-image post on a Page.
|
|
31
|
+
*
|
|
32
|
+
* Meta's /photos endpoint accepts JPEG and PNG only. The format is
|
|
33
|
+
* read from the downloaded bytes rather than `media.mimeType`, and
|
|
34
|
+
* anything else (e.g. WebP, which the CMS uses for blog covers) is
|
|
35
|
+
* converted to JPEG before upload via the optional `sharp` peer
|
|
36
|
+
* dependency. The constraints allow WebP so validation doesn't block
|
|
37
|
+
* the post before the adapter can convert it.
|
|
15
38
|
*/
|
|
39
|
+
import { SocialPublishError, SocialValidationError } from '../types.js';
|
|
16
40
|
import { vendorFetch } from '../http.js';
|
|
41
|
+
import { downloadMedia, ensureUploadableImage, filenameForMime, } from '../media.js';
|
|
17
42
|
import { validatePost } from '../validation.js';
|
|
18
43
|
const META_GRAPH_BASE = 'https://graph.facebook.com';
|
|
19
44
|
const META_GRAPH_VERSION = 'v18.0';
|
|
45
|
+
/** MIME types Meta's /photos endpoint actually accepts. */
|
|
46
|
+
const FACEBOOK_UPLOAD_MIME = new Set(['image/jpeg', 'image/png']);
|
|
47
|
+
/** Maximum photos per album-style feed post (Meta's hard limit). */
|
|
48
|
+
const MAX_FB_PHOTOS = 10;
|
|
20
49
|
export const FACEBOOK_CONSTRAINTS = {
|
|
21
50
|
maxBodyChars: 63206, // Meta's published max; 2000 is the recommended ceiling
|
|
22
51
|
minBodyChars: 1,
|
|
23
52
|
minHashtags: 0,
|
|
24
53
|
maxHashtags: 30,
|
|
25
|
-
|
|
26
|
-
|
|
54
|
+
// WebP is allowed at validation time so the adapter can convert it
|
|
55
|
+
// to JPEG before uploading — Meta's /photos endpoint rejects WebP.
|
|
56
|
+
allowedMimeTypes: ['image/jpeg', 'image/png', 'image/webp'],
|
|
57
|
+
// Facebook supports album-style posts with up to 10 photos via
|
|
58
|
+
// `attached_media` on the /feed endpoint. Single-photo posts still go
|
|
59
|
+
// through /photos; 2+ photos use the multi-upload flow.
|
|
60
|
+
maxMediaCount: 10,
|
|
27
61
|
allowedAspectRatios: ['1:1', '4:3', '16:9', '4:5'],
|
|
28
62
|
maxMediaSizeBytes: 10 * 1024 * 1024,
|
|
29
63
|
supportsFirstComment: false,
|
|
@@ -44,52 +78,139 @@ export function createFacebookAdapter(config = {}) {
|
|
|
44
78
|
},
|
|
45
79
|
async publish(post, credentials) {
|
|
46
80
|
const pageId = stripPagePrefix(credentials.authorRef);
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
81
|
+
const mediaCount = post.media?.length ?? 0;
|
|
82
|
+
// Text / link posts hit /<page-id>/feed — no image, so the
|
|
83
|
+
// original JSON body path is correct.
|
|
84
|
+
if (mediaCount === 0) {
|
|
85
|
+
const endpoint = `/${version}/${encodeURIComponent(pageId)}/feed`;
|
|
86
|
+
const res = await vendorFetch('facebook', `${baseUrl}${endpoint}`, {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers: {
|
|
89
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
90
|
+
'Content-Type': 'application/json',
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify(buildFeedBody(post)),
|
|
93
|
+
});
|
|
94
|
+
return parseResult(res, post);
|
|
95
|
+
}
|
|
96
|
+
// Meta hard-caps an album at 10 photos. `validate()` already
|
|
97
|
+
// reports this, but `publish()` can be called without it (and
|
|
98
|
+
// `config.constraints` can raise `maxMediaCount` past the vendor
|
|
99
|
+
// limit), so refuse loudly rather than silently dropping content.
|
|
100
|
+
if (mediaCount > MAX_FB_PHOTOS) {
|
|
101
|
+
throw new SocialValidationError(`[social/facebook] Too many photos (${mediaCount}; Meta allows ${MAX_FB_PHOTOS} per post).`, { platform: 'facebook' });
|
|
102
|
+
}
|
|
103
|
+
// Single photo posts hit /<page-id>/photos with multipart form
|
|
104
|
+
// data. We download the image in-process and upload the bytes via
|
|
105
|
+
// the `source` field — Meta's servers don't need to fetch
|
|
106
|
+
// anything.
|
|
107
|
+
if (mediaCount === 1) {
|
|
108
|
+
const endpoint = `/${version}/${encodeURIComponent(pageId)}/photos`;
|
|
109
|
+
const form = await photoForm(post.media[0], constraints.maxMediaSizeBytes);
|
|
110
|
+
form.append('message', post.body);
|
|
111
|
+
// Do NOT set Content-Type manually — fetch() sets the multipart
|
|
112
|
+
// boundary automatically from FormData. A manual header would
|
|
113
|
+
// omit the boundary and Meta would reject the request.
|
|
114
|
+
const res = await vendorFetch('facebook', `${baseUrl}${endpoint}`, {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
headers: { Authorization: `Bearer ${credentials.accessToken}` },
|
|
117
|
+
body: form,
|
|
118
|
+
});
|
|
119
|
+
return parseResult(res, post);
|
|
120
|
+
}
|
|
121
|
+
// Multi-photo posts (2–10 images) create an album-style feed
|
|
122
|
+
// post. Each image is pre-uploaded to /<page-id>/photos with
|
|
123
|
+
// `published=false` (multipart form data), then a single
|
|
124
|
+
// /<page-id>/feed post references all uploaded photo ids via
|
|
125
|
+
// `attached_media`.
|
|
126
|
+
const photosEndpoint = `/${version}/${encodeURIComponent(pageId)}/photos`;
|
|
127
|
+
const feedEndpoint = `/${version}/${encodeURIComponent(pageId)}/feed`;
|
|
128
|
+
const attachedMedia = [];
|
|
129
|
+
for (const media of post.media) {
|
|
130
|
+
const form = await photoForm(media, constraints.maxMediaSizeBytes);
|
|
131
|
+
form.append('published', 'false');
|
|
132
|
+
const uploadRes = await vendorFetch('facebook', `${baseUrl}${photosEndpoint}`, {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers: { Authorization: `Bearer ${credentials.accessToken}` },
|
|
135
|
+
body: form,
|
|
136
|
+
});
|
|
137
|
+
const uploadJson = uploadRes.body;
|
|
138
|
+
const photoId = uploadJson?.id;
|
|
139
|
+
if (!photoId) {
|
|
140
|
+
throw new SocialPublishError('[social/facebook] Multi-photo upload returned no id; vendor body: ' +
|
|
141
|
+
uploadRes.bodyText.slice(0, 200), { platform: 'facebook', raw: uploadRes.body });
|
|
142
|
+
}
|
|
143
|
+
attachedMedia.push({ media_fbid: photoId });
|
|
144
|
+
}
|
|
145
|
+
// Create the feed post referencing all uploaded photos. `link` is
|
|
146
|
+
// deliberately omitted: Meta cannot render a link preview and
|
|
147
|
+
// `attached_media` on the same story — same rule as LinkedIn.
|
|
148
|
+
const feedBody = {
|
|
149
|
+
message: post.body,
|
|
150
|
+
attached_media: attachedMedia,
|
|
151
|
+
};
|
|
152
|
+
const feedRes = await vendorFetch('facebook', `${baseUrl}${feedEndpoint}`, {
|
|
53
153
|
method: 'POST',
|
|
54
154
|
headers: {
|
|
55
155
|
Authorization: `Bearer ${credentials.accessToken}`,
|
|
56
156
|
'Content-Type': 'application/json',
|
|
57
157
|
},
|
|
58
|
-
body: JSON.stringify(
|
|
158
|
+
body: JSON.stringify(feedBody),
|
|
59
159
|
});
|
|
60
|
-
|
|
61
|
-
const externalRef = json?.post_id ?? json?.id;
|
|
62
|
-
if (!externalRef) {
|
|
63
|
-
throw new Error('[social/facebook] Publish returned no id; vendor body: ' + res.bodyText.slice(0, 200));
|
|
64
|
-
}
|
|
65
|
-
// Public URL form: facebook.com/<pageId>/posts/<postRef> (Meta
|
|
66
|
-
// composes a more user-friendly canonical URL behind the
|
|
67
|
-
// scenes, but the raw form always works).
|
|
68
|
-
return {
|
|
69
|
-
externalRef,
|
|
70
|
-
publishedUrl: `https://www.facebook.com/${encodeURIComponent(externalRef)}`,
|
|
71
|
-
publishedAt: new Date().toISOString(),
|
|
72
|
-
idempotencyKey: post.idempotencyKey,
|
|
73
|
-
correlationId: post.correlationId,
|
|
74
|
-
raw: json,
|
|
75
|
-
};
|
|
160
|
+
return parseResult(feedRes, post);
|
|
76
161
|
},
|
|
77
162
|
};
|
|
78
163
|
}
|
|
164
|
+
// ─── Internal helpers ─────────────────────────────────────────────────
|
|
79
165
|
function stripPagePrefix(authorRef) {
|
|
80
166
|
return authorRef.startsWith('page_') ? authorRef.slice(5) : authorRef;
|
|
81
167
|
}
|
|
82
|
-
function buildFeedBody(post
|
|
168
|
+
function buildFeedBody(post) {
|
|
83
169
|
const body = { message: post.body };
|
|
84
|
-
if (post.link
|
|
170
|
+
if (post.link) {
|
|
85
171
|
body.link = post.link;
|
|
86
172
|
}
|
|
87
|
-
if (hasMedia && post.media?.[0]) {
|
|
88
|
-
// For /<page-id>/photos, the `url` field carries the public
|
|
89
|
-
// image URL. Meta fetches it server-side. We pass through the
|
|
90
|
-
// post's first media URL verbatim.
|
|
91
|
-
body.url = post.media[0].url;
|
|
92
|
-
}
|
|
93
173
|
return body;
|
|
94
174
|
}
|
|
175
|
+
function parseResult(res, post) {
|
|
176
|
+
const json = res.body;
|
|
177
|
+
const externalRef = json?.post_id ?? json?.id;
|
|
178
|
+
if (!externalRef) {
|
|
179
|
+
throw new Error('[social/facebook] Publish returned no id; vendor body: ' + res.bodyText.slice(0, 200));
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
externalRef,
|
|
183
|
+
publishedUrl: `https://www.facebook.com/${encodeURIComponent(externalRef)}`,
|
|
184
|
+
publishedAt: new Date().toISOString(),
|
|
185
|
+
idempotencyKey: post.idempotencyKey,
|
|
186
|
+
correlationId: post.correlationId,
|
|
187
|
+
raw: json,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Build the multipart body for one `/<page-id>/photos` upload.
|
|
192
|
+
*
|
|
193
|
+
* Downloading in-process is what makes photo posts work everywhere:
|
|
194
|
+
* the URLs the CMS produces (presigned S3, MinIO on localhost, a
|
|
195
|
+
* private bucket with no CDN) are reachable from the publishing worker
|
|
196
|
+
* but not from Meta's servers, so we push the bytes rather than hand
|
|
197
|
+
* Meta a `url` to fetch.
|
|
198
|
+
*
|
|
199
|
+
* `downloadMedia` enforces the http(s)-only rule, the per-attachment
|
|
200
|
+
* byte cap and the abort timeout; `ensureUploadableImage` transcodes
|
|
201
|
+
* anything Meta's /photos endpoint won't take. The effective format
|
|
202
|
+
* comes from the payload's magic number, NOT `media.mimeType` — that
|
|
203
|
+
* field is caller metadata and goes stale (a WebP recorded as
|
|
204
|
+
* `image/jpeg` is exactly the failure this path guards against).
|
|
205
|
+
*
|
|
206
|
+
* The caller appends the fields that differ per flow (`message` for a
|
|
207
|
+
* single photo, `published=false` for an album member).
|
|
208
|
+
*/
|
|
209
|
+
async function photoForm(media, maxBytes) {
|
|
210
|
+
const downloaded = await downloadMedia(media.url, 'facebook', { maxBytes });
|
|
211
|
+
const { bytes, mimeType } = await ensureUploadableImage(downloaded, media.mimeType, FACEBOOK_UPLOAD_MIME, 'facebook');
|
|
212
|
+
const form = new FormData();
|
|
213
|
+
form.append('source', new Blob([bytes], { type: mimeType }), filenameForMime(mimeType));
|
|
214
|
+
return form;
|
|
215
|
+
}
|
|
95
216
|
//# sourceMappingURL=facebook.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"facebook.js","sourceRoot":"","sources":["../../src/adapters/facebook.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"facebook.js","sourceRoot":"","sources":["../../src/adapters/facebook.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,15 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* LinkedIn adapter —
|
|
2
|
+
* LinkedIn adapter — Posts API (REST).
|
|
3
3
|
*
|
|
4
|
-
* Endpoint: `POST https://api.linkedin.com/
|
|
4
|
+
* Endpoint: `POST https://api.linkedin.com/rest/posts`
|
|
5
5
|
* Auth: OAuth 2.0 bearer token in `Authorization: Bearer …`
|
|
6
6
|
* Author: `urn:li:organization:<id>` or `urn:li:person:<id>`
|
|
7
7
|
* — caller supplies via `credentials.authorRef`.
|
|
8
8
|
*
|
|
9
|
-
* The adapter
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* The adapter implements text, image, multi-image, and link-preview
|
|
10
|
+
* posts. Link previews use `content.article` (the Posts API does NOT
|
|
11
|
+
* scrape URLs — `title` and `description` are required fields we
|
|
12
|
+
* derive from the body). CTA buttons (`contentCallToActionLabel`) only
|
|
13
|
+
* render on sponsored content (ads), not organic posts, so we don't
|
|
14
|
+
* use them.
|
|
15
|
+
*
|
|
16
|
+
* Image staging:
|
|
17
|
+
* If `media.url` is already a LinkedIn image URN
|
|
18
|
+
* (`urn:li:image:…`) — e.g. the caller pre-staged the upload — the
|
|
19
|
+
* adapter uses it directly. Otherwise the adapter performs LinkedIn's
|
|
20
|
+
* two-step staging automatically:
|
|
21
|
+
* 1. `POST /rest/images?action=initializeUpload` → upload URL + image URN
|
|
22
|
+
* 2. `PUT` the image bytes to the upload URL
|
|
23
|
+
* 3. Use the image URN in the post's `content.media` or
|
|
24
|
+
* `content.multiImage` field
|
|
25
|
+
* This means callers can pass a plain image URL (presigned S3, CDN,
|
|
26
|
+
* etc.) without implementing the staging protocol themselves.
|
|
27
|
+
*
|
|
28
|
+
* LinkedIn's Images API accepts JPEG and PNG only (not WebP). The
|
|
29
|
+
* format is read from the downloaded bytes rather than
|
|
30
|
+
* `media.mimeType`, and anything else is converted to JPEG before
|
|
31
|
+
* upload. Conversion requires the optional `sharp` peer dependency;
|
|
32
|
+
* if it's not installed, the adapter throws a clear error.
|
|
13
33
|
*/
|
|
14
34
|
import type { SocialAdapter } from '../adapter.js';
|
|
15
35
|
import type { SocialPlatformConstraints } from '../types.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linkedin.d.ts","sourceRoot":"","sources":["../../src/adapters/linkedin.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"linkedin.d.ts","sourceRoot":"","sources":["../../src/adapters/linkedin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAIV,yBAAyB,EAG1B,MAAM,aAAa,CAAC;AAkBrB,eAAO,MAAM,oBAAoB,EAAE,yBAUlC,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,WAAW,CAAC,EAAE,OAAO,CAAC,yBAAyB,CAAC,CAAC;CAClD;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,GAAE,qBAA0B,GACjC,aAAa,CAiFf"}
|
|
@@ -1,19 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* LinkedIn adapter —
|
|
2
|
+
* LinkedIn adapter — Posts API (REST).
|
|
3
3
|
*
|
|
4
|
-
* Endpoint: `POST https://api.linkedin.com/
|
|
4
|
+
* Endpoint: `POST https://api.linkedin.com/rest/posts`
|
|
5
5
|
* Auth: OAuth 2.0 bearer token in `Authorization: Bearer …`
|
|
6
6
|
* Author: `urn:li:organization:<id>` or `urn:li:person:<id>`
|
|
7
7
|
* — caller supplies via `credentials.authorRef`.
|
|
8
8
|
*
|
|
9
|
-
* The adapter
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* The adapter implements text, image, multi-image, and link-preview
|
|
10
|
+
* posts. Link previews use `content.article` (the Posts API does NOT
|
|
11
|
+
* scrape URLs — `title` and `description` are required fields we
|
|
12
|
+
* derive from the body). CTA buttons (`contentCallToActionLabel`) only
|
|
13
|
+
* render on sponsored content (ads), not organic posts, so we don't
|
|
14
|
+
* use them.
|
|
15
|
+
*
|
|
16
|
+
* Image staging:
|
|
17
|
+
* If `media.url` is already a LinkedIn image URN
|
|
18
|
+
* (`urn:li:image:…`) — e.g. the caller pre-staged the upload — the
|
|
19
|
+
* adapter uses it directly. Otherwise the adapter performs LinkedIn's
|
|
20
|
+
* two-step staging automatically:
|
|
21
|
+
* 1. `POST /rest/images?action=initializeUpload` → upload URL + image URN
|
|
22
|
+
* 2. `PUT` the image bytes to the upload URL
|
|
23
|
+
* 3. Use the image URN in the post's `content.media` or
|
|
24
|
+
* `content.multiImage` field
|
|
25
|
+
* This means callers can pass a plain image URL (presigned S3, CDN,
|
|
26
|
+
* etc.) without implementing the staging protocol themselves.
|
|
27
|
+
*
|
|
28
|
+
* LinkedIn's Images API accepts JPEG and PNG only (not WebP). The
|
|
29
|
+
* format is read from the downloaded bytes rather than
|
|
30
|
+
* `media.mimeType`, and anything else is converted to JPEG before
|
|
31
|
+
* upload. Conversion requires the optional `sharp` peer dependency;
|
|
32
|
+
* if it's not installed, the adapter throws a clear error.
|
|
13
33
|
*/
|
|
34
|
+
import { SocialPublishError } from '../types.js';
|
|
14
35
|
import { vendorFetch } from '../http.js';
|
|
36
|
+
import { downloadMedia, ensureUploadableImage } from '../media.js';
|
|
15
37
|
import { validatePost } from '../validation.js';
|
|
16
38
|
const LINKEDIN_API_BASE = 'https://api.linkedin.com';
|
|
39
|
+
/**
|
|
40
|
+
* Required on every versioned (`/rest/*`) call. Format is `YYYYMM`;
|
|
41
|
+
* LinkedIn retires versions on a rolling ~1-year window, so this is
|
|
42
|
+
* the single place to bump.
|
|
43
|
+
*/
|
|
44
|
+
const LINKEDIN_VERSION = '202511';
|
|
45
|
+
/** LinkedIn's feedshare-image recipe accepts JPEG and PNG only. */
|
|
46
|
+
const LINKEDIN_ACCEPTED_MIME = new Set(['image/jpeg', 'image/png']);
|
|
17
47
|
export const LINKEDIN_CONSTRAINTS = {
|
|
18
48
|
maxBodyChars: 3000,
|
|
19
49
|
minBodyChars: 1,
|
|
@@ -43,28 +73,45 @@ export function createLinkedInAdapter(config = {}) {
|
|
|
43
73
|
return validatePost(post, constraints);
|
|
44
74
|
},
|
|
45
75
|
async publish(post, credentials) {
|
|
46
|
-
|
|
76
|
+
// Stage images that aren't already LinkedIn URNs.
|
|
77
|
+
//
|
|
78
|
+
// A link preview wins over media (`buildPostBody` emits
|
|
79
|
+
// `content.article` and ignores `stagedMedia`), so skip staging
|
|
80
|
+
// outright when `post.link` is set: uploading images the body
|
|
81
|
+
// will discard costs three vendor round-trips each and lets an
|
|
82
|
+
// irrelevant staging failure abort an otherwise-valid link post.
|
|
83
|
+
const media = post.link ? [] : (post.media ?? []);
|
|
84
|
+
const stagedMedia = [];
|
|
85
|
+
for (const m of media) {
|
|
86
|
+
if (m.url.startsWith('urn:li:image:')) {
|
|
87
|
+
// Caller already staged the upload — use the URN directly.
|
|
88
|
+
stagedMedia.push(m.url);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Auto-stage: download → initializeUpload → PUT bytes → URN.
|
|
92
|
+
stagedMedia.push(await stageImage(baseUrl, credentials, m, constraints.maxMediaSizeBytes));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Use the Posts API (/rest/posts). The older UGC Posts API
|
|
96
|
+
// (/v2/ugcPosts) is legacy and doesn't support the current
|
|
97
|
+
// content model (article / multiImage).
|
|
98
|
+
const body = buildPostBody(post, credentials, stagedMedia);
|
|
47
99
|
const headers = {
|
|
48
100
|
Authorization: `Bearer ${credentials.accessToken}`,
|
|
49
101
|
'Content-Type': 'application/json',
|
|
50
102
|
'X-Restli-Protocol-Version': '2.0.0',
|
|
103
|
+
'Linkedin-Version': LINKEDIN_VERSION,
|
|
51
104
|
};
|
|
52
|
-
|
|
53
|
-
// LinkedIn doesn't have a native Idempotency-Key header for
|
|
54
|
-
// UGC Posts; the value travels via the `originToken` field
|
|
55
|
-
// on the post body which LinkedIn dedupes against.
|
|
56
|
-
// (Implemented in buildUgcPost.)
|
|
57
|
-
}
|
|
58
|
-
const res = await vendorFetch('linkedin', `${baseUrl}/v2/ugcPosts`, {
|
|
105
|
+
const res = await vendorFetch('linkedin', `${baseUrl}/rest/posts`, {
|
|
59
106
|
method: 'POST',
|
|
60
107
|
headers,
|
|
61
108
|
body: JSON.stringify(body),
|
|
62
109
|
});
|
|
110
|
+
// The Posts API returns the post URN in the `x-restli-id` header
|
|
111
|
+
// (e.g. "urn:li:share:6844785523593134080") and an empty body.
|
|
63
112
|
const json = res.body;
|
|
64
|
-
const externalRef = json?.id ?? res.headers.get('x-linkedin-id') ?? '';
|
|
113
|
+
const externalRef = json?.id ?? res.headers.get('x-restli-id') ?? res.headers.get('x-linkedin-id') ?? '';
|
|
65
114
|
if (!externalRef) {
|
|
66
|
-
// LinkedIn always returns 201 with an id; if we didn't get
|
|
67
|
-
// one, the post probably WASN'T accepted — fail loud.
|
|
68
115
|
throw new Error('[social/linkedin] Publish returned no id; vendor body: ' + res.bodyText.slice(0, 200));
|
|
69
116
|
}
|
|
70
117
|
// First-comment companion thread. Best-effort: the post is
|
|
@@ -74,7 +121,6 @@ export function createLinkedInAdapter(config = {}) {
|
|
|
74
121
|
}
|
|
75
122
|
return {
|
|
76
123
|
externalRef,
|
|
77
|
-
// The public URL form is `https://www.linkedin.com/feed/update/<urn>`.
|
|
78
124
|
publishedUrl: `https://www.linkedin.com/feed/update/${encodeURIComponent(externalRef)}`,
|
|
79
125
|
publishedAt: new Date().toISOString(),
|
|
80
126
|
idempotencyKey: post.idempotencyKey,
|
|
@@ -84,12 +130,70 @@ export function createLinkedInAdapter(config = {}) {
|
|
|
84
130
|
},
|
|
85
131
|
};
|
|
86
132
|
}
|
|
133
|
+
// ─── Internal: image staging ──────────────────────────────────────────
|
|
134
|
+
/**
|
|
135
|
+
* Download an image from a URL, convert to JPEG if needed, initialize
|
|
136
|
+
* the upload with LinkedIn's Images API, push the bytes, and return
|
|
137
|
+
* the image URN (`urn:li:image:...`).
|
|
138
|
+
*
|
|
139
|
+
* Uses the Images API (`POST /rest/images?action=initializeUpload`)
|
|
140
|
+
* which returns `urn:li:image:...` URNs compatible with the Posts API.
|
|
141
|
+
* The older Assets API returned `urn:li:digitalmediaAsset:...` URNs
|
|
142
|
+
* which the Posts API rejects.
|
|
143
|
+
*/
|
|
144
|
+
async function stageImage(baseUrl, credentials, media, maxBytes) {
|
|
145
|
+
// Download the image (the calling process can reach presigned /
|
|
146
|
+
// private URLs that LinkedIn's servers cannot), then transcode if
|
|
147
|
+
// the image recipe won't take the format.
|
|
148
|
+
const downloaded = await downloadMedia(media.url, 'linkedin', { maxBytes });
|
|
149
|
+
const { bytes, mimeType } = await ensureUploadableImage(downloaded, media.mimeType, LINKEDIN_ACCEPTED_MIME, 'linkedin');
|
|
150
|
+
// Step 1: initialize the upload via the Images API.
|
|
151
|
+
const initBody = {
|
|
152
|
+
initializeUploadRequest: {
|
|
153
|
+
owner: credentials.authorRef,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
const initRes = await vendorFetch('linkedin', `${baseUrl}/rest/images?action=initializeUpload`, {
|
|
157
|
+
method: 'POST',
|
|
158
|
+
headers: {
|
|
159
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
160
|
+
'Content-Type': 'application/json',
|
|
161
|
+
'X-Restli-Protocol-Version': '2.0.0',
|
|
162
|
+
'Linkedin-Version': LINKEDIN_VERSION,
|
|
163
|
+
},
|
|
164
|
+
body: JSON.stringify(initBody),
|
|
165
|
+
});
|
|
166
|
+
const initJson = initRes.body;
|
|
167
|
+
const uploadUrl = initJson?.value?.uploadUrl;
|
|
168
|
+
const imageUrn = initJson?.value?.image;
|
|
169
|
+
if (!uploadUrl || !imageUrn) {
|
|
170
|
+
throw new SocialPublishError('[social/linkedin] initializeUpload returned no upload URL / image URN; vendor body: ' +
|
|
171
|
+
initRes.bodyText.slice(0, 500), { platform: 'linkedin', status: initRes.status, raw: initRes.body });
|
|
172
|
+
}
|
|
173
|
+
// Step 2: upload the bytes. The upload URL is a one-shot signed
|
|
174
|
+
// endpoint rather than the LinkedIn API proper, but it still goes
|
|
175
|
+
// through `vendorFetch` so the call inherits the abort timeout and
|
|
176
|
+
// the status → typed-error mapping.
|
|
177
|
+
await vendorFetch('linkedin', uploadUrl, {
|
|
178
|
+
method: 'PUT',
|
|
179
|
+
headers: {
|
|
180
|
+
'Content-Type': mimeType,
|
|
181
|
+
},
|
|
182
|
+
body: bytes,
|
|
183
|
+
});
|
|
184
|
+
return imageUrn;
|
|
185
|
+
}
|
|
87
186
|
// ─── Internal: first-comment ──────────────────────────────────────────
|
|
88
187
|
/**
|
|
89
|
-
* Post a first-comment under a freshly-published
|
|
188
|
+
* Post a first-comment under a freshly-published post via the
|
|
90
189
|
* `socialActions/{urn}/comments` endpoint. The comment is authored by
|
|
91
190
|
* the same actor as the post (`credentials.authorRef`).
|
|
92
191
|
*
|
|
192
|
+
* Still on `/v2`: comments have no `/rest` equivalent that accepts a
|
|
193
|
+
* `urn:li:share` object. `Linkedin-Version` is sent anyway so the call
|
|
194
|
+
* doesn't break when LinkedIn folds this endpoint into the versioned
|
|
195
|
+
* surface — the header is ignored on unversioned routes.
|
|
196
|
+
*
|
|
93
197
|
* Best-effort by contract: the post is already live, so a failed
|
|
94
198
|
* comment must never unwind a successful publish. We swallow vendor
|
|
95
199
|
* errors here and let the caller's publish result stand.
|
|
@@ -102,6 +206,7 @@ async function postFirstComment(baseUrl, credentials, postUrn, text) {
|
|
|
102
206
|
Authorization: `Bearer ${credentials.accessToken}`,
|
|
103
207
|
'Content-Type': 'application/json',
|
|
104
208
|
'X-Restli-Protocol-Version': '2.0.0',
|
|
209
|
+
'Linkedin-Version': LINKEDIN_VERSION,
|
|
105
210
|
},
|
|
106
211
|
body: JSON.stringify({
|
|
107
212
|
actor: credentials.authorRef,
|
|
@@ -114,43 +219,59 @@ async function postFirstComment(baseUrl, credentials, postUrn, text) {
|
|
|
114
219
|
// Swallowed — see doc comment.
|
|
115
220
|
}
|
|
116
221
|
}
|
|
117
|
-
function
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
const
|
|
222
|
+
function buildPostBody(post, credentials, stagedMedia) {
|
|
223
|
+
const hasMedia = stagedMedia.length > 0;
|
|
224
|
+
const hasLink = !!post.link;
|
|
225
|
+
const altTexts = post.media ?? [];
|
|
121
226
|
const body = {
|
|
122
227
|
author: credentials.authorRef,
|
|
228
|
+
commentary: post.body,
|
|
229
|
+
visibility: 'PUBLIC',
|
|
123
230
|
lifecycleState: 'PUBLISHED',
|
|
124
|
-
|
|
125
|
-
'
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
},
|
|
231
|
+
distribution: {
|
|
232
|
+
feedDistribution: 'MAIN_FEED',
|
|
233
|
+
targetEntities: [],
|
|
234
|
+
thirdPartyDistributionChannels: [],
|
|
129
235
|
},
|
|
130
|
-
|
|
236
|
+
isReshareDisabledByAuthor: false,
|
|
131
237
|
};
|
|
132
|
-
if (post.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
media: m.url,
|
|
144
|
-
}));
|
|
145
|
-
}
|
|
146
|
-
else if (hasArticleLink) {
|
|
147
|
-
body.specificContent['com.linkedin.ugc.ShareContent'].media = [
|
|
148
|
-
{
|
|
149
|
-
status: 'READY',
|
|
150
|
-
originalUrl: post.link,
|
|
238
|
+
if (hasLink && post.link) {
|
|
239
|
+
// Link preview post (content.article). LinkedIn can't combine
|
|
240
|
+
// a link preview with images, so media is dropped — same rule
|
|
241
|
+
// as Facebook. The Posts API does NOT scrape URLs, so title
|
|
242
|
+
// and description are required fields we derive from the body.
|
|
243
|
+
const firstLine = post.body.split('\n').find((l) => l.trim()) ?? post.body;
|
|
244
|
+
body.content = {
|
|
245
|
+
article: {
|
|
246
|
+
source: post.link,
|
|
247
|
+
title: firstLine.slice(0, 200),
|
|
248
|
+
description: post.body.slice(0, 400),
|
|
151
249
|
},
|
|
152
|
-
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
else if (hasMedia) {
|
|
253
|
+
if (stagedMedia.length === 1) {
|
|
254
|
+
// Single image — use content.media
|
|
255
|
+
body.content = {
|
|
256
|
+
media: {
|
|
257
|
+
id: stagedMedia[0],
|
|
258
|
+
...(altTexts[0]?.altText ? { title: altTexts[0].altText } : {}),
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
// Multiple images (2-20) — use content.multiImage
|
|
264
|
+
body.content = {
|
|
265
|
+
multiImage: {
|
|
266
|
+
images: stagedMedia.map((urn, i) => ({
|
|
267
|
+
id: urn,
|
|
268
|
+
...(altTexts[i]?.altText ? { altText: altTexts[i].altText } : {}),
|
|
269
|
+
})),
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
153
273
|
}
|
|
274
|
+
// else: text-only post, no content field
|
|
154
275
|
return body;
|
|
155
276
|
}
|
|
156
277
|
//# sourceMappingURL=linkedin.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linkedin.js","sourceRoot":"","sources":["../../src/adapters/linkedin.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"linkedin.js","sourceRoot":"","sources":["../../src/adapters/linkedin.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
CHANGED
|
@@ -17,7 +17,17 @@ import { type SocialPlatformKey } from './types.js';
|
|
|
17
17
|
export interface FetchOptions {
|
|
18
18
|
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
19
19
|
headers?: Record<string, string>;
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Request body.
|
|
22
|
+
*
|
|
23
|
+
* `FormData` (multipart uploads) and `Uint8Array` (raw binary
|
|
24
|
+
* uploads) are supported alongside the text forms so that adapters
|
|
25
|
+
* pushing image bytes can still go through this wrapper. Routing
|
|
26
|
+
* every vendor call through here is what keeps the timeout and the
|
|
27
|
+
* status → typed-error mapping uniform: an adapter that hand-rolls
|
|
28
|
+
* its own `fetch` silently loses 429 → `SocialRateLimitError`.
|
|
29
|
+
*/
|
|
30
|
+
body?: string | URLSearchParams | FormData | Uint8Array;
|
|
21
31
|
timeoutMs?: number;
|
|
22
32
|
/** Override the abort signal — e.g. when the caller propagates its own. */
|
|
23
33
|
signal?: AbortSignal;
|
package/dist/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAKL,KAAK,iBAAiB,EACvB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,GAAG,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAKL,KAAK,iBAAiB,EACvB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,eAAe,GAAG,QAAQ,GAAG,UAAU,CAAC;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,iBAAiB,EAC3B,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,WAAW,CAAC,CAmGtB"}
|
package/dist/http.js
CHANGED
|
@@ -34,15 +34,17 @@ export async function vendorFetch(platform, url, opts = {}) {
|
|
|
34
34
|
}
|
|
35
35
|
opts.signal.addEventListener('abort', () => controller.abort(), { once: true });
|
|
36
36
|
}
|
|
37
|
+
// Only text bodies get the JSON default. `FormData` MUST be left
|
|
38
|
+
// alone so `fetch` can generate the multipart boundary, and binary
|
|
39
|
+
// bodies always carry an explicit type from the caller.
|
|
40
|
+
const defaultsToJson = typeof opts.body === 'string' && !hasHeader(opts.headers, 'content-type');
|
|
37
41
|
let response;
|
|
38
42
|
try {
|
|
39
43
|
response = await fetch(url, {
|
|
40
44
|
method: opts.method ?? 'GET',
|
|
41
45
|
headers: {
|
|
42
46
|
Accept: 'application/json',
|
|
43
|
-
...(
|
|
44
|
-
? { 'Content-Type': 'application/json' }
|
|
45
|
-
: {}),
|
|
47
|
+
...(defaultsToJson ? { 'Content-Type': 'application/json' } : {}),
|
|
46
48
|
...(opts.headers ?? {}),
|
|
47
49
|
},
|
|
48
50
|
body: opts.body,
|
|
@@ -109,6 +111,13 @@ export async function vendorFetch(platform, url, opts = {}) {
|
|
|
109
111
|
raw: parsedBody,
|
|
110
112
|
});
|
|
111
113
|
}
|
|
114
|
+
/** Case-insensitive header lookup — vendors and callers disagree on casing. */
|
|
115
|
+
function hasHeader(headers, name) {
|
|
116
|
+
if (!headers)
|
|
117
|
+
return false;
|
|
118
|
+
const target = name.toLowerCase();
|
|
119
|
+
return Object.keys(headers).some((k) => k.toLowerCase() === target);
|
|
120
|
+
}
|
|
112
121
|
function parseRetryAfter(header) {
|
|
113
122
|
if (!header)
|
|
114
123
|
return undefined;
|
package/dist/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,GAEtB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export { createAdapterRegistry } from './registry.js';
|
|
|
24
24
|
export { validatePost, simplifyRatio } from './validation.js';
|
|
25
25
|
export { vendorFetch } from './http.js';
|
|
26
26
|
export type { FetchOptions, FetchResult } from './http.js';
|
|
27
|
+
export { downloadMedia, ensureUploadableImage, sniffImageMime, filenameForMime, } from './media.js';
|
|
28
|
+
export type { DownloadMediaOptions } from './media.js';
|
|
27
29
|
export { createLinkedInAdapter, LINKEDIN_CONSTRAINTS, createXAdapter, X_CONSTRAINTS, createFacebookAdapter, FACEBOOK_CONSTRAINTS, createInstagramAdapter, INSTAGRAM_CONSTRAINTS, } from './adapters/index.js';
|
|
28
30
|
export type { LinkedInAdapterConfig, XAdapterConfig, FacebookAdapterConfig, InstagramAdapterConfig, } from './adapters/index.js';
|
|
29
31
|
export { listSocialPlatforms, getSocialPlatformByKey, createSocialPlatform, patchSocialPlatform, deleteSocialPlatform, isUniqueViolation, isForeignKeyViolation, } from './catalog/index.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,YAAY,EACV,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,yBAAyB,EACzB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,YAAY,GACb,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AAGpB,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAGtD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAG9D,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAG3D,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,qBAAqB,EACrB,cAAc,EACd,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,oBAAoB,EACpB,iBAAiB,EACjB,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,YAAY,EACV,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,yBAAyB,EACzB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,YAAY,GACb,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AAGpB,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAGtD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAG9D,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAG3D,OAAO,EACL,aAAa,EACb,qBAAqB,EACrB,cAAc,EACd,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGvD,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,qBAAqB,EACrB,cAAc,EACd,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,oBAAoB,EACpB,iBAAiB,EACjB,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,8 @@ export { createAdapterRegistry } from './registry.js';
|
|
|
22
22
|
export { validatePost, simplifyRatio } from './validation.js';
|
|
23
23
|
// ─── HTTP helper (exposed for adapter authors building their own) ───
|
|
24
24
|
export { vendorFetch } from './http.js';
|
|
25
|
+
// ─── Media helpers (byte-upload adapters) ────────────────────────────
|
|
26
|
+
export { downloadMedia, ensureUploadableImage, sniffImageMime, filenameForMime, } from './media.js';
|
|
25
27
|
// ─── V1 adapters ─────────────────────────────────────────────────────
|
|
26
28
|
export { createLinkedInAdapter, LINKEDIN_CONSTRAINTS, createXAdapter, X_CONSTRAINTS, createFacebookAdapter, FACEBOOK_CONSTRAINTS, createInstagramAdapter, INSTAGRAM_CONSTRAINTS, } from './adapters/index.js';
|
|
27
29
|
// ─── Platform catalog (vk_social_platforms CRUD) ─────────────────────
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAiBH,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AAKpB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAEtD,wEAAwE;AACxE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE9D,uEAAuE;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAGxC,wEAAwE;AACxE,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAQ7B,wEAAwE;AACxE,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAQ5B,wEAAwE;AACxE,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAiBH,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AAKpB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAEtD,wEAAwE;AACxE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE9D,uEAAuE;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAGxC,wEAAwE;AACxE,OAAO,EACL,aAAa,EACb,qBAAqB,EACrB,cAAc,EACd,eAAe,GAChB,MAAM,YAAY,CAAC;AAGpB,wEAAwE;AACxE,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAQ7B,wEAAwE;AACxE,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAQ5B,wEAAwE;AACxE,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/media.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared media helpers for adapters that upload image *bytes* instead
|
|
3
|
+
* of handing the platform a URL to fetch server-side.
|
|
4
|
+
*
|
|
5
|
+
* Meta's `/photos` endpoint and LinkedIn's `registerUpload` flow both
|
|
6
|
+
* need the raw payload, because the URLs the CMS produces (presigned
|
|
7
|
+
* S3 / MinIO on localhost / private-VPC buckets without a CDN) are
|
|
8
|
+
* reachable from the publishing worker but not from the vendor.
|
|
9
|
+
*
|
|
10
|
+
* Everything here is deliberately platform-neutral: the adapters pass
|
|
11
|
+
* their own `SocialPlatformKey` and their own accepted-MIME set, so
|
|
12
|
+
* the download + convert logic exists exactly once.
|
|
13
|
+
*/
|
|
14
|
+
import { type SocialPlatformKey } from './types.js';
|
|
15
|
+
export interface DownloadMediaOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Reject payloads larger than this, in bytes. Pass `-1` to disable
|
|
18
|
+
* the cap (matches `SocialPlatformConstraints.maxMediaSizeBytes`).
|
|
19
|
+
*/
|
|
20
|
+
maxBytes: number;
|
|
21
|
+
/** Abort the download after this long. Defaults to 30s. */
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Download media bytes from a URL the calling process can reach.
|
|
26
|
+
*
|
|
27
|
+
* Throws `SocialValidationError` for problems that will never resolve
|
|
28
|
+
* on retry (unusable scheme, oversized payload) and
|
|
29
|
+
* `SocialPublishError` for transport failures the caller may retry.
|
|
30
|
+
*
|
|
31
|
+
* Only `http(s)` is accepted. The package cannot know which hosts are
|
|
32
|
+
* safe for a given deployment, so callers that accept user-supplied
|
|
33
|
+
* media URLs remain responsible for blocking internal targets
|
|
34
|
+
* (link-local, RFC1918, cloud metadata endpoints).
|
|
35
|
+
*/
|
|
36
|
+
export declare function downloadMedia(url: string, platform: SocialPlatformKey, opts: DownloadMediaOptions): Promise<Uint8Array>;
|
|
37
|
+
/**
|
|
38
|
+
* Normalise downloaded bytes into something the platform will accept.
|
|
39
|
+
*
|
|
40
|
+
* The effective format is read from the payload's magic number rather
|
|
41
|
+
* than `SocialMedia.mimeType`: that field is caller metadata and goes
|
|
42
|
+
* stale (a WebP recorded as `image/jpeg` is precisely the failure this
|
|
43
|
+
* module exists to prevent). The declared type is only a fallback for
|
|
44
|
+
* formats we don't recognise.
|
|
45
|
+
*
|
|
46
|
+
* Anything outside `accepted` is transcoded to JPEG via `sharp`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function ensureUploadableImage(bytes: Uint8Array, declaredMimeType: string, accepted: ReadonlySet<string>, platform: SocialPlatformKey): Promise<{
|
|
49
|
+
bytes: Uint8Array;
|
|
50
|
+
mimeType: string;
|
|
51
|
+
}>;
|
|
52
|
+
/**
|
|
53
|
+
* Identify an image by its magic number. Returns `undefined` when the
|
|
54
|
+
* format isn't one of the raster types the adapters deal with, which
|
|
55
|
+
* lets the caller fall back to whatever it was told.
|
|
56
|
+
*/
|
|
57
|
+
export declare function sniffImageMime(bytes: Uint8Array): string | undefined;
|
|
58
|
+
/** Upload filename hint. Vendors key off the MIME type, but a matching extension avoids confusion in their dashboards. */
|
|
59
|
+
export declare function filenameForMime(mimeType: string): string;
|
|
60
|
+
//# sourceMappingURL=media.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../src/media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,YAAY,CAAC;AAQpB,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,iBAAiB,EAC3B,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,UAAU,CAAC,CAoDrB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,UAAU,EACjB,gBAAgB,EAAE,MAAM,EACxB,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,EAC7B,QAAQ,EAAE,iBAAiB,GAC1B,OAAO,CAAC;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAMlD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CA4BpE;AAED,0HAA0H;AAC1H,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGxD"}
|
package/dist/media.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared media helpers for adapters that upload image *bytes* instead
|
|
3
|
+
* of handing the platform a URL to fetch server-side.
|
|
4
|
+
*
|
|
5
|
+
* Meta's `/photos` endpoint and LinkedIn's `registerUpload` flow both
|
|
6
|
+
* need the raw payload, because the URLs the CMS produces (presigned
|
|
7
|
+
* S3 / MinIO on localhost / private-VPC buckets without a CDN) are
|
|
8
|
+
* reachable from the publishing worker but not from the vendor.
|
|
9
|
+
*
|
|
10
|
+
* Everything here is deliberately platform-neutral: the adapters pass
|
|
11
|
+
* their own `SocialPlatformKey` and their own accepted-MIME set, so
|
|
12
|
+
* the download + convert logic exists exactly once.
|
|
13
|
+
*/
|
|
14
|
+
import { SocialPublishError, SocialValidationError, } from './types.js';
|
|
15
|
+
/** The format every adapter in this package can fall back to. */
|
|
16
|
+
const JPEG_MIME = 'image/jpeg';
|
|
17
|
+
/** Matches `vendorFetch`'s default so all outbound calls behave alike. */
|
|
18
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
19
|
+
/**
|
|
20
|
+
* Download media bytes from a URL the calling process can reach.
|
|
21
|
+
*
|
|
22
|
+
* Throws `SocialValidationError` for problems that will never resolve
|
|
23
|
+
* on retry (unusable scheme, oversized payload) and
|
|
24
|
+
* `SocialPublishError` for transport failures the caller may retry.
|
|
25
|
+
*
|
|
26
|
+
* Only `http(s)` is accepted. The package cannot know which hosts are
|
|
27
|
+
* safe for a given deployment, so callers that accept user-supplied
|
|
28
|
+
* media URLs remain responsible for blocking internal targets
|
|
29
|
+
* (link-local, RFC1918, cloud metadata endpoints).
|
|
30
|
+
*/
|
|
31
|
+
export async function downloadMedia(url, platform, opts) {
|
|
32
|
+
assertFetchableUrl(url, platform);
|
|
33
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
36
|
+
let res;
|
|
37
|
+
try {
|
|
38
|
+
res = await fetch(url, { signal: controller.signal });
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
if (err.name === 'AbortError') {
|
|
42
|
+
throw new SocialPublishError(`[social/${platform}] Media download timed out after ${timeoutMs}ms — ${url}`, { platform });
|
|
43
|
+
}
|
|
44
|
+
throw new SocialPublishError(`[social/${platform}] Network error downloading media: ${err.message} — ${url}`, { platform });
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
}
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
throw new SocialPublishError(`[social/${platform}] Could not fetch media for upload: ` +
|
|
51
|
+
`${res.status} ${res.statusText} — ${url}`, { platform, status: res.status });
|
|
52
|
+
}
|
|
53
|
+
// Cheap pre-check so an oversized object is refused before it is
|
|
54
|
+
// buffered. Kept alongside the post-check below because `fetch`
|
|
55
|
+
// omits Content-Length on chunked responses (and servers can lie).
|
|
56
|
+
const declaredLength = Number(res.headers.get('content-length'));
|
|
57
|
+
if (Number.isFinite(declaredLength) && exceedsCap(declaredLength, opts.maxBytes)) {
|
|
58
|
+
throw new SocialValidationError(`[social/${platform}] Media too large (${declaredLength} bytes; max ${opts.maxBytes}) — ${url}`, { platform });
|
|
59
|
+
}
|
|
60
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
61
|
+
if (exceedsCap(bytes.byteLength, opts.maxBytes)) {
|
|
62
|
+
throw new SocialValidationError(`[social/${platform}] Media too large (${bytes.byteLength} bytes; max ${opts.maxBytes}) — ${url}`, { platform });
|
|
63
|
+
}
|
|
64
|
+
return bytes;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Normalise downloaded bytes into something the platform will accept.
|
|
68
|
+
*
|
|
69
|
+
* The effective format is read from the payload's magic number rather
|
|
70
|
+
* than `SocialMedia.mimeType`: that field is caller metadata and goes
|
|
71
|
+
* stale (a WebP recorded as `image/jpeg` is precisely the failure this
|
|
72
|
+
* module exists to prevent). The declared type is only a fallback for
|
|
73
|
+
* formats we don't recognise.
|
|
74
|
+
*
|
|
75
|
+
* Anything outside `accepted` is transcoded to JPEG via `sharp`.
|
|
76
|
+
*/
|
|
77
|
+
export async function ensureUploadableImage(bytes, declaredMimeType, accepted, platform) {
|
|
78
|
+
const mimeType = sniffImageMime(bytes) ?? declaredMimeType;
|
|
79
|
+
if (accepted.has(mimeType)) {
|
|
80
|
+
return { bytes, mimeType };
|
|
81
|
+
}
|
|
82
|
+
return { bytes: await convertToJpeg(bytes, platform), mimeType: JPEG_MIME };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Identify an image by its magic number. Returns `undefined` when the
|
|
86
|
+
* format isn't one of the raster types the adapters deal with, which
|
|
87
|
+
* lets the caller fall back to whatever it was told.
|
|
88
|
+
*/
|
|
89
|
+
export function sniffImageMime(bytes) {
|
|
90
|
+
if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
|
|
91
|
+
return JPEG_MIME;
|
|
92
|
+
}
|
|
93
|
+
if (bytes.length >= 8 &&
|
|
94
|
+
bytes[0] === 0x89 &&
|
|
95
|
+
bytes[1] === 0x50 &&
|
|
96
|
+
bytes[2] === 0x4e &&
|
|
97
|
+
bytes[3] === 0x47 &&
|
|
98
|
+
bytes[4] === 0x0d &&
|
|
99
|
+
bytes[5] === 0x0a &&
|
|
100
|
+
bytes[6] === 0x1a &&
|
|
101
|
+
bytes[7] === 0x0a) {
|
|
102
|
+
return 'image/png';
|
|
103
|
+
}
|
|
104
|
+
if (bytes.length >= 12 && ascii(bytes, 0, 4) === 'RIFF' && ascii(bytes, 8, 12) === 'WEBP') {
|
|
105
|
+
return 'image/webp';
|
|
106
|
+
}
|
|
107
|
+
const gif = bytes.length >= 6 ? ascii(bytes, 0, 6) : '';
|
|
108
|
+
if (gif === 'GIF87a' || gif === 'GIF89a') {
|
|
109
|
+
return 'image/gif';
|
|
110
|
+
}
|
|
111
|
+
if (bytes.length >= 2 && bytes[0] === 0x42 && bytes[1] === 0x4d) {
|
|
112
|
+
return 'image/bmp';
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
/** Upload filename hint. Vendors key off the MIME type, but a matching extension avoids confusion in their dashboards. */
|
|
117
|
+
export function filenameForMime(mimeType) {
|
|
118
|
+
const ext = mimeType === 'image/png' ? 'png' : 'jpg';
|
|
119
|
+
return `upload.${ext}`;
|
|
120
|
+
}
|
|
121
|
+
// ─── Internal ──────────────────────────────────────────────────────────
|
|
122
|
+
function exceedsCap(bytes, maxBytes) {
|
|
123
|
+
return maxBytes > 0 && bytes > maxBytes;
|
|
124
|
+
}
|
|
125
|
+
function ascii(bytes, start, end) {
|
|
126
|
+
let out = '';
|
|
127
|
+
for (let i = start; i < end; i++) {
|
|
128
|
+
out += String.fromCharCode(bytes[i]);
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
function assertFetchableUrl(url, platform) {
|
|
133
|
+
let parsed;
|
|
134
|
+
try {
|
|
135
|
+
parsed = new URL(url);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
throw new SocialValidationError(`[social/${platform}] Media URL is not a valid URL: ${url}`, { platform });
|
|
139
|
+
}
|
|
140
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
141
|
+
throw new SocialValidationError(`[social/${platform}] Media URL must be http(s); got '${parsed.protocol}' — ${url}`, { platform });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function convertToJpeg(bytes, platform) {
|
|
145
|
+
const sharp = await loadSharp(platform);
|
|
146
|
+
try {
|
|
147
|
+
return await sharp(bytes).jpeg({ quality: 90 }).toBuffer();
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
// A payload sharp can't decode will never decode — permanent.
|
|
151
|
+
throw new SocialValidationError(`[social/${platform}] Could not convert media to JPEG: ${err.message}`, { platform });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Lazy-import `sharp` so the native binary is only loaded when an
|
|
156
|
+
* image actually needs transcoding. It is declared as an optional peer
|
|
157
|
+
* dependency — deployments that only ever post JPEG/PNG never need it,
|
|
158
|
+
* and the build doesn't require it either (see `src/sharp.d.ts`).
|
|
159
|
+
*/
|
|
160
|
+
async function loadSharp(platform) {
|
|
161
|
+
try {
|
|
162
|
+
return (await import('sharp')).default;
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
throw new SocialPublishError(`[social/${platform}] The optional 'sharp' dependency is required to convert this ` +
|
|
166
|
+
`image to JPEG. Install it with 'pnpm add sharp'. (${err.message})`, { platform });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=media.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.js","sourceRoot":"","sources":["../src/media.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -92,8 +92,11 @@ export interface SocialPost {
|
|
|
92
92
|
/** First-comment companion thread (LinkedIn, X). Ignored on platforms that don't support it. */
|
|
93
93
|
firstComment?: string;
|
|
94
94
|
/**
|
|
95
|
-
*
|
|
96
|
-
*
|
|
95
|
+
* Caller-side idempotency token. This is NEVER sent to a vendor: none
|
|
96
|
+
* of the supported platforms expose an idempotency field, and
|
|
97
|
+
* LinkedIn's Posts API rejects unknown body fields outright with
|
|
98
|
+
* `403 ACCESS_DENIED`. Adapters echo it back on `PublishResult` so the
|
|
99
|
+
* caller can dedupe retries against its own records.
|
|
97
100
|
*/
|
|
98
101
|
idempotencyKey?: string;
|
|
99
102
|
/** Caller-side correlation id (audit + log fan-out). */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,GAAG,GAAG,UAAU,GAAG,WAAW,GAAG,MAAM,CAAC;AAErF;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB;IAC/B,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;OASG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;IACtB,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gGAAgG;IAChG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAuMH,0EAA0E;AAE1E;;;;GAIG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAClC,QAAQ,CAAoB;IACrC,gEAAgE;IACvD,MAAM,CAAU;IACzB,mDAAmD;IAC1C,GAAG,CAAW;IACvB,YAAY,OAAe,EAAE,IAI5B;QACC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;IACtB,CAAC;CACF;AAED,iEAAiE;AACjE,MAAM,OAAO,eAAgB,SAAQ,kBAAkB;IACrD,YAAY,OAAe,EAAE,IAAqE;QAChG,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,yDAAyD;AACzD,MAAM,OAAO,oBAAqB,SAAQ,kBAAkB;IAC1D,yEAAyE;IAChE,iBAAiB,CAAU;IACpC,YACE,OAAe,EACf,IAAiG;QAEjG,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QACnC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,CAAC;IAClD,CAAC;CACF;AAED,6EAA6E;AAC7E,MAAM,OAAO,qBAAsB,SAAQ,kBAAkB;IAC3D,YAAY,OAAe,EAAE,IAAqE;QAChG,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@venturekit-pro/social",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
4
|
"description": "Social-platform publishing adapters + catalog for VentureKit applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -55,19 +55,24 @@
|
|
|
55
55
|
"publishing"
|
|
56
56
|
],
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@venturekit/core": "0.0.
|
|
58
|
+
"@venturekit/core": "0.0.22"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"@venturekit/data": "0.0.
|
|
61
|
+
"@venturekit/data": "0.0.22",
|
|
62
|
+
"sharp": ">=0.32"
|
|
62
63
|
},
|
|
63
64
|
"peerDependenciesMeta": {
|
|
64
65
|
"@venturekit/data": {
|
|
65
66
|
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"sharp": {
|
|
69
|
+
"optional": true
|
|
66
70
|
}
|
|
67
71
|
},
|
|
68
72
|
"devDependencies": {
|
|
69
|
-
"@types/node": "^26.1.
|
|
70
|
-
"@venturekit/data": "0.0.
|
|
73
|
+
"@types/node": "^26.1.2",
|
|
74
|
+
"@venturekit/data": "0.0.22",
|
|
75
|
+
"sharp": "^0.35.3",
|
|
71
76
|
"typescript": "^5.3.0"
|
|
72
77
|
},
|
|
73
78
|
"scripts": {
|