naystack 1.8.18 → 1.9.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 +94 -34
- package/dist/auth/client.cjs.js +2 -2
- package/dist/auth/client.esm.js +2 -2
- package/dist/auth/email/client.cjs.js +2 -2
- package/dist/auth/email/client.esm.js +2 -2
- package/dist/auth/email/index.cjs.js +2 -2
- package/dist/auth/email/index.esm.js +2 -2
- package/dist/auth/email/next.cjs.js +2 -2
- package/dist/auth/email/next.esm.js +2 -2
- package/dist/auth/email/routes/delete.cjs.js +2 -2
- package/dist/auth/email/routes/delete.esm.js +2 -2
- package/dist/auth/email/routes/get.cjs.js +2 -2
- package/dist/auth/email/routes/get.esm.js +2 -2
- package/dist/auth/email/routes/post.cjs.js +2 -2
- package/dist/auth/email/routes/post.esm.js +2 -2
- package/dist/auth/email/routes/put.cjs.js +2 -2
- package/dist/auth/email/routes/put.esm.js +2 -2
- package/dist/auth/email/token.cjs.js +2 -2
- package/dist/auth/email/token.esm.js +2 -2
- package/dist/auth/email/utils.cjs.js +2 -2
- package/dist/auth/email/utils.esm.js +2 -2
- package/dist/auth/google/get.cjs.js +2 -2
- package/dist/auth/google/get.esm.js +2 -2
- package/dist/auth/google/index.cjs.js +2 -2
- package/dist/auth/google/index.esm.js +2 -2
- package/dist/auth/index.cjs.js +4 -297
- package/dist/auth/index.d.mts +0 -5
- package/dist/auth/index.d.ts +0 -5
- package/dist/auth/index.esm.js +3 -293
- package/dist/auth/token-store.cjs.js +2 -2
- package/dist/auth/token-store.esm.js +2 -2
- package/dist/env.cjs.js +3 -3
- package/dist/env.d.mts +3 -2
- package/dist/env.d.ts +3 -2
- package/dist/env.esm.js +3 -3
- package/dist/file/client.cjs.js +2 -2
- package/dist/file/client.esm.js +2 -2
- package/dist/file/index.cjs.js +2 -2
- package/dist/file/index.esm.js +2 -2
- package/dist/file/put.cjs.js +2 -2
- package/dist/file/put.esm.js +2 -2
- package/dist/file/setup.cjs.js +2 -2
- package/dist/file/setup.esm.js +2 -2
- package/dist/file/utils.cjs.js +2 -2
- package/dist/file/utils.esm.js +2 -2
- package/dist/graphql/client.cjs.js +2 -2
- package/dist/graphql/client.esm.js +2 -2
- package/dist/graphql/index.cjs.js +2 -2
- package/dist/graphql/index.esm.js +2 -2
- package/dist/graphql/init.cjs.js +2 -2
- package/dist/graphql/init.esm.js +2 -2
- package/dist/graphql/links.cjs.js +2 -2
- package/dist/graphql/links.esm.js +2 -2
- package/dist/graphql/next.cjs.js +2 -2
- package/dist/graphql/next.esm.js +2 -2
- package/dist/graphql/server.cjs.js +2 -2
- package/dist/graphql/server.esm.js +2 -2
- package/dist/graphql/utils.cjs.js +2 -2
- package/dist/graphql/utils.esm.js +2 -2
- package/dist/socials/connect.cjs.js +151 -0
- package/dist/socials/connect.d.mts +70 -0
- package/dist/socials/connect.d.ts +70 -0
- package/dist/socials/connect.esm.js +126 -0
- package/dist/socials/index.cjs.js +367 -35
- package/dist/socials/index.d.mts +8 -2
- package/dist/socials/index.d.ts +8 -2
- package/dist/socials/index.esm.js +357 -34
- package/dist/socials/instagram/adapter.cjs.js +275 -0
- package/dist/socials/instagram/adapter.d.mts +30 -0
- package/dist/socials/instagram/adapter.d.ts +30 -0
- package/dist/socials/instagram/adapter.esm.js +248 -0
- package/dist/{auth/instagram/utils.cjs.js → socials/instagram/auth.cjs.js} +43 -41
- package/dist/socials/instagram/auth.d.mts +52 -0
- package/dist/socials/instagram/auth.d.ts +52 -0
- package/dist/{auth/instagram/utils.esm.js → socials/instagram/auth.esm.js} +37 -35
- package/dist/socials/instagram/getters.d.mts +1 -0
- package/dist/socials/instagram/getters.d.ts +1 -0
- package/dist/socials/instagram/setters.cjs.js +56 -31
- package/dist/socials/instagram/setters.d.mts +1 -0
- package/dist/socials/instagram/setters.d.ts +1 -0
- package/dist/socials/instagram/setters.esm.js +56 -31
- package/dist/socials/instagram/types.d.mts +2 -1
- package/dist/socials/instagram/types.d.ts +2 -1
- package/dist/socials/meta/container.cjs.js +57 -30
- package/dist/socials/meta/container.d.mts +15 -28
- package/dist/socials/meta/container.d.ts +15 -28
- package/dist/socials/meta/container.esm.js +56 -31
- package/dist/socials/provider.cjs.js +18 -0
- package/dist/socials/provider.d.mts +52 -0
- package/dist/socials/provider.d.ts +52 -0
- package/dist/socials/provider.esm.js +0 -0
- package/dist/socials/threads/getters.d.mts +1 -0
- package/dist/socials/threads/getters.d.ts +1 -0
- package/dist/socials/threads/setters.cjs.js +56 -31
- package/dist/socials/threads/setters.d.mts +1 -0
- package/dist/socials/threads/setters.d.ts +1 -0
- package/dist/socials/threads/setters.esm.js +56 -31
- package/dist/socials/threads/types.d.mts +2 -1
- package/dist/socials/threads/types.d.ts +2 -1
- package/dist/socials/types.cjs.js +43 -0
- package/dist/socials/types.d.mts +99 -0
- package/dist/socials/types.d.ts +99 -0
- package/dist/socials/types.esm.js +17 -0
- package/dist/socials/utils/poll.cjs.js +59 -0
- package/dist/socials/utils/poll.d.mts +85 -0
- package/dist/socials/utils/poll.d.ts +85 -0
- package/dist/socials/utils/poll.esm.js +33 -0
- package/dist/utils/client/index.cjs.js +2 -2
- package/dist/utils/client/index.esm.js +2 -2
- package/dist/utils/client/seo.cjs.js +2 -2
- package/dist/utils/client/seo.esm.js +2 -2
- package/package.json +1 -1
- package/dist/auth/instagram/index.cjs.js +0 -379
- package/dist/auth/instagram/index.d.mts +0 -58
- package/dist/auth/instagram/index.d.ts +0 -58
- package/dist/auth/instagram/index.esm.js +0 -350
- package/dist/auth/instagram/route.cjs.js +0 -359
- package/dist/auth/instagram/route.d.mts +0 -15
- package/dist/auth/instagram/route.d.ts +0 -15
- package/dist/auth/instagram/route.esm.js +0 -334
- package/dist/auth/instagram/utils.d.mts +0 -57
- package/dist/auth/instagram/utils.d.ts +0 -57
|
@@ -1,22 +1,39 @@
|
|
|
1
|
-
// src/socials/
|
|
1
|
+
// src/socials/utils/poll.ts
|
|
2
2
|
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
3
|
-
async function
|
|
3
|
+
async function pollUntilReady(read, isPending, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
|
|
4
4
|
const deadline = Date.now() + timeoutMS;
|
|
5
|
-
let state = await
|
|
5
|
+
let state = await read();
|
|
6
6
|
if (!state) {
|
|
7
7
|
await sleep(2e3);
|
|
8
8
|
return null;
|
|
9
9
|
}
|
|
10
|
-
while (state
|
|
10
|
+
while (state && isPending(state) && Date.now() < deadline) {
|
|
11
11
|
await sleep(intervalMS);
|
|
12
|
-
state = await
|
|
12
|
+
state = await read();
|
|
13
13
|
}
|
|
14
14
|
return state;
|
|
15
15
|
}
|
|
16
|
+
async function withRetry(run, options = {}) {
|
|
17
|
+
const attempts = Math.max(1, options.attempts ?? 3);
|
|
18
|
+
const backoffMS = options.backoffMS ?? 2e3;
|
|
19
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
20
|
+
const result = await run(attempt);
|
|
21
|
+
if (result) return result;
|
|
22
|
+
if (attempt < attempts) {
|
|
23
|
+
const delayMS = backoffMS * 2 ** (attempt - 1);
|
|
24
|
+
options.onRetry?.(attempt, attempts, delayMS);
|
|
25
|
+
await sleep(delayMS);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/socials/meta/container.ts
|
|
16
32
|
function createPublisher(platform) {
|
|
17
33
|
const isReady = async (token, id, label, wait) => {
|
|
18
|
-
const state = await
|
|
34
|
+
const state = await pollUntilReady(
|
|
19
35
|
() => platform.getStatus(token, id),
|
|
36
|
+
(current) => current.status === "IN_PROGRESS",
|
|
20
37
|
wait
|
|
21
38
|
);
|
|
22
39
|
if (state && state.status !== "FINISHED" && state.status !== "PUBLISHED") {
|
|
@@ -27,22 +44,21 @@ function createPublisher(platform) {
|
|
|
27
44
|
}
|
|
28
45
|
return true;
|
|
29
46
|
};
|
|
30
|
-
const createReady =
|
|
31
|
-
|
|
32
|
-
const backoffMS = retry?.backoffMS ?? 2e3;
|
|
33
|
-
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
47
|
+
const createReady = (token, params, label, wait, retry) => withRetry(
|
|
48
|
+
async () => {
|
|
34
49
|
const id = await platform.createContainer(token, params);
|
|
35
|
-
if (id
|
|
36
|
-
|
|
37
|
-
|
|
50
|
+
if (!id || !await isReady(token, id, label, wait)) return null;
|
|
51
|
+
return id;
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
...retry,
|
|
55
|
+
onRetry: (attempt, attempts, delayMS) => {
|
|
38
56
|
console.warn(
|
|
39
|
-
`[naystack] ${platform.name} ${label} attempt ${attempt}/${attempts} failed \u2014 retrying in ${
|
|
57
|
+
`[naystack] ${platform.name} ${label} attempt ${attempt}/${attempts} failed \u2014 retrying in ${delayMS}ms`
|
|
40
58
|
);
|
|
41
|
-
await sleep(delay);
|
|
42
59
|
}
|
|
43
60
|
}
|
|
44
|
-
|
|
45
|
-
};
|
|
61
|
+
);
|
|
46
62
|
return {
|
|
47
63
|
/** Creates a container, waits for it to finish processing, then publishes it. */
|
|
48
64
|
publish: async (token, params, wait, retry) => {
|
|
@@ -56,21 +72,30 @@ function createPublisher(platform) {
|
|
|
56
72
|
if (!containerID) return null;
|
|
57
73
|
return platform.publish(token, containerID);
|
|
58
74
|
},
|
|
59
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Creates and awaits every carousel child **in parallel**, resolving to
|
|
77
|
+
* their ids in input order — or `null` if any failed.
|
|
78
|
+
*
|
|
79
|
+
* Parallel because each child is an independent create-then-poll and Meta
|
|
80
|
+
* spends ~5s accepting a single container, so ten sequential children cost
|
|
81
|
+
* ~50s of mostly waiting — past what a serverless caller can fit in its
|
|
82
|
+
* timeout. `Promise.all` preserves input order, which the carousel's slide
|
|
83
|
+
* order depends on.
|
|
84
|
+
*/
|
|
60
85
|
createChildren: async (token, items, wait, retry) => {
|
|
61
|
-
const children =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return children;
|
|
86
|
+
const children = await Promise.all(
|
|
87
|
+
items.map(
|
|
88
|
+
(item) => createReady(
|
|
89
|
+
token,
|
|
90
|
+
{ ...item, is_carousel_item: true },
|
|
91
|
+
"carousel item",
|
|
92
|
+
wait,
|
|
93
|
+
retry
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
);
|
|
97
|
+
const created = children.filter((childID) => childID !== null);
|
|
98
|
+
return created.length === children.length ? created : null;
|
|
74
99
|
}
|
|
75
100
|
};
|
|
76
101
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { WaitForContainerOptions
|
|
1
|
+
import { WaitForContainerOptions } from '../meta/container.mjs';
|
|
2
2
|
import { MetaMediaType } from '../meta/types.mjs';
|
|
3
|
+
import { RetryOptions } from '../utils/poll.mjs';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Threads post (single post or reply).
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { WaitForContainerOptions
|
|
1
|
+
import { WaitForContainerOptions } from '../meta/container.js';
|
|
2
2
|
import { MetaMediaType } from '../meta/types.js';
|
|
3
|
+
import { RetryOptions } from '../utils/poll.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Threads post (single post or reply).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/socials/types.ts
|
|
21
|
+
var types_exports = {};
|
|
22
|
+
__export(types_exports, {
|
|
23
|
+
SocialMediaKind: () => SocialMediaKind,
|
|
24
|
+
SocialPlatform: () => SocialPlatform
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(types_exports);
|
|
27
|
+
var SocialPlatform = /* @__PURE__ */ ((SocialPlatform2) => {
|
|
28
|
+
SocialPlatform2["Instagram"] = "instagram";
|
|
29
|
+
SocialPlatform2["YouTube"] = "youtube";
|
|
30
|
+
SocialPlatform2["TikTok"] = "tiktok";
|
|
31
|
+
return SocialPlatform2;
|
|
32
|
+
})(SocialPlatform || {});
|
|
33
|
+
var SocialMediaKind = /* @__PURE__ */ ((SocialMediaKind2) => {
|
|
34
|
+
SocialMediaKind2["Image"] = "image";
|
|
35
|
+
SocialMediaKind2["Video"] = "video";
|
|
36
|
+
SocialMediaKind2["Carousel"] = "carousel";
|
|
37
|
+
return SocialMediaKind2;
|
|
38
|
+
})(SocialMediaKind || {});
|
|
39
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
40
|
+
0 && (module.exports = {
|
|
41
|
+
SocialMediaKind,
|
|
42
|
+
SocialPlatform
|
|
43
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform-neutral shapes every social provider maps its API onto: one row for a
|
|
3
|
+
* connected account, one for a piece of its content. Raw per-platform payloads
|
|
4
|
+
* (`InstagramUser`, `ThreadsPost`, …) stay available for anything these flatten.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* A platform a user can connect an account on. Values double as URL path
|
|
10
|
+
* segments, so a route mounted at `/api/social/[platform]` matches them verbatim.
|
|
11
|
+
*
|
|
12
|
+
* @category Socials
|
|
13
|
+
*/
|
|
14
|
+
declare enum SocialPlatform {
|
|
15
|
+
Instagram = "instagram",
|
|
16
|
+
YouTube = "youtube",
|
|
17
|
+
TikTok = "tiktok"
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* What a piece of content is. Each adapter maps its platform's own vocabulary
|
|
21
|
+
* (Instagram's uppercase `IMAGE` / `VIDEO` / `CAROUSEL_ALBUM`, a reel being a
|
|
22
|
+
* `VIDEO`) onto these three.
|
|
23
|
+
*
|
|
24
|
+
* @category Socials
|
|
25
|
+
*/
|
|
26
|
+
declare enum SocialMediaKind {
|
|
27
|
+
Image = "image",
|
|
28
|
+
Video = "video",
|
|
29
|
+
Carousel = "carousel"
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A connected account's profile and audience.
|
|
33
|
+
*
|
|
34
|
+
* @property platformUserId - The platform's own id. Stable across renames — key on it, not `username`. `null` when the platform doesn't return one.
|
|
35
|
+
* @property username - Public handle. Mutable, and a freed handle can be taken by someone else.
|
|
36
|
+
* @property avatar - Usually a signed CDN URL that expires; copy it to your own storage.
|
|
37
|
+
* @property followers - Subscribers on YouTube, followers elsewhere.
|
|
38
|
+
* @property metadata - Platform extras, display-only: keys differ per platform, so never filter or sort on them.
|
|
39
|
+
*
|
|
40
|
+
* @category Socials
|
|
41
|
+
*/
|
|
42
|
+
type SocialProfile = {
|
|
43
|
+
platform: SocialPlatform;
|
|
44
|
+
platformUserId: string | null;
|
|
45
|
+
username: string;
|
|
46
|
+
displayName: string | null;
|
|
47
|
+
avatar: string | null;
|
|
48
|
+
followers: number;
|
|
49
|
+
contentCount: number;
|
|
50
|
+
metadata: Record<string, unknown>;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* One piece of content from a connected account.
|
|
54
|
+
*
|
|
55
|
+
* Every metric is `number | null`, and `null` always means *the platform did not
|
|
56
|
+
* give us this* — hidden, unrequested, or nonexistent on that platform. Adapters
|
|
57
|
+
* never substitute `0`: "nobody liked it" and "likes are hidden" are different
|
|
58
|
+
* facts, and only the caller knows which its averages should skip.
|
|
59
|
+
*
|
|
60
|
+
* @property platformMediaId - Unique within a platform, not across them.
|
|
61
|
+
* @property thumbnail - Usually a signed CDN URL that expires; copy it to your own storage.
|
|
62
|
+
* @property publishedAt - ISO 8601.
|
|
63
|
+
* @property metadata - Platform extras, display-only, same contract as {@link SocialProfile}.
|
|
64
|
+
*
|
|
65
|
+
* @category Socials
|
|
66
|
+
*/
|
|
67
|
+
type SocialPost = {
|
|
68
|
+
platform: SocialPlatform;
|
|
69
|
+
platformMediaId: string;
|
|
70
|
+
permalink: string;
|
|
71
|
+
thumbnail: string | null;
|
|
72
|
+
mediaURL: string | null;
|
|
73
|
+
caption: string | null;
|
|
74
|
+
kind: SocialMediaKind;
|
|
75
|
+
publishedAt: string;
|
|
76
|
+
likes: number | null;
|
|
77
|
+
comments: number | null;
|
|
78
|
+
views: number | null;
|
|
79
|
+
shares: number | null;
|
|
80
|
+
metadata: Record<string, unknown>;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Credentials for a connected account. Instagram issues a long-lived
|
|
84
|
+
* `accessToken` refreshed in place; Google and TikTok pair a short one with a
|
|
85
|
+
* `refreshToken`. One shape covers both.
|
|
86
|
+
*
|
|
87
|
+
* @property expiresAt - Absent when the platform doesn't say.
|
|
88
|
+
* @property scopes - Permissions actually granted. A token can be valid yet lack the scope for a given call.
|
|
89
|
+
*
|
|
90
|
+
* @category Socials
|
|
91
|
+
*/
|
|
92
|
+
type SocialTokens = {
|
|
93
|
+
accessToken: string;
|
|
94
|
+
refreshToken?: string;
|
|
95
|
+
expiresAt?: Date;
|
|
96
|
+
scopes?: string[];
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export { SocialMediaKind, SocialPlatform, type SocialPost, type SocialProfile, type SocialTokens };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform-neutral shapes every social provider maps its API onto: one row for a
|
|
3
|
+
* connected account, one for a piece of its content. Raw per-platform payloads
|
|
4
|
+
* (`InstagramUser`, `ThreadsPost`, …) stay available for anything these flatten.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* A platform a user can connect an account on. Values double as URL path
|
|
10
|
+
* segments, so a route mounted at `/api/social/[platform]` matches them verbatim.
|
|
11
|
+
*
|
|
12
|
+
* @category Socials
|
|
13
|
+
*/
|
|
14
|
+
declare enum SocialPlatform {
|
|
15
|
+
Instagram = "instagram",
|
|
16
|
+
YouTube = "youtube",
|
|
17
|
+
TikTok = "tiktok"
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* What a piece of content is. Each adapter maps its platform's own vocabulary
|
|
21
|
+
* (Instagram's uppercase `IMAGE` / `VIDEO` / `CAROUSEL_ALBUM`, a reel being a
|
|
22
|
+
* `VIDEO`) onto these three.
|
|
23
|
+
*
|
|
24
|
+
* @category Socials
|
|
25
|
+
*/
|
|
26
|
+
declare enum SocialMediaKind {
|
|
27
|
+
Image = "image",
|
|
28
|
+
Video = "video",
|
|
29
|
+
Carousel = "carousel"
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A connected account's profile and audience.
|
|
33
|
+
*
|
|
34
|
+
* @property platformUserId - The platform's own id. Stable across renames — key on it, not `username`. `null` when the platform doesn't return one.
|
|
35
|
+
* @property username - Public handle. Mutable, and a freed handle can be taken by someone else.
|
|
36
|
+
* @property avatar - Usually a signed CDN URL that expires; copy it to your own storage.
|
|
37
|
+
* @property followers - Subscribers on YouTube, followers elsewhere.
|
|
38
|
+
* @property metadata - Platform extras, display-only: keys differ per platform, so never filter or sort on them.
|
|
39
|
+
*
|
|
40
|
+
* @category Socials
|
|
41
|
+
*/
|
|
42
|
+
type SocialProfile = {
|
|
43
|
+
platform: SocialPlatform;
|
|
44
|
+
platformUserId: string | null;
|
|
45
|
+
username: string;
|
|
46
|
+
displayName: string | null;
|
|
47
|
+
avatar: string | null;
|
|
48
|
+
followers: number;
|
|
49
|
+
contentCount: number;
|
|
50
|
+
metadata: Record<string, unknown>;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* One piece of content from a connected account.
|
|
54
|
+
*
|
|
55
|
+
* Every metric is `number | null`, and `null` always means *the platform did not
|
|
56
|
+
* give us this* — hidden, unrequested, or nonexistent on that platform. Adapters
|
|
57
|
+
* never substitute `0`: "nobody liked it" and "likes are hidden" are different
|
|
58
|
+
* facts, and only the caller knows which its averages should skip.
|
|
59
|
+
*
|
|
60
|
+
* @property platformMediaId - Unique within a platform, not across them.
|
|
61
|
+
* @property thumbnail - Usually a signed CDN URL that expires; copy it to your own storage.
|
|
62
|
+
* @property publishedAt - ISO 8601.
|
|
63
|
+
* @property metadata - Platform extras, display-only, same contract as {@link SocialProfile}.
|
|
64
|
+
*
|
|
65
|
+
* @category Socials
|
|
66
|
+
*/
|
|
67
|
+
type SocialPost = {
|
|
68
|
+
platform: SocialPlatform;
|
|
69
|
+
platformMediaId: string;
|
|
70
|
+
permalink: string;
|
|
71
|
+
thumbnail: string | null;
|
|
72
|
+
mediaURL: string | null;
|
|
73
|
+
caption: string | null;
|
|
74
|
+
kind: SocialMediaKind;
|
|
75
|
+
publishedAt: string;
|
|
76
|
+
likes: number | null;
|
|
77
|
+
comments: number | null;
|
|
78
|
+
views: number | null;
|
|
79
|
+
shares: number | null;
|
|
80
|
+
metadata: Record<string, unknown>;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Credentials for a connected account. Instagram issues a long-lived
|
|
84
|
+
* `accessToken` refreshed in place; Google and TikTok pair a short one with a
|
|
85
|
+
* `refreshToken`. One shape covers both.
|
|
86
|
+
*
|
|
87
|
+
* @property expiresAt - Absent when the platform doesn't say.
|
|
88
|
+
* @property scopes - Permissions actually granted. A token can be valid yet lack the scope for a given call.
|
|
89
|
+
*
|
|
90
|
+
* @category Socials
|
|
91
|
+
*/
|
|
92
|
+
type SocialTokens = {
|
|
93
|
+
accessToken: string;
|
|
94
|
+
refreshToken?: string;
|
|
95
|
+
expiresAt?: Date;
|
|
96
|
+
scopes?: string[];
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export { SocialMediaKind, SocialPlatform, type SocialPost, type SocialProfile, type SocialTokens };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/socials/types.ts
|
|
2
|
+
var SocialPlatform = /* @__PURE__ */ ((SocialPlatform2) => {
|
|
3
|
+
SocialPlatform2["Instagram"] = "instagram";
|
|
4
|
+
SocialPlatform2["YouTube"] = "youtube";
|
|
5
|
+
SocialPlatform2["TikTok"] = "tiktok";
|
|
6
|
+
return SocialPlatform2;
|
|
7
|
+
})(SocialPlatform || {});
|
|
8
|
+
var SocialMediaKind = /* @__PURE__ */ ((SocialMediaKind2) => {
|
|
9
|
+
SocialMediaKind2["Image"] = "image";
|
|
10
|
+
SocialMediaKind2["Video"] = "video";
|
|
11
|
+
SocialMediaKind2["Carousel"] = "carousel";
|
|
12
|
+
return SocialMediaKind2;
|
|
13
|
+
})(SocialMediaKind || {});
|
|
14
|
+
export {
|
|
15
|
+
SocialMediaKind,
|
|
16
|
+
SocialPlatform
|
|
17
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/socials/utils/poll.ts
|
|
21
|
+
var poll_exports = {};
|
|
22
|
+
__export(poll_exports, {
|
|
23
|
+
pollUntilReady: () => pollUntilReady,
|
|
24
|
+
withRetry: () => withRetry
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(poll_exports);
|
|
27
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
28
|
+
async function pollUntilReady(read, isPending, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
|
|
29
|
+
const deadline = Date.now() + timeoutMS;
|
|
30
|
+
let state = await read();
|
|
31
|
+
if (!state) {
|
|
32
|
+
await sleep(2e3);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
while (state && isPending(state) && Date.now() < deadline) {
|
|
36
|
+
await sleep(intervalMS);
|
|
37
|
+
state = await read();
|
|
38
|
+
}
|
|
39
|
+
return state;
|
|
40
|
+
}
|
|
41
|
+
async function withRetry(run, options = {}) {
|
|
42
|
+
const attempts = Math.max(1, options.attempts ?? 3);
|
|
43
|
+
const backoffMS = options.backoffMS ?? 2e3;
|
|
44
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
45
|
+
const result = await run(attempt);
|
|
46
|
+
if (result) return result;
|
|
47
|
+
if (attempt < attempts) {
|
|
48
|
+
const delayMS = backoffMS * 2 ** (attempt - 1);
|
|
49
|
+
options.onRetry?.(attempt, attempts, delayMS);
|
|
50
|
+
await sleep(delayMS);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
56
|
+
0 && (module.exports = {
|
|
57
|
+
pollUntilReady,
|
|
58
|
+
withRetry
|
|
59
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Waiting and retrying, with no platform in them. Meta's media containers were
|
|
3
|
+
* the first caller; any API that hands back a job id and a status endpoint wants
|
|
4
|
+
* the same two helpers.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Polling settings for {@link pollUntilReady}.
|
|
10
|
+
*
|
|
11
|
+
* @property intervalMS - Delay between reads. Default: `5000`.
|
|
12
|
+
* @property timeoutMS - Stop polling after this long. Default: `300000`.
|
|
13
|
+
*
|
|
14
|
+
* @category Socials
|
|
15
|
+
*/
|
|
16
|
+
type PollOptions = {
|
|
17
|
+
intervalMS?: number;
|
|
18
|
+
timeoutMS?: number;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Retry settings for {@link withRetry}.
|
|
22
|
+
*
|
|
23
|
+
* @property attempts - Total tries, including the first. Default: `3`.
|
|
24
|
+
* @property backoffMS - Delay before the second try, doubled each time after. Default: `2000`.
|
|
25
|
+
*
|
|
26
|
+
* @category Socials
|
|
27
|
+
*/
|
|
28
|
+
type RetryOptions = {
|
|
29
|
+
attempts?: number;
|
|
30
|
+
backoffMS?: number;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Reads a job's state until it stops being pending.
|
|
34
|
+
*
|
|
35
|
+
* Resolves `null` when the first read returns nothing. That is "unknown", not
|
|
36
|
+
* "failed": some jobs are complete the moment they exist and never publish a
|
|
37
|
+
* readable status, so treating `null` as an error would reject work that already
|
|
38
|
+
* succeeded. A short pause before returning gives such a job a beat to settle.
|
|
39
|
+
*
|
|
40
|
+
* @param read - `null` means no state was readable.
|
|
41
|
+
* @param isPending - `true` while the state means "still working".
|
|
42
|
+
* @returns The last state read, or `null` if the state was never readable.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* import { pollUntilReady } from "naystack/socials";
|
|
47
|
+
*
|
|
48
|
+
* const state = await pollUntilReady(
|
|
49
|
+
* () => getInstagramContainerStatus(token, containerID),
|
|
50
|
+
* (s) => s.status === "IN_PROGRESS",
|
|
51
|
+
* );
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @category Socials
|
|
55
|
+
*/
|
|
56
|
+
declare function pollUntilReady<T>(read: () => Promise<T | null>, isPending: (state: T) => boolean, { intervalMS, timeoutMS }?: PollOptions): Promise<T | null>;
|
|
57
|
+
/**
|
|
58
|
+
* Runs an operation until it produces a result, backing off between tries.
|
|
59
|
+
*
|
|
60
|
+
* `run` signals failure by resolving `null` rather than throwing — the socials
|
|
61
|
+
* helpers log the API's message and resolve `null`, so a loop that only caught
|
|
62
|
+
* exceptions would never retry.
|
|
63
|
+
*
|
|
64
|
+
* @param run - Receives the 1-based attempt number; resolve `null` to retry.
|
|
65
|
+
* @param options - {@link RetryOptions}, plus `onRetry` for logging before each delay.
|
|
66
|
+
* @returns The first non-`null` result, or `null` once every attempt is spent.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* import { withRetry } from "naystack/socials";
|
|
71
|
+
*
|
|
72
|
+
* const id = await withRetry(() => createContainer(token, params), {
|
|
73
|
+
* attempts: 3,
|
|
74
|
+
* onRetry: (attempt, attempts, delayMS) =>
|
|
75
|
+
* console.warn(`attempt ${attempt}/${attempts} failed — retrying in ${delayMS}ms`),
|
|
76
|
+
* });
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @category Socials
|
|
80
|
+
*/
|
|
81
|
+
declare function withRetry<T>(run: (attempt: number) => Promise<T | null>, options?: RetryOptions & {
|
|
82
|
+
onRetry?: (attempt: number, attempts: number, delayMS: number) => void;
|
|
83
|
+
}): Promise<T | null>;
|
|
84
|
+
|
|
85
|
+
export { type PollOptions, type RetryOptions, pollUntilReady, withRetry };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Waiting and retrying, with no platform in them. Meta's media containers were
|
|
3
|
+
* the first caller; any API that hands back a job id and a status endpoint wants
|
|
4
|
+
* the same two helpers.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Polling settings for {@link pollUntilReady}.
|
|
10
|
+
*
|
|
11
|
+
* @property intervalMS - Delay between reads. Default: `5000`.
|
|
12
|
+
* @property timeoutMS - Stop polling after this long. Default: `300000`.
|
|
13
|
+
*
|
|
14
|
+
* @category Socials
|
|
15
|
+
*/
|
|
16
|
+
type PollOptions = {
|
|
17
|
+
intervalMS?: number;
|
|
18
|
+
timeoutMS?: number;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Retry settings for {@link withRetry}.
|
|
22
|
+
*
|
|
23
|
+
* @property attempts - Total tries, including the first. Default: `3`.
|
|
24
|
+
* @property backoffMS - Delay before the second try, doubled each time after. Default: `2000`.
|
|
25
|
+
*
|
|
26
|
+
* @category Socials
|
|
27
|
+
*/
|
|
28
|
+
type RetryOptions = {
|
|
29
|
+
attempts?: number;
|
|
30
|
+
backoffMS?: number;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Reads a job's state until it stops being pending.
|
|
34
|
+
*
|
|
35
|
+
* Resolves `null` when the first read returns nothing. That is "unknown", not
|
|
36
|
+
* "failed": some jobs are complete the moment they exist and never publish a
|
|
37
|
+
* readable status, so treating `null` as an error would reject work that already
|
|
38
|
+
* succeeded. A short pause before returning gives such a job a beat to settle.
|
|
39
|
+
*
|
|
40
|
+
* @param read - `null` means no state was readable.
|
|
41
|
+
* @param isPending - `true` while the state means "still working".
|
|
42
|
+
* @returns The last state read, or `null` if the state was never readable.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* import { pollUntilReady } from "naystack/socials";
|
|
47
|
+
*
|
|
48
|
+
* const state = await pollUntilReady(
|
|
49
|
+
* () => getInstagramContainerStatus(token, containerID),
|
|
50
|
+
* (s) => s.status === "IN_PROGRESS",
|
|
51
|
+
* );
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @category Socials
|
|
55
|
+
*/
|
|
56
|
+
declare function pollUntilReady<T>(read: () => Promise<T | null>, isPending: (state: T) => boolean, { intervalMS, timeoutMS }?: PollOptions): Promise<T | null>;
|
|
57
|
+
/**
|
|
58
|
+
* Runs an operation until it produces a result, backing off between tries.
|
|
59
|
+
*
|
|
60
|
+
* `run` signals failure by resolving `null` rather than throwing — the socials
|
|
61
|
+
* helpers log the API's message and resolve `null`, so a loop that only caught
|
|
62
|
+
* exceptions would never retry.
|
|
63
|
+
*
|
|
64
|
+
* @param run - Receives the 1-based attempt number; resolve `null` to retry.
|
|
65
|
+
* @param options - {@link RetryOptions}, plus `onRetry` for logging before each delay.
|
|
66
|
+
* @returns The first non-`null` result, or `null` once every attempt is spent.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* import { withRetry } from "naystack/socials";
|
|
71
|
+
*
|
|
72
|
+
* const id = await withRetry(() => createContainer(token, params), {
|
|
73
|
+
* attempts: 3,
|
|
74
|
+
* onRetry: (attempt, attempts, delayMS) =>
|
|
75
|
+
* console.warn(`attempt ${attempt}/${attempts} failed — retrying in ${delayMS}ms`),
|
|
76
|
+
* });
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @category Socials
|
|
80
|
+
*/
|
|
81
|
+
declare function withRetry<T>(run: (attempt: number) => Promise<T | null>, options?: RetryOptions & {
|
|
82
|
+
onRetry?: (attempt: number, attempts: number, delayMS: number) => void;
|
|
83
|
+
}): Promise<T | null>;
|
|
84
|
+
|
|
85
|
+
export { type PollOptions, type RetryOptions, pollUntilReady, withRetry };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// src/socials/utils/poll.ts
|
|
2
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
3
|
+
async function pollUntilReady(read, isPending, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
|
|
4
|
+
const deadline = Date.now() + timeoutMS;
|
|
5
|
+
let state = await read();
|
|
6
|
+
if (!state) {
|
|
7
|
+
await sleep(2e3);
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
while (state && isPending(state) && Date.now() < deadline) {
|
|
11
|
+
await sleep(intervalMS);
|
|
12
|
+
state = await read();
|
|
13
|
+
}
|
|
14
|
+
return state;
|
|
15
|
+
}
|
|
16
|
+
async function withRetry(run, options = {}) {
|
|
17
|
+
const attempts = Math.max(1, options.attempts ?? 3);
|
|
18
|
+
const backoffMS = options.backoffMS ?? 2e3;
|
|
19
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
20
|
+
const result = await run(attempt);
|
|
21
|
+
if (result) return result;
|
|
22
|
+
if (attempt < attempts) {
|
|
23
|
+
const delayMS = backoffMS * 2 ** (attempt - 1);
|
|
24
|
+
options.onRetry?.(attempt, attempts, delayMS);
|
|
25
|
+
await sleep(delayMS);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
pollUntilReady,
|
|
32
|
+
withRetry
|
|
33
|
+
};
|
|
@@ -81,8 +81,8 @@ var getEnvValue = (key) => {
|
|
|
81
81
|
return process.env.NEXT_PUBLIC_EMAIL_AUTH_ENDPOINT || EXTRA_ENV.EMAIL_AUTH_ENDPOINT;
|
|
82
82
|
case "NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT" /* NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT */:
|
|
83
83
|
return process.env.NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT || EXTRA_ENV.GOOGLE_AUTH_ENDPOINT;
|
|
84
|
-
case "
|
|
85
|
-
return process.env.
|
|
84
|
+
case "NEXT_PUBLIC_SOCIAL_AUTH_ENDPOINT" /* NEXT_PUBLIC_SOCIAL_AUTH_ENDPOINT */:
|
|
85
|
+
return process.env.NEXT_PUBLIC_SOCIAL_AUTH_ENDPOINT || EXTRA_ENV.SOCIAL_AUTH_ENDPOINT;
|
|
86
86
|
case "NEXT_PUBLIC_FILE_ENDPOINT" /* NEXT_PUBLIC_FILE_ENDPOINT */:
|
|
87
87
|
return process.env.NEXT_PUBLIC_FILE_ENDPOINT || EXTRA_ENV.FILE_ENDPOINT;
|
|
88
88
|
case "NEXT_PUBLIC_BASE_URL" /* NEXT_PUBLIC_BASE_URL */:
|