@opencoredev/social-sdk 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/cli-request.d.ts +15 -0
  2. package/dist/cli-request.js +193 -0
  3. package/dist/cli.d.ts +4 -3
  4. package/dist/cli.js +19 -21
  5. package/dist/cloud/common.d.ts +7 -6
  6. package/dist/cloud/common.js +35 -54
  7. package/dist/cloud/lifecycle.js +31 -35
  8. package/dist/cloud/media.d.ts +2 -2
  9. package/dist/cloud/media.js +13 -3
  10. package/dist/cloud/outcomes.d.ts +4 -3
  11. package/dist/cloud/outcomes.js +8 -15
  12. package/dist/cloud/post-for-me.js +41 -49
  13. package/dist/cloud/zernio.js +58 -98
  14. package/dist/core/client.js +79 -99
  15. package/dist/core/fields.d.ts +14 -0
  16. package/dist/core/fields.js +14 -0
  17. package/dist/core/idempotency.d.ts +7 -2
  18. package/dist/core/idempotency.js +37 -20
  19. package/dist/core/pagination.js +8 -7
  20. package/dist/core/types.d.ts +3 -2
  21. package/dist/platforms/bluesky.d.ts +65 -1
  22. package/dist/platforms/bluesky.js +675 -276
  23. package/dist/platforms/instagram.d.ts +2 -0
  24. package/dist/platforms/instagram.js +130 -105
  25. package/dist/platforms/linkedin.d.ts +58 -1
  26. package/dist/platforms/linkedin.js +877 -107
  27. package/dist/platforms/threads.d.ts +13 -1
  28. package/dist/platforms/threads.js +204 -302
  29. package/dist/platforms/tiktok.d.ts +4 -0
  30. package/dist/platforms/tiktok.js +140 -124
  31. package/dist/platforms/webhook-adapter.d.ts +9 -0
  32. package/dist/platforms/webhook-adapter.js +24 -0
  33. package/dist/platforms/x-engagement.js +7 -12
  34. package/dist/platforms/x-stream.d.ts +83 -0
  35. package/dist/platforms/x-stream.js +350 -0
  36. package/dist/platforms/x.d.ts +72 -0
  37. package/dist/platforms/x.js +328 -119
  38. package/dist/platforms/youtube-upload.d.ts +1 -1
  39. package/dist/platforms/youtube-upload.js +6 -2
  40. package/dist/platforms/youtube.d.ts +28 -4
  41. package/dist/platforms/youtube.js +291 -133
  42. package/dist/server/bluesky-oauth.d.ts +177 -0
  43. package/dist/server/bluesky-oauth.js +1229 -0
  44. package/dist/server/connections.d.ts +14 -0
  45. package/dist/server/connections.js +10 -2
  46. package/dist/server/egress.d.ts +14 -0
  47. package/dist/server/egress.js +115 -0
  48. package/dist/server/oauth-internal.d.ts +6 -0
  49. package/dist/server/oauth-internal.js +66 -0
  50. package/dist/server/oauth.d.ts +1 -1
  51. package/dist/server/oauth.js +46 -99
  52. package/dist/server/webhooks.d.ts +136 -3
  53. package/dist/server/webhooks.js +639 -25
  54. package/dist/testing/index.js +14 -28
  55. package/dist/transport/http.d.ts +1 -1
  56. package/dist/transport/http.js +0 -1
  57. package/dist/transport/json.d.ts +7 -0
  58. package/dist/transport/json.js +32 -4
  59. package/dist/transport/upload.d.ts +1 -1
  60. package/dist/transport/upload.js +46 -38
  61. package/dist/transport/validation.d.ts +16 -5
  62. package/dist/transport/validation.js +29 -7
  63. package/package.json +2 -2
@@ -22,8 +22,13 @@ export interface IdempotencyStore {
22
22
  readonly outcome: DeliveryOutcome;
23
23
  }): Promise<void>;
24
24
  }
25
- export declare function stableSerialize(value: unknown): string;
26
- export declare function fingerprint(value: unknown): Promise<string>;
25
+ /**
26
+ * Serializes any caller value to canonical JSON. The input is parsed at runtime:
27
+ * non-finite numbers, bigints, symbols, functions, `Blob`s, and cycles throw a TypeError.
28
+ */
29
+ export declare function stableSerialize<Payload>(value: Payload): string;
30
+ /** SHA-256 hex digest of {@link stableSerialize}. */
31
+ export declare function fingerprint<Payload>(value: Payload): Promise<string>;
27
32
  export declare function deriveTargetIdempotencyKey(input: {
28
33
  readonly logicalKey: string;
29
34
  readonly scope?: string;
@@ -1,43 +1,59 @@
1
+ function isJsonScalar(value) {
2
+ return value === null || typeof value === "string" || typeof value === "boolean";
3
+ }
4
+ function isNumber(value) {
5
+ return typeof value === "number";
6
+ }
7
+ /** Arrays and plain or class objects; excludes bigints, symbols, and functions. */
8
+ function isWalkableObject(value) {
9
+ return typeof value === "object" && value !== null;
10
+ }
11
+ /**
12
+ * Parses an arbitrary caller value into canonical JSON: object keys are sorted,
13
+ * undefined properties are dropped, and values JSON cannot represent throw a TypeError.
14
+ */
1
15
  function normalizeForJson(value, seen) {
2
- if (value === null || typeof value === "string" || typeof value === "boolean")
16
+ if (isJsonScalar(value))
3
17
  return value;
4
- if (typeof value === "number") {
18
+ if (isNumber(value)) {
5
19
  if (!Number.isFinite(value))
6
20
  throw new TypeError("Idempotency payload numbers must be finite");
7
21
  return value;
8
22
  }
9
- if (typeof value === "undefined")
23
+ if (value === undefined)
10
24
  return undefined;
11
- if (typeof value === "bigint" || typeof value === "symbol" || typeof value === "function") {
25
+ if (!isWalkableObject(value))
12
26
  throw new TypeError("Idempotency payload must be JSON-safe");
13
- }
14
27
  if (Array.isArray(value)) {
15
28
  if (seen.has(value))
16
29
  throw new TypeError("Idempotency payload must not contain cycles");
17
30
  seen.add(value);
18
- const result = value.map((entry) => normalizeForJson(entry, seen));
31
+ // JSON.stringify writes an undefined array entry as null, so mapping it here keeps the output identical.
32
+ const result = value.map((entry) => normalizeForJson(entry, seen) ?? null);
19
33
  seen.delete(value);
20
34
  return result;
21
35
  }
22
36
  if (typeof Blob !== "undefined" && value instanceof Blob) {
23
37
  throw new TypeError("Blob inputs require a caller-provided media fingerprint");
24
38
  }
25
- if (typeof value === "object") {
26
- if (seen.has(value))
27
- throw new TypeError("Idempotency payload must not contain cycles");
28
- seen.add(value);
29
- const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
30
- const result = {};
31
- for (const [key, entry] of entries) {
32
- const normalized = normalizeForJson(entry, seen);
33
- if (normalized !== undefined)
34
- result[key] = normalized;
35
- }
36
- seen.delete(value);
37
- return result;
39
+ if (seen.has(value))
40
+ throw new TypeError("Idempotency payload must not contain cycles");
41
+ seen.add(value);
42
+ const entries = Object.entries(value);
43
+ entries.sort(([left], [right]) => left.localeCompare(right));
44
+ const result = {};
45
+ for (const [key, entry] of entries) {
46
+ const normalized = normalizeForJson(entry, seen);
47
+ if (normalized !== undefined)
48
+ result[key] = normalized;
38
49
  }
39
- throw new TypeError("Unsupported idempotency payload value");
50
+ seen.delete(value);
51
+ return result;
40
52
  }
53
+ /**
54
+ * Serializes any caller value to canonical JSON. The input is parsed at runtime:
55
+ * non-finite numbers, bigints, symbols, functions, `Blob`s, and cycles throw a TypeError.
56
+ */
41
57
  export function stableSerialize(value) {
42
58
  return JSON.stringify(normalizeForJson(value, new Set()));
43
59
  }
@@ -47,6 +63,7 @@ function bytesToHex(bytes) {
47
63
  result += byte.toString(16).padStart(2, "0");
48
64
  return result;
49
65
  }
66
+ /** SHA-256 hex digest of {@link stableSerialize}. */
50
67
  export async function fingerprint(value) {
51
68
  const bytes = new TextEncoder().encode(stableSerialize(value));
52
69
  const digest = await crypto.subtle.digest("SHA-256", bytes);
@@ -56,18 +56,19 @@ export function encodeCursor(scope, value) {
56
56
  });
57
57
  return `social-v1.${encodeURIComponent(JSON.stringify([scope, value]))}`;
58
58
  }
59
+ /** A decoded cursor body: `[scope, upstreamCursor]`. */
60
+ function isCursorPayload(value) {
61
+ return (Array.isArray(value) &&
62
+ value.length === 2 &&
63
+ typeof value[0] === "string" &&
64
+ typeof value[1] === "string");
65
+ }
59
66
  export function decodeCursor(scope, cursor) {
60
67
  try {
61
68
  if (!cursor.startsWith("social-v1.") || cursor.length > 100_000)
62
69
  throw new Error();
63
70
  const parsed = JSON.parse(decodeURIComponent(cursor.slice(10)));
64
- if (!Array.isArray(parsed) ||
65
- parsed.length !== 2 ||
66
- parsed[0] !== scope ||
67
- // oxlint-disable-next-line anti-slop/no-runtime-typeof -- Validate the untrusted decoded cursor tuple.
68
- typeof parsed[1] !== "string" ||
69
- !parsed[1] ||
70
- parsed[1].length > 16_384)
71
+ if (!isCursorPayload(parsed) || parsed[0] !== scope || !parsed[1] || parsed[1].length > 16_384)
71
72
  throw new Error();
72
73
  return parsed[1];
73
74
  }
@@ -94,7 +94,8 @@ export type MediaInput = {
94
94
  readonly fingerprint: string;
95
95
  };
96
96
  export interface MediaAttachment {
97
- readonly kind: "image" | "video";
97
+ /** `document` covers paged files such as PDF, PPTX or DOCX where a platform accepts them. */
98
+ readonly kind: "image" | "video" | "document";
98
99
  readonly source: MediaInput;
99
100
  readonly mimeType?: string;
100
101
  readonly filename?: string;
@@ -327,7 +328,7 @@ export interface CapabilityDeclaration {
327
328
  readonly operation: string;
328
329
  readonly platform: Platform | "*";
329
330
  readonly availability: CapabilityAvailability;
330
- readonly formats?: readonly ("text" | "image" | "video" | "carousel" | "sequence")[];
331
+ readonly formats?: readonly ("text" | "image" | "video" | "carousel" | "sequence" | "document")[];
331
332
  readonly requiredScopes?: readonly string[];
332
333
  readonly notes?: string;
333
334
  }
@@ -17,6 +17,36 @@ export interface BlueskyOptions {
17
17
  readonly did: string;
18
18
  readonly fetchHandler: (pathname: string, init?: RequestInit) => Promise<Response>;
19
19
  };
20
+ /** HTTPS origin of the Bluesky video service. Defaults to https://video.bsky.app. */
21
+ readonly videoService?: string;
22
+ /**
23
+ * Service DID of the account's PDS (for example `did:web:pds.example.com`). It is the
24
+ * audience of the service token that lets the video service store the processed blob.
25
+ * When omitted, `uploadVideo` reads the `#atproto_pds` endpoint from the DID document
26
+ * returned by `com.atproto.server.getSession`.
27
+ */
28
+ readonly pdsDid?: string;
29
+ }
30
+ /** A Bluesky video processing job (`app.bsky.video.defs#jobStatus`). */
31
+ export interface BlueskyVideoJob {
32
+ readonly jobId: string;
33
+ readonly did: string;
34
+ /** `JOB_STATE_COMPLETED`, `JOB_STATE_FAILED`, or an in-progress state. */
35
+ readonly state: string;
36
+ readonly progress?: number;
37
+ /** The processed blob. Present once the video is stored on the PDS. */
38
+ readonly blob?: JsonObject;
39
+ readonly failureCode?: string;
40
+ readonly error?: string;
41
+ readonly message?: string;
42
+ }
43
+ /** Output of `app.bsky.video.getUploadLimits` for the configured account. */
44
+ export interface BlueskyVideoUploadLimits {
45
+ readonly canUpload: boolean;
46
+ readonly remainingDailyVideos?: number;
47
+ readonly remainingDailyBytes?: number;
48
+ readonly message?: string;
49
+ readonly error?: string;
20
50
  }
21
51
  export interface BlueskyPostRef {
22
52
  readonly uri: string;
@@ -84,12 +114,29 @@ export interface BlueskyNative {
84
114
  readonly account: ConnectedAccountRef;
85
115
  readonly context?: AdapterOperationContext;
86
116
  }) => Promise<void>;
117
+ /**
118
+ * Sends one MP4 to the video service with a single upload request and returns the
119
+ * processing job. It does not wait for processing; poll `getVideoJobStatus` explicitly.
120
+ */
87
121
  readonly uploadVideo: (input: {
88
122
  readonly account: ConnectedAccountRef;
89
123
  readonly video: Blob;
90
124
  readonly mimeType?: string;
125
+ /** File name reported to the video service. Defaults to `video.mp4`. */
126
+ readonly name?: string;
91
127
  readonly context?: AdapterOperationContext;
92
- }) => Promise<JsonObject>;
128
+ }) => Promise<BlueskyVideoJob>;
129
+ /** Reads a video processing job once. The caller decides when to check again. */
130
+ readonly getVideoJobStatus: (input: {
131
+ readonly account: ConnectedAccountRef;
132
+ readonly jobId: string;
133
+ readonly context?: AdapterOperationContext;
134
+ }) => Promise<BlueskyVideoJob>;
135
+ /** Reads the account's daily video upload allowance from the video service. */
136
+ readonly getVideoUploadLimits: (input: {
137
+ readonly account: ConnectedAccountRef;
138
+ readonly context?: AdapterOperationContext;
139
+ }) => Promise<BlueskyVideoUploadLimits>;
93
140
  readonly follow: (input: {
94
141
  readonly account: ConnectedAccountRef;
95
142
  readonly did: string;
@@ -186,6 +233,23 @@ export interface BlueskyNative {
186
233
  readonly text: string;
187
234
  readonly context?: AdapterOperationContext;
188
235
  }) => Promise<JsonObject>;
236
+ /**
237
+ * Hides or unhides a reply in a thread whose root post belongs to this account.
238
+ * Updates the root post's `app.bsky.feed.threadgate` record `hiddenReplies` list.
239
+ */
240
+ readonly hideReply: (input: BlueskyHideReplyInput) => Promise<BlueskyHideReplyResult>;
241
+ }
242
+ export interface BlueskyHideReplyInput {
243
+ readonly account: ConnectedAccountRef;
244
+ /** AT-URI of the reply post to hide or unhide. */
245
+ readonly replyUri: string;
246
+ readonly hidden: boolean;
247
+ readonly context?: AdapterOperationContext;
248
+ }
249
+ export interface BlueskyHideReplyResult {
250
+ readonly hidden: boolean;
251
+ /** The threadgate record after the change. Absent when no threadgate exists. */
252
+ readonly threadgate?: BlueskyPostRef;
189
253
  }
190
254
  export interface BlueskyPageInput {
191
255
  readonly account: ConnectedAccountRef;