@opencoredev/social-sdk 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/cloud/common.d.ts.map +1 -1
- package/dist/cloud/common.js +48 -20
- package/dist/cloud/common.js.map +1 -1
- package/dist/cloud/lifecycle.d.ts.map +1 -1
- package/dist/cloud/lifecycle.js +5 -0
- package/dist/cloud/lifecycle.js.map +1 -1
- package/dist/cloud/media.d.ts.map +1 -1
- package/dist/cloud/media.js +12 -8
- package/dist/cloud/media.js.map +1 -1
- package/dist/cloud/outcomes.d.ts.map +1 -1
- package/dist/cloud/outcomes.js +17 -9
- package/dist/cloud/outcomes.js.map +1 -1
- package/dist/cloud/post-for-me.d.ts.map +1 -1
- package/dist/cloud/post-for-me.js +99 -28
- package/dist/cloud/post-for-me.js.map +1 -1
- package/dist/cloud/zernio.d.ts.map +1 -1
- package/dist/cloud/zernio.js +100 -9
- package/dist/cloud/zernio.js.map +1 -1
- package/dist/core/adapter.d.ts +42 -1
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js.map +1 -1
- package/dist/core/client.d.ts +35 -1
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +503 -110
- package/dist/core/client.js.map +1 -1
- package/dist/core/concurrency.d.ts.map +1 -1
- package/dist/core/concurrency.js +5 -3
- package/dist/core/concurrency.js.map +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +14 -7
- package/dist/core/errors.js.map +1 -1
- package/dist/core/idempotency.d.ts.map +1 -1
- package/dist/core/idempotency.js.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/pagination.d.ts.map +1 -1
- package/dist/core/pagination.js +1 -0
- package/dist/core/pagination.js.map +1 -1
- package/dist/core/types.d.ts +70 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +10 -0
- package/dist/core/types.js.map +1 -1
- package/dist/platforms/bluesky.d.ts +131 -1
- package/dist/platforms/bluesky.d.ts.map +1 -1
- package/dist/platforms/bluesky.js +772 -44
- package/dist/platforms/bluesky.js.map +1 -1
- package/dist/platforms/instagram.d.ts +70 -3
- package/dist/platforms/instagram.d.ts.map +1 -1
- package/dist/platforms/instagram.js +403 -47
- package/dist/platforms/instagram.js.map +1 -1
- package/dist/platforms/linkedin.d.ts +55 -0
- package/dist/platforms/linkedin.d.ts.map +1 -1
- package/dist/platforms/linkedin.js +350 -39
- package/dist/platforms/linkedin.js.map +1 -1
- package/dist/platforms/threads.d.ts +37 -4
- package/dist/platforms/threads.d.ts.map +1 -1
- package/dist/platforms/threads.js +419 -44
- package/dist/platforms/threads.js.map +1 -1
- package/dist/platforms/tiktok.d.ts.map +1 -1
- package/dist/platforms/tiktok.js +109 -26
- package/dist/platforms/tiktok.js.map +1 -1
- package/dist/platforms/x-engagement.d.ts.map +1 -1
- package/dist/platforms/x-engagement.js +4 -0
- package/dist/platforms/x-engagement.js.map +1 -1
- package/dist/platforms/x.d.ts +227 -9
- package/dist/platforms/x.d.ts.map +1 -1
- package/dist/platforms/x.js +826 -49
- package/dist/platforms/x.js.map +1 -1
- package/dist/platforms/youtube-upload.d.ts.map +1 -1
- package/dist/platforms/youtube-upload.js +25 -9
- package/dist/platforms/youtube-upload.js.map +1 -1
- package/dist/platforms/youtube.d.ts +56 -2
- package/dist/platforms/youtube.d.ts.map +1 -1
- package/dist/platforms/youtube.js +571 -31
- package/dist/platforms/youtube.js.map +1 -1
- package/dist/server/connections.d.ts.map +1 -1
- package/dist/server/connections.js +4 -2
- package/dist/server/connections.js.map +1 -1
- package/dist/server/credentials.d.ts.map +1 -1
- package/dist/server/credentials.js +10 -16
- package/dist/server/credentials.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/oauth.d.ts +5 -0
- package/dist/server/oauth.d.ts.map +1 -1
- package/dist/server/oauth.js +118 -47
- package/dist/server/oauth.js.map +1 -1
- package/dist/server/webhooks.d.ts.map +1 -1
- package/dist/server/webhooks.js +11 -4
- package/dist/server/webhooks.js.map +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +153 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/transport/binary.d.ts.map +1 -1
- package/dist/transport/binary.js.map +1 -1
- package/dist/transport/budget.d.ts.map +1 -1
- package/dist/transport/budget.js.map +1 -1
- package/dist/transport/http.d.ts +2 -1
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +42 -12
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/json.d.ts +7 -1
- package/dist/transport/json.d.ts.map +1 -1
- package/dist/transport/json.js +7 -1
- package/dist/transport/json.js.map +1 -1
- package/dist/transport/upload.d.ts +1 -0
- package/dist/transport/upload.d.ts.map +1 -1
- package/dist/transport/upload.js +38 -17
- package/dist/transport/upload.js.map +1 -1
- package/dist/transport/validation.d.ts.map +1 -1
- package/dist/transport/validation.js +1 -0
- package/dist/transport/validation.js.map +1 -1
- package/package.json +2 -1
package/dist/core/client.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* oxlint-disable anti-slop/require-readable-spacing, anti-slop/no-conditional-empty-object-spread, anti-slop/require-safety-comment-for-type-assertion -- facade dispatch keeps capability-specific branches together. */
|
|
1
2
|
import { decodeCursor, encodeCursor, iterateItems } from "./pagination.js";
|
|
2
3
|
import { createConcurrencyLimiter } from "./concurrency.js";
|
|
3
4
|
import { SocialError } from "./errors.js";
|
|
@@ -44,12 +45,10 @@ function mediaFingerprintView(content) {
|
|
|
44
45
|
};
|
|
45
46
|
}
|
|
46
47
|
function preparationIssue(code, message, targetIndex) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
...(targetIndex === undefined ? {} : { targetIndex }),
|
|
52
|
-
};
|
|
48
|
+
const issue = { code, message, severity: "error" };
|
|
49
|
+
if (targetIndex !== undefined)
|
|
50
|
+
Object.assign(issue, { targetIndex });
|
|
51
|
+
return issue;
|
|
53
52
|
}
|
|
54
53
|
function publicationStatus(outcomes) {
|
|
55
54
|
const pending = new Set(["scheduled", "accepted", "processing"]);
|
|
@@ -76,16 +75,18 @@ async function mapConcurrent(count, concurrency, execute) {
|
|
|
76
75
|
return results;
|
|
77
76
|
}
|
|
78
77
|
function makeContext(backendInstance, correlationId, options, targetIdempotencyKey) {
|
|
79
|
-
|
|
78
|
+
const context = {
|
|
80
79
|
backendInstance,
|
|
81
80
|
correlationId,
|
|
82
81
|
retryBudget: options?.retryBudget ?? { maxAttempts: 1, maxElapsedMs: 30_000 },
|
|
83
|
-
...(options?.signal === undefined ? {} : { signal: options.signal }),
|
|
84
|
-
...(options?.authorization === undefined ? {} : { authorization: options.authorization }),
|
|
85
|
-
...(targetIdempotencyKey === undefined
|
|
86
|
-
? {}
|
|
87
|
-
: { idempotencyKey: targetIdempotencyKey, targetIdempotencyKey }),
|
|
88
82
|
};
|
|
83
|
+
if (options?.signal !== undefined)
|
|
84
|
+
Object.assign(context, { signal: options.signal });
|
|
85
|
+
if (options?.authorization !== undefined)
|
|
86
|
+
Object.assign(context, { authorization: options.authorization });
|
|
87
|
+
if (targetIdempotencyKey !== undefined)
|
|
88
|
+
Object.assign(context, { idempotencyKey: targetIdempotencyKey, targetIdempotencyKey });
|
|
89
|
+
return context;
|
|
89
90
|
}
|
|
90
91
|
function unsupported(operation, backend) {
|
|
91
92
|
throw new SocialError({
|
|
@@ -95,7 +96,9 @@ function unsupported(operation, backend) {
|
|
|
95
96
|
message: `${backend} does not implement ${operation}`,
|
|
96
97
|
});
|
|
97
98
|
}
|
|
98
|
-
|
|
99
|
+
// oxlint-disable-next-line anti-slop/no-unknown-parameters -- Adapter rejections enter the error boundary here.
|
|
100
|
+
function outcomeFromError(error, // Transport and adapter rejections can be arbitrary JavaScript values.
|
|
101
|
+
target, observedAt) {
|
|
99
102
|
if (error instanceof SocialError) {
|
|
100
103
|
if (error.code === "cancelled") {
|
|
101
104
|
return {
|
|
@@ -183,7 +186,17 @@ export function createSocial(config) {
|
|
|
183
186
|
message: `Unknown backend instance: ${backend}`,
|
|
184
187
|
});
|
|
185
188
|
}
|
|
186
|
-
return limiter(signal, operation, work)
|
|
189
|
+
return limiter(signal, operation, work).catch((error) => {
|
|
190
|
+
if (error instanceof SocialError)
|
|
191
|
+
throw error;
|
|
192
|
+
throw new SocialError({
|
|
193
|
+
code: "upstream_failure",
|
|
194
|
+
operation,
|
|
195
|
+
backend,
|
|
196
|
+
message: "The adapter failed while processing the request",
|
|
197
|
+
cause: error,
|
|
198
|
+
});
|
|
199
|
+
});
|
|
187
200
|
}
|
|
188
201
|
const clock = config.clock ?? (() => new Date());
|
|
189
202
|
let correlationSequence = 0;
|
|
@@ -250,10 +263,13 @@ export function createSocial(config) {
|
|
|
250
263
|
targetKey: key,
|
|
251
264
|
account: target.account,
|
|
252
265
|
content: mergeContent(request.content, target.content),
|
|
253
|
-
...(target.options === undefined ? {} : { options: target.options }),
|
|
254
|
-
...(request.schedule === undefined ? {} : { schedule: request.schedule }),
|
|
255
|
-
...(replyTo === undefined ? {} : { replyTo }),
|
|
256
266
|
};
|
|
267
|
+
if (target.options !== undefined)
|
|
268
|
+
Object.assign(prepared, { options: target.options });
|
|
269
|
+
if (request.schedule !== undefined)
|
|
270
|
+
Object.assign(prepared, { schedule: request.schedule });
|
|
271
|
+
if (replyTo !== undefined)
|
|
272
|
+
Object.assign(prepared, { replyTo });
|
|
257
273
|
targets.push(prepared);
|
|
258
274
|
try {
|
|
259
275
|
issues.push(...adapter.posts.prepareTarget(prepared));
|
|
@@ -284,6 +300,41 @@ export function createSocial(config) {
|
|
|
284
300
|
});
|
|
285
301
|
}
|
|
286
302
|
}
|
|
303
|
+
async function authorizeRefWithFallback(operation, fallback, account, options, correlationId) {
|
|
304
|
+
try {
|
|
305
|
+
await authorizeRef(operation, account, options, correlationId);
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
if (!(error instanceof SocialError) || error.code !== "unauthorized")
|
|
309
|
+
throw error;
|
|
310
|
+
await authorizeRef(fallback, account, options, correlationId);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function validateAccountRef(account, operation) {
|
|
314
|
+
if (account === null ||
|
|
315
|
+
typeof account !== "object" ||
|
|
316
|
+
account.kind !== "connected-account" ||
|
|
317
|
+
account.version !== 1 ||
|
|
318
|
+
typeof account.backend !== "string" ||
|
|
319
|
+
!account.backend.trim() ||
|
|
320
|
+
!account.platform ||
|
|
321
|
+
typeof account.accountId !== "string" ||
|
|
322
|
+
!account.accountId.trim())
|
|
323
|
+
throw new SocialError({
|
|
324
|
+
code: "invalid_input",
|
|
325
|
+
operation,
|
|
326
|
+
message: "A valid connected account reference is required",
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
function validDateOnly(value) {
|
|
330
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value))
|
|
331
|
+
return false;
|
|
332
|
+
const parsed = new Date(`${value}T00:00:00Z`);
|
|
333
|
+
return !Number.isNaN(parsed.getTime()) && parsed.toISOString().slice(0, 10) === value;
|
|
334
|
+
}
|
|
335
|
+
function validTimestamp(value) {
|
|
336
|
+
return typeof value === "string" && value.trim() !== "" && !Number.isNaN(Date.parse(value));
|
|
337
|
+
}
|
|
287
338
|
function selected(ref, operation) {
|
|
288
339
|
if (ref.kind !== undefined && (ref.kind === "" || ref.version !== 1)) {
|
|
289
340
|
throw new SocialError({
|
|
@@ -497,18 +548,8 @@ export function createSocial(config) {
|
|
|
497
548
|
callOptions?.authorization?.tenantId ?? null,
|
|
498
549
|
callOptions?.limit ?? null,
|
|
499
550
|
]);
|
|
500
|
-
const received = await dispatch(first[0], callOptions?.signal, "accounts.read", () => first[1].accounts.list(
|
|
501
|
-
|
|
502
|
-
? {}
|
|
503
|
-
: { cursor: decodeCursor(cursorScope, callOptions.cursor) }),
|
|
504
|
-
...(callOptions?.limit === undefined ? {} : { limit: callOptions.limit }),
|
|
505
|
-
}, makeContext(first[0], correlationId, callOptions)));
|
|
506
|
-
const page = {
|
|
507
|
-
...received,
|
|
508
|
-
...(received.nextCursor === undefined
|
|
509
|
-
? {}
|
|
510
|
-
: { nextCursor: encodeCursor(cursorScope, received.nextCursor) }),
|
|
511
|
-
};
|
|
551
|
+
const received = await dispatch(first[0], callOptions?.signal, "accounts.read", () => first[1].accounts.list(decodePageOptions(cursorScope, callOptions), makeContext(first[0], correlationId, callOptions)));
|
|
552
|
+
const page = encodePage(cursorScope, received);
|
|
512
553
|
if (config.authorization === undefined)
|
|
513
554
|
return page;
|
|
514
555
|
const decisions = await config.authorization.authorizeTargets({
|
|
@@ -517,14 +558,17 @@ export function createSocial(config) {
|
|
|
517
558
|
context: makeContext(first[0], correlationId, callOptions),
|
|
518
559
|
});
|
|
519
560
|
const allowed = new Set(decisions.filter((item) => item.allowed).map((item) => targetKey(item.account)));
|
|
520
|
-
|
|
561
|
+
const filtered = {
|
|
521
562
|
items: page.items.filter((item) => allowed.has(targetKey(item.ref))),
|
|
522
|
-
...(page.nextCursor === undefined ? {} : { nextCursor: page.nextCursor }),
|
|
523
|
-
...(page.metadata === undefined ? {} : { metadata: page.metadata }),
|
|
524
563
|
};
|
|
564
|
+
if (page.nextCursor !== undefined)
|
|
565
|
+
Object.assign(filtered, { nextCursor: page.nextCursor });
|
|
566
|
+
if (page.metadata !== undefined)
|
|
567
|
+
Object.assign(filtered, { metadata: page.metadata });
|
|
568
|
+
return filtered;
|
|
525
569
|
},
|
|
526
570
|
iterate(callOptions) {
|
|
527
|
-
return iterateItems((cursor) => accountsFacade.list(
|
|
571
|
+
return iterateItems((cursor) => accountsFacade.list(iterationPageOptions(callOptions, cursor)), callOptions);
|
|
528
572
|
},
|
|
529
573
|
async get(ref, callOptions) {
|
|
530
574
|
const correlationId = `social-${++correlationSequence}`;
|
|
@@ -536,6 +580,137 @@ export function createSocial(config) {
|
|
|
536
580
|
return dispatch(ref.backend, callOptions?.signal, "accounts.read", () => adapter.accounts.get(ref, makeContext(ref.backend, correlationId, callOptions)));
|
|
537
581
|
},
|
|
538
582
|
};
|
|
583
|
+
const graphFacade = {
|
|
584
|
+
async getProfile(account, input, callOptions) {
|
|
585
|
+
validateAccountRef(account, "profiles.read");
|
|
586
|
+
if (input === null ||
|
|
587
|
+
typeof input !== "object" ||
|
|
588
|
+
(input.profileId !== undefined &&
|
|
589
|
+
input.handle !== undefined &&
|
|
590
|
+
typeof input.profileId !== "string" &&
|
|
591
|
+
typeof input.handle !== "string") ||
|
|
592
|
+
(input.profileId !== undefined &&
|
|
593
|
+
(typeof input.profileId !== "string" || input.profileId.trim() === "")) ||
|
|
594
|
+
(input.handle !== undefined &&
|
|
595
|
+
(typeof input.handle !== "string" || input.handle.trim() === "")))
|
|
596
|
+
throw new SocialError({
|
|
597
|
+
code: "invalid_input",
|
|
598
|
+
operation: "profiles.read",
|
|
599
|
+
message: "Profile selector fields must be nonempty strings when provided",
|
|
600
|
+
});
|
|
601
|
+
const correlationId = `social-${++correlationSequence}`;
|
|
602
|
+
await authorizeRef("profiles.read", account, callOptions, correlationId);
|
|
603
|
+
const adapter = selected(account, "profiles.read");
|
|
604
|
+
requireCapability(adapter, "profiles.read", account.platform, account.backend);
|
|
605
|
+
if (adapter.graph?.getProfile === undefined)
|
|
606
|
+
unsupported("profiles.read", account.backend);
|
|
607
|
+
return dispatch(account.backend, callOptions?.signal, "profiles.read", () => adapter.graph.getProfile(account, input, makeContext(account.backend, correlationId, callOptions)));
|
|
608
|
+
},
|
|
609
|
+
async listRelationships(account, input, callOptions) {
|
|
610
|
+
validateAccountRef(account, "graph.read");
|
|
611
|
+
if (input === null ||
|
|
612
|
+
typeof input !== "object" ||
|
|
613
|
+
!["following", "followers", "blocked", "muted"].includes(input.kind) ||
|
|
614
|
+
(input.limit !== undefined && (!Number.isSafeInteger(input.limit) || input.limit < 1)))
|
|
615
|
+
throw new SocialError({
|
|
616
|
+
code: "invalid_input",
|
|
617
|
+
operation: "graph.read",
|
|
618
|
+
message: !["following", "followers", "blocked", "muted"].includes(input?.kind)
|
|
619
|
+
? "Relationship kind must be following, followers, blocked, or muted"
|
|
620
|
+
: "Relationship page limit must be a positive integer",
|
|
621
|
+
});
|
|
622
|
+
const correlationId = `social-${++correlationSequence}`;
|
|
623
|
+
await authorizeRef("graph.read", account, callOptions, correlationId);
|
|
624
|
+
const adapter = selected(account, "graph.read");
|
|
625
|
+
requireCapability(adapter, "graph.read", account.platform, account.backend);
|
|
626
|
+
if (adapter.graph?.listRelationships === undefined)
|
|
627
|
+
unsupported("graph.read", account.backend);
|
|
628
|
+
const scope = JSON.stringify([
|
|
629
|
+
account.backend,
|
|
630
|
+
"graph.read",
|
|
631
|
+
callOptions?.authorization?.tenantId ?? null,
|
|
632
|
+
account.platform,
|
|
633
|
+
account.accountId,
|
|
634
|
+
input.kind,
|
|
635
|
+
input.limit ?? null,
|
|
636
|
+
]);
|
|
637
|
+
const page = await dispatch(account.backend, callOptions?.signal, "graph.read", () => adapter.graph.listRelationships(account, {
|
|
638
|
+
...input,
|
|
639
|
+
...(input.cursor === undefined ? {} : { cursor: decodeCursor(scope, input.cursor) }),
|
|
640
|
+
}, makeContext(account.backend, correlationId, callOptions)));
|
|
641
|
+
return encodePage(scope, page);
|
|
642
|
+
},
|
|
643
|
+
follow: (target, options) => graphMutation(target, "graph.follow", options),
|
|
644
|
+
unfollow: (target, options) => graphMutation(target, "graph.unfollow", options),
|
|
645
|
+
block: (target, options) => graphMutation(target, "graph.block", options),
|
|
646
|
+
unblock: (target, options) => graphMutation(target, "graph.unblock", options),
|
|
647
|
+
mute: (target, options) => graphMutation(target, "graph.mute", options),
|
|
648
|
+
unmute: (target, options) => graphMutation(target, "graph.unmute", options),
|
|
649
|
+
};
|
|
650
|
+
async function graphMutation(target, operation, callOptions) {
|
|
651
|
+
if (target === null ||
|
|
652
|
+
typeof target !== "object" ||
|
|
653
|
+
target.kind !== "profile" ||
|
|
654
|
+
target.version !== 1 ||
|
|
655
|
+
typeof target.backend !== "string" ||
|
|
656
|
+
target.backend.trim() === "" ||
|
|
657
|
+
typeof target.platform !== "string" ||
|
|
658
|
+
target.platform.trim() === "" ||
|
|
659
|
+
typeof target.accountId !== "string" ||
|
|
660
|
+
target.accountId.trim() === "" ||
|
|
661
|
+
typeof target.profileId !== "string" ||
|
|
662
|
+
target.profileId.trim() === "")
|
|
663
|
+
throw new SocialError({
|
|
664
|
+
code: "invalid_input",
|
|
665
|
+
operation,
|
|
666
|
+
message: "A valid profile reference is required",
|
|
667
|
+
});
|
|
668
|
+
const account = {
|
|
669
|
+
kind: "connected-account",
|
|
670
|
+
version: 1,
|
|
671
|
+
backend: target.backend,
|
|
672
|
+
platform: target.platform,
|
|
673
|
+
accountId: target.accountId,
|
|
674
|
+
};
|
|
675
|
+
const correlationId = `social-${++correlationSequence}`;
|
|
676
|
+
await authorizeRef(operation, account, callOptions, correlationId);
|
|
677
|
+
const adapter = selected(target, operation);
|
|
678
|
+
requireCapability(adapter, operation, target.platform, target.backend);
|
|
679
|
+
if (adapter.graph === undefined)
|
|
680
|
+
unsupported(operation, target.backend);
|
|
681
|
+
const context = makeContext(target.backend, correlationId, callOptions);
|
|
682
|
+
return dispatch(target.backend, callOptions?.signal, operation, async () => {
|
|
683
|
+
switch (operation) {
|
|
684
|
+
case "graph.follow":
|
|
685
|
+
if (adapter.graph.follow === undefined)
|
|
686
|
+
unsupported(operation, target.backend);
|
|
687
|
+
return await adapter.graph.follow(target, context);
|
|
688
|
+
case "graph.unfollow":
|
|
689
|
+
if (adapter.graph.unfollow === undefined)
|
|
690
|
+
unsupported(operation, target.backend);
|
|
691
|
+
await adapter.graph.unfollow(target, context);
|
|
692
|
+
return undefined;
|
|
693
|
+
case "graph.block":
|
|
694
|
+
if (adapter.graph.block === undefined)
|
|
695
|
+
unsupported(operation, target.backend);
|
|
696
|
+
return await adapter.graph.block(target, context);
|
|
697
|
+
case "graph.unblock":
|
|
698
|
+
if (adapter.graph.unblock === undefined)
|
|
699
|
+
unsupported(operation, target.backend);
|
|
700
|
+
await adapter.graph.unblock(target, context);
|
|
701
|
+
return undefined;
|
|
702
|
+
case "graph.mute":
|
|
703
|
+
if (adapter.graph.mute === undefined)
|
|
704
|
+
unsupported(operation, target.backend);
|
|
705
|
+
return await adapter.graph.mute(target, context);
|
|
706
|
+
case "graph.unmute":
|
|
707
|
+
if (adapter.graph.unmute === undefined)
|
|
708
|
+
unsupported(operation, target.backend);
|
|
709
|
+
await adapter.graph.unmute(target, context);
|
|
710
|
+
return undefined;
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
}
|
|
539
714
|
async function lifecycle(ref, expectedKind, operation, callOptions) {
|
|
540
715
|
if (ref.kind !== expectedKind || ref.version !== 1 || !ref.accountId || !ref.platform)
|
|
541
716
|
throw new SocialError({
|
|
@@ -571,25 +746,12 @@ export function createSocial(config) {
|
|
|
571
746
|
account.accountId,
|
|
572
747
|
callOptions?.limit ?? null,
|
|
573
748
|
]);
|
|
574
|
-
const input =
|
|
575
|
-
...(callOptions?.cursor === undefined
|
|
576
|
-
? {}
|
|
577
|
-
: { cursor: decodeCursor(scope, callOptions.cursor) }),
|
|
578
|
-
...(callOptions?.limit === undefined ? {} : { limit: callOptions.limit }),
|
|
579
|
-
};
|
|
749
|
+
const input = decodePageOptions(scope, callOptions);
|
|
580
750
|
const page = await dispatch(account.backend, callOptions?.signal, "posts.list", () => adapter.posts.list(account, input, makeContext(account.backend, correlationId, callOptions)));
|
|
581
|
-
return
|
|
582
|
-
...page,
|
|
583
|
-
...(page.nextCursor === undefined
|
|
584
|
-
? {}
|
|
585
|
-
: { nextCursor: encodeCursor(scope, page.nextCursor) }),
|
|
586
|
-
};
|
|
751
|
+
return encodePage(scope, page);
|
|
587
752
|
},
|
|
588
753
|
iterate(account, callOptions) {
|
|
589
|
-
return iterateItems((cursor) => postsFacade.list(account,
|
|
590
|
-
...callOptions,
|
|
591
|
-
...(cursor === undefined ? {} : { cursor }),
|
|
592
|
-
}), callOptions);
|
|
754
|
+
return iterateItems((cursor) => postsFacade.list(account, iterationPageOptions(callOptions, cursor)), callOptions);
|
|
593
755
|
},
|
|
594
756
|
async cancelScheduled(ref, callOptions) {
|
|
595
757
|
const { adapter, context } = await lifecycle(ref, "scheduled-job", "posts.cancelScheduled", callOptions);
|
|
@@ -618,25 +780,105 @@ export function createSocial(config) {
|
|
|
618
780
|
operation: "posts.publishSequence",
|
|
619
781
|
message: "A sequence requires at least one item",
|
|
620
782
|
});
|
|
621
|
-
|
|
783
|
+
if (typeof request.idempotencyKey !== "string" || request.idempotencyKey.trim() === "")
|
|
784
|
+
throw new SocialError({
|
|
785
|
+
code: "invalid_input",
|
|
786
|
+
operation: "posts.publishSequence",
|
|
787
|
+
message: "A nonempty idempotencyKey is required",
|
|
788
|
+
});
|
|
789
|
+
const prepared = [];
|
|
622
790
|
for (const [index, item] of request.items.entries()) {
|
|
623
|
-
const
|
|
791
|
+
const publishRequest = {
|
|
624
792
|
targets: item.targets,
|
|
625
793
|
content: item.content,
|
|
626
794
|
idempotencyKey: `${request.idempotencyKey}:${index}`,
|
|
627
|
-
|
|
628
|
-
|
|
795
|
+
};
|
|
796
|
+
if (item.replyTo !== undefined)
|
|
797
|
+
Object.assign(publishRequest, { replyTo: item.replyTo });
|
|
798
|
+
const chained = request.replyToPrevious === true && index > 0;
|
|
799
|
+
if (chained) {
|
|
800
|
+
const account = item.targets[0]?.account;
|
|
801
|
+
const parent = request.items[index - 1]?.targets[0]?.account;
|
|
802
|
+
// A chain replies to the single post the previous item created, so every link
|
|
803
|
+
// must publish to one target on the same account.
|
|
804
|
+
if (item.targets.length !== 1 ||
|
|
805
|
+
request.items[index - 1]?.targets.length !== 1 ||
|
|
806
|
+
item.replyTo !== undefined ||
|
|
807
|
+
item.targets[0]?.replyTo !== undefined ||
|
|
808
|
+
account === undefined ||
|
|
809
|
+
parent === undefined ||
|
|
810
|
+
account.backend !== parent.backend ||
|
|
811
|
+
account.platform !== parent.platform ||
|
|
812
|
+
account.accountId !== parent.accountId)
|
|
813
|
+
throw new SocialError({
|
|
814
|
+
code: "invalid_input",
|
|
815
|
+
operation: "posts.publishSequence",
|
|
816
|
+
message: "replyToPrevious requires every item to publish to the same single account without its own replyTo",
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
const plan = prepare(chained
|
|
820
|
+
? {
|
|
821
|
+
...publishRequest,
|
|
822
|
+
// Placeholder parent so adapters validate reply support before anything is sent.
|
|
823
|
+
replyTo: {
|
|
824
|
+
kind: "platform-post",
|
|
825
|
+
version: 1,
|
|
826
|
+
backend: item.targets[0].account.backend,
|
|
827
|
+
platform: item.targets[0].account.platform,
|
|
828
|
+
accountId: item.targets[0].account.accountId,
|
|
829
|
+
postId: "preflight",
|
|
830
|
+
},
|
|
831
|
+
}
|
|
832
|
+
: publishRequest);
|
|
833
|
+
if (!plan.ok)
|
|
834
|
+
throw new SocialError({
|
|
835
|
+
code: "invalid_input",
|
|
836
|
+
operation: "posts.publishSequence",
|
|
837
|
+
message: "Publication preparation failed; no targets were dispatched",
|
|
838
|
+
issues: plan.issues,
|
|
839
|
+
});
|
|
840
|
+
for (const target of plan.targets)
|
|
841
|
+
await authorizeRef("posts.publish", target.account, callOptions, `social-${++correlationSequence}`);
|
|
842
|
+
prepared.push(publishRequest);
|
|
843
|
+
}
|
|
844
|
+
const results = [];
|
|
845
|
+
const failures = [];
|
|
846
|
+
let previous;
|
|
847
|
+
for (const [index, publishRequest] of prepared.entries()) {
|
|
848
|
+
const chained = request.replyToPrevious === true && index > 0;
|
|
849
|
+
// Without a published parent the item would post as an unrelated root post.
|
|
850
|
+
if (chained && previous === undefined)
|
|
851
|
+
break;
|
|
852
|
+
const nextRequest = chained && previous !== undefined
|
|
853
|
+
? { ...publishRequest, replyTo: previous }
|
|
854
|
+
: publishRequest;
|
|
855
|
+
let result;
|
|
856
|
+
previous = undefined;
|
|
857
|
+
try {
|
|
858
|
+
result = await publish(nextRequest, callOptions);
|
|
859
|
+
}
|
|
860
|
+
catch (error) {
|
|
861
|
+
if (error instanceof SocialError) {
|
|
862
|
+
failures.push({ index, code: error.code, message: error.message });
|
|
863
|
+
if (request.stopOnFailure !== false)
|
|
864
|
+
break;
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
throw error;
|
|
868
|
+
}
|
|
629
869
|
results.push(result);
|
|
630
|
-
|
|
870
|
+
const published = result.outcomes.find((outcome) => outcome.state === "published");
|
|
871
|
+
if (published?.post !== undefined)
|
|
872
|
+
previous = published.post;
|
|
873
|
+
if (request.stopOnFailure !== false && result.status === "partial")
|
|
631
874
|
break;
|
|
632
875
|
}
|
|
633
|
-
const status = results.length
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
? "partial"
|
|
876
|
+
const status = results.length < request.items.length || results.some((item) => item.status === "partial")
|
|
877
|
+
? "partial"
|
|
878
|
+
: results.every((item) => item.status === "complete")
|
|
879
|
+
? "complete"
|
|
638
880
|
: "pending";
|
|
639
|
-
return { status, items: results };
|
|
881
|
+
return { status, items: results, failures };
|
|
640
882
|
},
|
|
641
883
|
async get(ref, callOptions) {
|
|
642
884
|
const correlationId = `social-${++correlationSequence}`;
|
|
@@ -666,10 +908,61 @@ export function createSocial(config) {
|
|
|
666
908
|
const adapter = selected(ref, "posts.getDelivery");
|
|
667
909
|
requireCapability(adapter, "posts.status", ref.platform, ref.backend);
|
|
668
910
|
if (adapter.posts?.getDelivery === undefined)
|
|
669
|
-
unsupported("posts.
|
|
911
|
+
unsupported("posts.status", ref.backend);
|
|
670
912
|
return dispatch(ref.backend, callOptions?.signal, "posts.status", () => adapter.posts.getDelivery(ref, makeContext(ref.backend, correlationId, callOptions)));
|
|
671
913
|
},
|
|
672
914
|
};
|
|
915
|
+
const searchFacade = {
|
|
916
|
+
async posts(account, input, callOptions) {
|
|
917
|
+
validateAccountRef(account, "search.posts");
|
|
918
|
+
if (input === null ||
|
|
919
|
+
input === undefined ||
|
|
920
|
+
typeof input.query !== "string" ||
|
|
921
|
+
!input.query.trim() ||
|
|
922
|
+
(input.limit !== undefined && (!Number.isSafeInteger(input.limit) || input.limit < 1)) ||
|
|
923
|
+
(input.startTime !== undefined &&
|
|
924
|
+
(typeof input.startTime !== "string" || !validTimestamp(input.startTime))) ||
|
|
925
|
+
(input.endTime !== undefined &&
|
|
926
|
+
(typeof input.endTime !== "string" || !validTimestamp(input.endTime))) ||
|
|
927
|
+
(input.startTime !== undefined &&
|
|
928
|
+
input.endTime !== undefined &&
|
|
929
|
+
Date.parse(input.startTime) > Date.parse(input.endTime)) ||
|
|
930
|
+
(input.scope !== undefined && input.scope !== "recent" && input.scope !== "all"))
|
|
931
|
+
throw new SocialError({
|
|
932
|
+
code: "invalid_input",
|
|
933
|
+
operation: "search.posts",
|
|
934
|
+
message: "Search requires an account reference, a nonempty query, and a positive page limit.",
|
|
935
|
+
});
|
|
936
|
+
const correlationId = `social-${++correlationSequence}`;
|
|
937
|
+
await authorizeRef("search.posts", account, callOptions, correlationId);
|
|
938
|
+
const adapter = selected(account, "search.posts");
|
|
939
|
+
requireCapability(adapter, "search.posts", account.platform, account.backend);
|
|
940
|
+
const search = adapter.search;
|
|
941
|
+
if (search === undefined)
|
|
942
|
+
unsupported("search.posts", account.backend);
|
|
943
|
+
const scope = await fingerprint([
|
|
944
|
+
account.backend,
|
|
945
|
+
"search.posts",
|
|
946
|
+
callOptions?.authorization?.tenantId ?? null,
|
|
947
|
+
account.platform,
|
|
948
|
+
account.accountId,
|
|
949
|
+
input.query,
|
|
950
|
+
input.limit ?? null,
|
|
951
|
+
input.startTime ?? null,
|
|
952
|
+
input.endTime ?? null,
|
|
953
|
+
input.scope ?? "recent",
|
|
954
|
+
]);
|
|
955
|
+
const providerInput = { ...input };
|
|
956
|
+
if (input.cursor !== undefined) {
|
|
957
|
+
Object.assign(providerInput, { cursor: decodeCursor(scope, input.cursor) });
|
|
958
|
+
}
|
|
959
|
+
const page = await dispatch(account.backend, callOptions?.signal, "search.posts", () => search.posts(account, providerInput, makeContext(account.backend, correlationId, callOptions)));
|
|
960
|
+
return encodePage(scope, page);
|
|
961
|
+
},
|
|
962
|
+
iteratePosts(account, input, callOptions) {
|
|
963
|
+
return iterateItems((cursor) => searchFacade.posts(account, { ...input, ...iterationPageOptions(undefined, cursor) }, callOptions), callOptions);
|
|
964
|
+
},
|
|
965
|
+
};
|
|
673
966
|
const mediaFacade = {
|
|
674
967
|
async upload(input, account, callOptions) {
|
|
675
968
|
const correlationId = `social-${++correlationSequence}`;
|
|
@@ -683,8 +976,9 @@ export function createSocial(config) {
|
|
|
683
976
|
};
|
|
684
977
|
const analyticsFacade = {
|
|
685
978
|
async getAccountMetrics(ref, callOptions) {
|
|
979
|
+
validateAccountRef(ref, "analytics.account.read");
|
|
686
980
|
const correlationId = `social-${++correlationSequence}`;
|
|
687
|
-
await
|
|
981
|
+
await authorizeRefWithFallback("analytics.account.read", "analytics.read", ref, callOptions, correlationId);
|
|
688
982
|
const adapter = selected(ref, "analytics.getAccountMetrics");
|
|
689
983
|
requireCapability(adapter, "analytics.account.read", ref.platform, ref.backend);
|
|
690
984
|
if (!adapter.analytics?.getAccountMetrics)
|
|
@@ -707,10 +1001,47 @@ export function createSocial(config) {
|
|
|
707
1001
|
unsupported("analytics.read", ref.backend);
|
|
708
1002
|
return dispatch(ref.backend, callOptions?.signal, "analytics.read", () => adapter.analytics.getPostMetrics(ref, makeContext(ref.backend, correlationId, callOptions)));
|
|
709
1003
|
},
|
|
1004
|
+
async getReport(ref, query, callOptions) {
|
|
1005
|
+
validateAccountRef(ref, "analytics.report.read");
|
|
1006
|
+
if (query === undefined ||
|
|
1007
|
+
query === null ||
|
|
1008
|
+
!validDateOnly(query.from) ||
|
|
1009
|
+
!validDateOnly(query.to) ||
|
|
1010
|
+
query.from > query.to ||
|
|
1011
|
+
!Array.isArray(query.metrics) ||
|
|
1012
|
+
query.metrics.length === 0 ||
|
|
1013
|
+
query.metrics.some(
|
|
1014
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- runtime query boundary.
|
|
1015
|
+
(metric) => typeof metric !== "string" || !metric.trim()) ||
|
|
1016
|
+
(query.dimensions !== undefined &&
|
|
1017
|
+
(!Array.isArray(query.dimensions) ||
|
|
1018
|
+
query.dimensions.some(
|
|
1019
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- runtime query boundary.
|
|
1020
|
+
(dimension) => typeof dimension !== "string" || !dimension.trim()))))
|
|
1021
|
+
throw new SocialError({
|
|
1022
|
+
code: "invalid_input",
|
|
1023
|
+
operation: "analytics.report.read",
|
|
1024
|
+
message: "Reports require an ordered YYYY-MM-DD range, at least one metric, and nonempty dimensions.",
|
|
1025
|
+
});
|
|
1026
|
+
const correlationId = `social-${++correlationSequence}`;
|
|
1027
|
+
await authorizeRef("analytics.report.read", ref, callOptions, correlationId);
|
|
1028
|
+
const adapter = selected(ref, "analytics.getReport");
|
|
1029
|
+
requireCapability(adapter, "analytics.report.read", ref.platform, ref.backend);
|
|
1030
|
+
if (!adapter.analytics?.getReport)
|
|
1031
|
+
unsupported("analytics.report.read", ref.backend);
|
|
1032
|
+
return dispatch(ref.backend, callOptions?.signal, "analytics.report.read", () => adapter.analytics.getReport(ref, query, makeContext(ref.backend, correlationId, callOptions)));
|
|
1033
|
+
},
|
|
710
1034
|
};
|
|
711
1035
|
const commentsFacade = {
|
|
712
1036
|
async list(ref, callOptions) {
|
|
713
1037
|
const correlationId = `social-${++correlationSequence}`;
|
|
1038
|
+
if (callOptions?.limit !== undefined &&
|
|
1039
|
+
(!Number.isSafeInteger(callOptions.limit) || callOptions.limit < 1))
|
|
1040
|
+
throw new SocialError({
|
|
1041
|
+
code: "invalid_input",
|
|
1042
|
+
operation: "comments.read",
|
|
1043
|
+
message: "Comment page limit must be a positive integer",
|
|
1044
|
+
});
|
|
714
1045
|
const account = {
|
|
715
1046
|
kind: "connected-account",
|
|
716
1047
|
version: 1,
|
|
@@ -732,21 +1063,11 @@ export function createSocial(config) {
|
|
|
732
1063
|
ref.postId,
|
|
733
1064
|
callOptions?.limit ?? null,
|
|
734
1065
|
]);
|
|
735
|
-
const received = await dispatch(ref.backend, callOptions?.signal, "comments.read", () => adapter.comments.list(ref,
|
|
736
|
-
|
|
737
|
-
? {}
|
|
738
|
-
: { cursor: decodeCursor(cursorScope, callOptions.cursor) }),
|
|
739
|
-
...(callOptions?.limit === undefined ? {} : { limit: callOptions.limit }),
|
|
740
|
-
}, makeContext(ref.backend, correlationId, callOptions)));
|
|
741
|
-
return {
|
|
742
|
-
...received,
|
|
743
|
-
...(received.nextCursor === undefined
|
|
744
|
-
? {}
|
|
745
|
-
: { nextCursor: encodeCursor(cursorScope, received.nextCursor) }),
|
|
746
|
-
};
|
|
1066
|
+
const received = await dispatch(ref.backend, callOptions?.signal, "comments.read", () => adapter.comments.list(ref, decodePageOptions(cursorScope, callOptions), makeContext(ref.backend, correlationId, callOptions)));
|
|
1067
|
+
return encodePage(cursorScope, received);
|
|
747
1068
|
},
|
|
748
1069
|
iterate(ref, callOptions) {
|
|
749
|
-
return iterateItems((cursor) => commentsFacade.list(ref,
|
|
1070
|
+
return iterateItems((cursor) => commentsFacade.list(ref, iterationPageOptions(callOptions, cursor)), callOptions);
|
|
750
1071
|
},
|
|
751
1072
|
async reply(ref, content, callOptions) {
|
|
752
1073
|
const correlationId = `social-${++correlationSequence}`;
|
|
@@ -768,6 +1089,13 @@ export function createSocial(config) {
|
|
|
768
1089
|
const messagesFacade = {
|
|
769
1090
|
async listConversations(account, callOptions) {
|
|
770
1091
|
const correlationId = `social-${++correlationSequence}`;
|
|
1092
|
+
if (callOptions?.limit !== undefined &&
|
|
1093
|
+
(!Number.isSafeInteger(callOptions.limit) || callOptions.limit < 1))
|
|
1094
|
+
throw new SocialError({
|
|
1095
|
+
code: "invalid_input",
|
|
1096
|
+
operation: "messages.read",
|
|
1097
|
+
message: "Message page limit must be a positive integer",
|
|
1098
|
+
});
|
|
771
1099
|
await authorizeRef("messages.read", account, callOptions, correlationId);
|
|
772
1100
|
const adapter = selected(account, "messages.listConversations");
|
|
773
1101
|
requireCapability(adapter, "messages.read", account.platform, account.backend);
|
|
@@ -782,27 +1110,21 @@ export function createSocial(config) {
|
|
|
782
1110
|
"conversations",
|
|
783
1111
|
callOptions?.limit ?? null,
|
|
784
1112
|
]);
|
|
785
|
-
const received = await dispatch(account.backend, callOptions?.signal, "messages.read", () => adapter.messages.listConversations(account,
|
|
786
|
-
|
|
787
|
-
? {}
|
|
788
|
-
: { cursor: decodeCursor(cursorScope, callOptions.cursor) }),
|
|
789
|
-
...(callOptions?.limit === undefined ? {} : { limit: callOptions.limit }),
|
|
790
|
-
}, makeContext(account.backend, correlationId, callOptions)));
|
|
791
|
-
return {
|
|
792
|
-
...received,
|
|
793
|
-
...(received.nextCursor === undefined
|
|
794
|
-
? {}
|
|
795
|
-
: { nextCursor: encodeCursor(cursorScope, received.nextCursor) }),
|
|
796
|
-
};
|
|
1113
|
+
const received = await dispatch(account.backend, callOptions?.signal, "messages.read", () => adapter.messages.listConversations(account, decodePageOptions(cursorScope, callOptions), makeContext(account.backend, correlationId, callOptions)));
|
|
1114
|
+
return encodePage(cursorScope, received);
|
|
797
1115
|
},
|
|
798
1116
|
iterateConversations(account, callOptions) {
|
|
799
|
-
return iterateItems((cursor) => messagesFacade.listConversations(account,
|
|
800
|
-
...callOptions,
|
|
801
|
-
...(cursor === undefined ? {} : { cursor }),
|
|
802
|
-
}), callOptions);
|
|
1117
|
+
return iterateItems((cursor) => messagesFacade.listConversations(account, iterationPageOptions(callOptions, cursor)), callOptions);
|
|
803
1118
|
},
|
|
804
1119
|
async listMessages(ref, callOptions) {
|
|
805
1120
|
const correlationId = `social-${++correlationSequence}`;
|
|
1121
|
+
if (callOptions?.limit !== undefined &&
|
|
1122
|
+
(!Number.isSafeInteger(callOptions.limit) || callOptions.limit < 1))
|
|
1123
|
+
throw new SocialError({
|
|
1124
|
+
code: "invalid_input",
|
|
1125
|
+
operation: "messages.read",
|
|
1126
|
+
message: "Message page limit must be a positive integer",
|
|
1127
|
+
});
|
|
806
1128
|
const account = {
|
|
807
1129
|
kind: "connected-account",
|
|
808
1130
|
version: 1,
|
|
@@ -824,24 +1146,11 @@ export function createSocial(config) {
|
|
|
824
1146
|
ref.conversationId,
|
|
825
1147
|
callOptions?.limit ?? null,
|
|
826
1148
|
]);
|
|
827
|
-
const received = await dispatch(ref.backend, callOptions?.signal, "messages.read", () => adapter.messages.listMessages(ref,
|
|
828
|
-
|
|
829
|
-
? {}
|
|
830
|
-
: { cursor: decodeCursor(cursorScope, callOptions.cursor) }),
|
|
831
|
-
...(callOptions?.limit === undefined ? {} : { limit: callOptions.limit }),
|
|
832
|
-
}, makeContext(ref.backend, correlationId, callOptions)));
|
|
833
|
-
return {
|
|
834
|
-
...received,
|
|
835
|
-
...(received.nextCursor === undefined
|
|
836
|
-
? {}
|
|
837
|
-
: { nextCursor: encodeCursor(cursorScope, received.nextCursor) }),
|
|
838
|
-
};
|
|
1149
|
+
const received = await dispatch(ref.backend, callOptions?.signal, "messages.read", () => adapter.messages.listMessages(ref, decodePageOptions(cursorScope, callOptions), makeContext(ref.backend, correlationId, callOptions)));
|
|
1150
|
+
return encodePage(cursorScope, received);
|
|
839
1151
|
},
|
|
840
1152
|
iterateMessages(ref, callOptions) {
|
|
841
|
-
return iterateItems((cursor) => messagesFacade.listMessages(ref,
|
|
842
|
-
...callOptions,
|
|
843
|
-
...(cursor === undefined ? {} : { cursor }),
|
|
844
|
-
}), callOptions);
|
|
1153
|
+
return iterateItems((cursor) => messagesFacade.listMessages(ref, iterationPageOptions(callOptions, cursor)), callOptions);
|
|
845
1154
|
},
|
|
846
1155
|
async send(ref, content, callOptions) {
|
|
847
1156
|
const correlationId = `social-${++correlationSequence}`;
|
|
@@ -860,13 +1169,65 @@ export function createSocial(config) {
|
|
|
860
1169
|
return dispatch(ref.backend, callOptions?.signal, "messages.write", () => adapter.messages.send(ref, content, makeContext(ref.backend, correlationId, callOptions)));
|
|
861
1170
|
},
|
|
862
1171
|
};
|
|
1172
|
+
const notificationsFacade = {
|
|
1173
|
+
async list(account, callOptions) {
|
|
1174
|
+
validateAccountRef(account, "notifications.read");
|
|
1175
|
+
if (callOptions?.limit !== undefined &&
|
|
1176
|
+
(!Number.isSafeInteger(callOptions.limit) || callOptions.limit < 1))
|
|
1177
|
+
throw new SocialError({
|
|
1178
|
+
code: "invalid_input",
|
|
1179
|
+
operation: "notifications.read",
|
|
1180
|
+
message: "Notification page limit must be a positive integer",
|
|
1181
|
+
});
|
|
1182
|
+
const correlationId = `social-${++correlationSequence}`;
|
|
1183
|
+
await authorizeRef("notifications.read", account, callOptions, correlationId);
|
|
1184
|
+
const adapter = selected(account, "notifications.list");
|
|
1185
|
+
requireCapability(adapter, "notifications.read", account.platform, account.backend);
|
|
1186
|
+
if (adapter.notifications === undefined)
|
|
1187
|
+
unsupported("notifications.read", account.backend);
|
|
1188
|
+
const cursorScope = JSON.stringify([
|
|
1189
|
+
account.backend,
|
|
1190
|
+
"notifications.read",
|
|
1191
|
+
callOptions?.authorization?.tenantId ?? null,
|
|
1192
|
+
account.platform,
|
|
1193
|
+
account.accountId,
|
|
1194
|
+
callOptions?.limit ?? null,
|
|
1195
|
+
]);
|
|
1196
|
+
const received = await dispatch(account.backend, callOptions?.signal, "notifications.read", () => adapter.notifications.list(account, decodePageOptions(cursorScope, callOptions), makeContext(account.backend, correlationId, callOptions)));
|
|
1197
|
+
return encodePage(cursorScope, received);
|
|
1198
|
+
},
|
|
1199
|
+
iterate(account, callOptions) {
|
|
1200
|
+
return iterateItems((cursor) => notificationsFacade.list(account, iterationPageOptions(callOptions, cursor)), callOptions);
|
|
1201
|
+
},
|
|
1202
|
+
async markSeen(account, input = {}, callOptions) {
|
|
1203
|
+
validateAccountRef(account, "notifications.seen");
|
|
1204
|
+
if (input === null ||
|
|
1205
|
+
typeof input !== "object" ||
|
|
1206
|
+
(input.seenAt !== undefined && !validTimestamp(input.seenAt)))
|
|
1207
|
+
throw new SocialError({
|
|
1208
|
+
code: "invalid_input",
|
|
1209
|
+
operation: "notifications.seen",
|
|
1210
|
+
message: "seenAt must be a valid timestamp",
|
|
1211
|
+
});
|
|
1212
|
+
const correlationId = `social-${++correlationSequence}`;
|
|
1213
|
+
await authorizeRef("notifications.seen", account, callOptions, correlationId);
|
|
1214
|
+
const adapter = selected(account, "notifications.markSeen");
|
|
1215
|
+
requireCapability(adapter, "notifications.seen", account.platform, account.backend);
|
|
1216
|
+
if (adapter.notifications === undefined)
|
|
1217
|
+
unsupported("notifications.seen", account.backend);
|
|
1218
|
+
return dispatch(account.backend, callOptions?.signal, "notifications.seen", () => adapter.notifications.markSeen(account, input, makeContext(account.backend, correlationId, callOptions)));
|
|
1219
|
+
},
|
|
1220
|
+
};
|
|
863
1221
|
return {
|
|
864
1222
|
accounts: accountsFacade,
|
|
1223
|
+
graph: graphFacade,
|
|
865
1224
|
posts: postsFacade,
|
|
1225
|
+
search: searchFacade,
|
|
866
1226
|
media: mediaFacade,
|
|
867
1227
|
analytics: analyticsFacade,
|
|
868
1228
|
comments: commentsFacade,
|
|
869
1229
|
messages: messagesFacade,
|
|
1230
|
+
notifications: notificationsFacade,
|
|
870
1231
|
capabilities: () => Object.fromEntries(entries.map(([key, adapter]) => [key, adapter.capabilities])),
|
|
871
1232
|
adapter: (backend, acknowledgement) => {
|
|
872
1233
|
if (acknowledgement?.acknowledgeUnsafe !== true)
|
|
@@ -877,17 +1238,49 @@ export function createSocial(config) {
|
|
|
877
1238
|
});
|
|
878
1239
|
const selected = registry[String(backend)];
|
|
879
1240
|
if (selected === undefined)
|
|
880
|
-
throw new
|
|
1241
|
+
throw new SocialError({
|
|
1242
|
+
code: "invalid_input",
|
|
1243
|
+
operation: "adapter",
|
|
1244
|
+
message: `Unknown backend: ${String(backend)}`,
|
|
1245
|
+
});
|
|
881
1246
|
return selected;
|
|
882
1247
|
},
|
|
883
1248
|
native: (backend, acknowledgement) => {
|
|
884
1249
|
if (acknowledgement?.acknowledgeUnsafe !== true)
|
|
885
|
-
throw new
|
|
1250
|
+
throw new SocialError({
|
|
1251
|
+
code: "invalid_input",
|
|
1252
|
+
operation: "native",
|
|
1253
|
+
message: "Native access requires acknowledgeUnsafe: true",
|
|
1254
|
+
});
|
|
886
1255
|
const selected = registry[String(backend)];
|
|
887
1256
|
if (selected === undefined)
|
|
888
|
-
throw new
|
|
1257
|
+
throw new SocialError({
|
|
1258
|
+
code: "invalid_input",
|
|
1259
|
+
operation: "native",
|
|
1260
|
+
message: `Unknown backend: ${String(backend)}`,
|
|
1261
|
+
});
|
|
889
1262
|
return selected.native;
|
|
890
1263
|
},
|
|
891
1264
|
};
|
|
892
1265
|
}
|
|
1266
|
+
function decodePageOptions(scope, options) {
|
|
1267
|
+
const input = {};
|
|
1268
|
+
if (options?.cursor !== undefined)
|
|
1269
|
+
input.cursor = decodeCursor(scope, options.cursor);
|
|
1270
|
+
if (options?.limit !== undefined)
|
|
1271
|
+
input.limit = options.limit;
|
|
1272
|
+
return input;
|
|
1273
|
+
}
|
|
1274
|
+
function encodePage(scope, page) {
|
|
1275
|
+
const result = { ...page };
|
|
1276
|
+
if (page.nextCursor !== undefined)
|
|
1277
|
+
result.nextCursor = encodeCursor(scope, page.nextCursor);
|
|
1278
|
+
return result;
|
|
1279
|
+
}
|
|
1280
|
+
function iterationPageOptions(options, cursor) {
|
|
1281
|
+
const result = { ...options };
|
|
1282
|
+
if (cursor !== undefined)
|
|
1283
|
+
result.cursor = cursor;
|
|
1284
|
+
return result;
|
|
1285
|
+
}
|
|
893
1286
|
//# sourceMappingURL=client.js.map
|