@venturekit-pro/social 0.0.19 → 0.0.21

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 CHANGED
@@ -107,14 +107,42 @@ and passes it per call.
107
107
 
108
108
  ## Media staging
109
109
 
110
- Image upload is **caller-side**: the package treats `SocialMedia.url` as
111
- an opaque platform reference. For LinkedIn / X the caller stages the
112
- upload via the platform's pre-upload endpoint and passes the resulting
113
- URN / `media_id_string` as `url`. For Facebook / Instagram the package
114
- calls Meta's `/photos` or `/media` endpoint with a public image URL
115
- directly.
116
-
117
- The CMS handles staging in its own pre-publish workflow.
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: `POST /v18.0/<page-id>/feed`
6
- * - Photo post: `POST /v18.0/<page-id>/photos`
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;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAIV,yBAAyB,EAE1B,MAAM,aAAa,CAAC;AAOrB,eAAO,MAAM,oBAAoB,EAAE,yBAUlC,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,CAwDf"}
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: `POST /v18.0/<page-id>/feed`
6
- * - Photo post: `POST /v18.0/<page-id>/photos`
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
- allowedMimeTypes: ['image/jpeg', 'image/png'],
26
- maxMediaCount: 1,
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 hasMedia = (post.media?.length ?? 0) > 0;
48
- const endpoint = hasMedia
49
- ? `/${version}/${encodeURIComponent(pageId)}/photos`
50
- : `/${version}/${encodeURIComponent(pageId)}/feed`;
51
- const body = buildFeedBody(post, hasMedia);
52
- const res = await vendorFetch('facebook', `${baseUrl}${endpoint}`, {
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(body),
158
+ body: JSON.stringify(feedBody),
59
159
  });
60
- const json = res.body;
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, hasMedia) {
168
+ function buildFeedBody(post) {
83
169
  const body = { message: post.body };
84
- if (post.link && !hasMedia) {
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":"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"}
1
+ {"version":3,"file":"facebook.js","sourceRoot":"","sources":["../../src/adapters/facebook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAUH,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,WAAW,EAAoB,MAAM,YAAY,CAAC;AAC3D,OAAO,EACL,aAAa,EACb,qBAAqB,EACrB,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD,MAAM,eAAe,GAAG,4BAA4B,CAAC;AACrD,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAEnC,2DAA2D;AAC3D,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC,CAAC;AAElE,oEAAoE;AACpE,MAAM,aAAa,GAAG,EAAE,CAAC;AAEzB,MAAM,CAAC,MAAM,oBAAoB,GAA8B;IAC7D,YAAY,EAAE,KAAK,EAAE,wDAAwD;IAC7E,YAAY,EAAE,CAAC;IACf,WAAW,EAAE,CAAC;IACd,WAAW,EAAE,EAAE;IACf,mEAAmE;IACnE,mEAAmE;IACnE,gBAAgB,EAAE,CAAC,YAAY,EAAE,WAAW,EAAE,YAAY,CAAC;IAC3D,+DAA+D;IAC/D,sEAAsE;IACtE,wDAAwD;IACxD,aAAa,EAAE,EAAE;IACjB,mBAAmB,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC;IAClD,iBAAiB,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;IACnC,oBAAoB,EAAE,KAAK;CAC5B,CAAC;AAQF,MAAM,UAAU,qBAAqB,CACnC,SAAgC,EAAE;IAElC,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,eAAe,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACxE,MAAM,OAAO,GAAG,MAAM,CAAC,YAAY,IAAI,kBAAkB,CAAC;IAC1D,MAAM,WAAW,GAA8B;QAC7C,GAAG,oBAAoB;QACvB,GAAG,MAAM,CAAC,WAAW;KACtB,CAAC;IAEF,OAAO;QACL,GAAG,EAAE,UAAU;QACf,WAAW,EAAE,UAAU;QACvB,WAAW;QAEX,QAAQ,CAAC,IAAgB;YACvB,OAAO,YAAY,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QACzC,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW;YAC7B,MAAM,MAAM,GAAG,eAAe,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;YACtD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC,CAAC;YAE3C,2DAA2D;YAC3D,sCAAsC;YACtC,IAAI,UAAU,KAAK,CAAC,EAAE,CAAC;gBACrB,MAAM,QAAQ,GAAG,IAAI,OAAO,IAAI,kBAAkB,CAAC,MAAM,CAAC,OAAO,CAAC;gBAClE,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,UAAU,EAAE,GAAG,OAAO,GAAG,QAAQ,EAAE,EAAE;oBACjE,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE;wBACP,aAAa,EAAE,UAAU,WAAW,CAAC,WAAW,EAAE;wBAClD,cAAc,EAAE,kBAAkB;qBACnC;oBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;iBAC1C,CAAC,CAAC;gBACH,OAAO,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAChC,CAAC;YAED,6DAA6D;YAC7D,8DAA8D;YAC9D,iEAAiE;YACjE,kEAAkE;YAClE,IAAI,UAAU,GAAG,aAAa,EAAE,CAAC;gBAC/B,MAAM,IAAI,qBAAqB,CAC7B,sCAAsC,UAAU,iBAAiB,aAAa,aAAa,EAC3F,EAAE,QAAQ,EAAE,UAAU,EAAE,CACzB,CAAC;YACJ,CAAC;YAED,+DAA+D;YAC/D,kEAAkE;YAClE,0DAA0D;YAC1D,YAAY;YACZ,IAAI,UAAU,KAAK,CAAC,EAAE,CAAC;gBACrB,MAAM,QAAQ,GAAG,IAAI,OAAO,IAAI,kBAAkB,CAAC,MAAM,CAAC,SAAS,CAAC;gBACpE,MAAM,IAAI,GAAG,MAAM,SAAS,CAC1B,IAAI,CAAC,KAAM,CAAC,CAAC,CAAE,EACf,WAAW,CAAC,iBAAiB,CAC9B,CAAC;gBACF,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;gBAElC,gEAAgE;gBAChE,8DAA8D;gBAC9D,uDAAuD;gBACvD,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,UAAU,EAAE,GAAG,OAAO,GAAG,QAAQ,EAAE,EAAE;oBACjE,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,WAAW,CAAC,WAAW,EAAE,EAAE;oBAC/D,IAAI,EAAE,IAAI;iBACX,CAAC,CAAC;gBACH,OAAO,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAChC,CAAC;YAED,6DAA6D;YAC7D,6DAA6D;YAC7D,yDAAyD;YACzD,6DAA6D;YAC7D,oBAAoB;YACpB,MAAM,cAAc,GAAG,IAAI,OAAO,IAAI,kBAAkB,CAAC,MAAM,CAAC,SAAS,CAAC;YAC1E,MAAM,YAAY,GAAG,IAAI,OAAO,IAAI,kBAAkB,CAAC,MAAM,CAAC,OAAO,CAAC;YACtE,MAAM,aAAa,GAAkC,EAAE,CAAC;YAExD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,KAAM,EAAE,CAAC;gBAChC,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAC;gBACnE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;gBAElC,MAAM,SAAS,GAAG,MAAM,WAAW,CACjC,UAAU,EACV,GAAG,OAAO,GAAG,cAAc,EAAE,EAC7B;oBACE,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,WAAW,CAAC,WAAW,EAAE,EAAE;oBAC/D,IAAI,EAAE,IAAI;iBACX,CACF,CAAC;gBACF,MAAM,UAAU,GAAG,SAAS,CAAC,IAA8B,CAAC;gBAC5D,MAAM,OAAO,GAAG,UAAU,EAAE,EAAE,CAAC;gBAC/B,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,MAAM,IAAI,kBAAkB,CAC1B,oEAAoE;wBAClE,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAClC,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,EAAE,SAAS,CAAC,IAAI,EAAE,CAC9C,CAAC;gBACJ,CAAC;gBACD,aAAa,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;YAC9C,CAAC;YAED,kEAAkE;YAClE,8DAA8D;YAC9D,8DAA8D;YAC9D,MAAM,QAAQ,GAAa;gBACzB,OAAO,EAAE,IAAI,CAAC,IAAI;gBAClB,cAAc,EAAE,aAAa;aAC9B,CAAC;YACF,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,UAAU,EAAE,GAAG,OAAO,GAAG,YAAY,EAAE,EAAE;gBACzE,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,aAAa,EAAE,UAAU,WAAW,CAAC,WAAW,EAAE;oBAClD,cAAc,EAAE,kBAAkB;iBACnC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;aAC/B,CAAC,CAAC;YACH,OAAO,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACpC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,yEAAyE;AAEzE,SAAS,eAAe,CAAC,SAAiB;IACxC,OAAO,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACxE,CAAC;AASD,SAAS,aAAa,CAAC,IAAgB;IACrC,MAAM,IAAI,GAAa,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IAC9C,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACxB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,WAAW,CAAC,GAAgB,EAAE,IAAgB;IACrD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAgD,CAAC;IAClE,MAAM,WAAW,GAAG,IAAI,EAAE,OAAO,IAAI,IAAI,EAAE,EAAE,CAAC;IAC9C,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,yDAAyD,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CACvF,CAAC;IACJ,CAAC;IACD,OAAO;QACL,WAAW;QACX,YAAY,EAAE,4BAA4B,kBAAkB,CAAC,WAAW,CAAC,EAAE;QAC3E,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACrC,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,GAAG,EAAE,IAAI;KACV,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,KAAK,UAAU,SAAS,CACtB,KAAkB,EAClB,QAAgB;IAEhB,MAAM,UAAU,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,GAAG,EAAE,UAAU,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC5E,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,qBAAqB,CACrD,UAAU,EACV,KAAK,CAAC,QAAQ,EACd,oBAAoB,EACpB,UAAU,CACX,CAAC;IAEF,MAAM,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAC;IAC5B,IAAI,CAAC,MAAM,CACT,QAAQ,EACR,IAAI,IAAI,CAAC,CAAC,KAA+B,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAC/D,eAAe,CAAC,QAAQ,CAAC,CAC1B,CAAC;IACF,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -1,15 +1,35 @@
1
1
  /**
2
- * LinkedIn adapter — UGC Posts API (v2).
2
+ * LinkedIn adapter — Posts API (REST).
3
3
  *
4
- * Endpoint: `POST https://api.linkedin.com/v2/ugcPosts`
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 only implements text + image posts (the 95% case for
10
- * editorial content). Article / video / multi-image carousels are
11
- * out of scope for the v1 ship and require the staged-upload
12
- * endpoint; deliberately deferred.
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;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAIV,yBAAyB,EAE1B,MAAM,aAAa,CAAC;AAMrB,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,CA+Df"}
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 — UGC Posts API (v2).
2
+ * LinkedIn adapter — Posts API (REST).
3
3
  *
4
- * Endpoint: `POST https://api.linkedin.com/v2/ugcPosts`
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 only implements text + image posts (the 95% case for
10
- * editorial content). Article / video / multi-image carousels are
11
- * out of scope for the v1 ship and require the staged-upload
12
- * endpoint; deliberately deferred.
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
- const body = buildUgcPost(post, credentials);
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
- if (post.idempotencyKey) {
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 UGC post via the
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 buildUgcPost(post, credentials) {
118
- const media = post.media ?? [];
119
- const hasMedia = media.length > 0;
120
- const hasArticleLink = !hasMedia && !!post.link;
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
- specificContent: {
125
- 'com.linkedin.ugc.ShareContent': {
126
- shareCommentary: { text: post.body },
127
- shareMediaCategory: hasMedia ? 'IMAGE' : hasArticleLink ? 'ARTICLE' : 'NONE',
128
- },
231
+ distribution: {
232
+ feedDistribution: 'MAIN_FEED',
233
+ targetEntities: [],
234
+ thirdPartyDistributionChannels: [],
129
235
  },
130
- visibility: { 'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC' },
236
+ isReshareDisabledByAuthor: false,
131
237
  };
132
- if (post.idempotencyKey) {
133
- body.originToken = post.idempotencyKey;
134
- }
135
- if (hasMedia) {
136
- body.specificContent['com.linkedin.ugc.ShareContent'].media = media.map((m) => ({
137
- status: 'READY',
138
- ...(m.altText ? { description: { text: m.altText } } : {}),
139
- // LinkedIn expects an image URN (`urn:li:digitalmediaAsset:…`),
140
- // not a public URL. For v1 we assume the caller has already
141
- // staged the upload + put the URN in `media.url`. Caller-side
142
- // staging is documented in the README.
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":"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"}
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
- body?: string | URLSearchParams;
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;
@@ -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;IAChC,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,CA+FtB"}
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
- ...(opts.body && !('Content-Type' in (opts.headers ?? {}))
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":"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"}
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';
@@ -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"}
@@ -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
- * Idempotency token forwarded to vendor APIs that support it (LinkedIn,
96
- * Meta Graph). Used to make retries safe.
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). */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
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;AAoMH,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"}
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.19",
3
+ "version": "0.0.21",
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.19"
58
+ "@venturekit/core": "0.0.21"
59
59
  },
60
60
  "peerDependencies": {
61
- "@venturekit/data": "0.0.19"
61
+ "@venturekit/data": "0.0.21",
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.0",
70
- "@venturekit/data": "0.0.19",
73
+ "@types/node": "^26.1.2",
74
+ "@venturekit/data": "0.0.21",
75
+ "sharp": "^0.35.3",
71
76
  "typescript": "^5.3.0"
72
77
  },
73
78
  "scripts": {