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,3 +1,183 @@
|
|
|
1
|
+
// src/socials/connect.ts
|
|
2
|
+
import { NextResponse as NextResponse2 } from "next/server";
|
|
3
|
+
|
|
4
|
+
// src/auth/email/token.ts
|
|
5
|
+
import { compare } from "bcryptjs";
|
|
6
|
+
import { JsonWebTokenError, sign, verify } from "jsonwebtoken";
|
|
7
|
+
import { cookies } from "next/headers";
|
|
8
|
+
import { redirect } from "next/navigation";
|
|
9
|
+
import { NextResponse } from "next/server";
|
|
10
|
+
|
|
11
|
+
// src/env.ts
|
|
12
|
+
var EXTRA_ENV = globalThis.__NAYSTACK_ENV__ || (globalThis.__NAYSTACK_ENV__ = {});
|
|
13
|
+
var getEnvValue = (key) => {
|
|
14
|
+
switch (key) {
|
|
15
|
+
case "NEXT_PUBLIC_GRAPHQL_ENDPOINT" /* NEXT_PUBLIC_GRAPHQL_ENDPOINT */:
|
|
16
|
+
return process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT || EXTRA_ENV.GRAPHQL_ENDPOINT;
|
|
17
|
+
case "NEXT_PUBLIC_EMAIL_AUTH_ENDPOINT" /* NEXT_PUBLIC_EMAIL_AUTH_ENDPOINT */:
|
|
18
|
+
return process.env.NEXT_PUBLIC_EMAIL_AUTH_ENDPOINT || EXTRA_ENV.EMAIL_AUTH_ENDPOINT;
|
|
19
|
+
case "NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT" /* NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT */:
|
|
20
|
+
return process.env.NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT || EXTRA_ENV.GOOGLE_AUTH_ENDPOINT;
|
|
21
|
+
case "NEXT_PUBLIC_SOCIAL_AUTH_ENDPOINT" /* NEXT_PUBLIC_SOCIAL_AUTH_ENDPOINT */:
|
|
22
|
+
return process.env.NEXT_PUBLIC_SOCIAL_AUTH_ENDPOINT || EXTRA_ENV.SOCIAL_AUTH_ENDPOINT;
|
|
23
|
+
case "NEXT_PUBLIC_FILE_ENDPOINT" /* NEXT_PUBLIC_FILE_ENDPOINT */:
|
|
24
|
+
return process.env.NEXT_PUBLIC_FILE_ENDPOINT || EXTRA_ENV.FILE_ENDPOINT;
|
|
25
|
+
case "NEXT_PUBLIC_BASE_URL" /* NEXT_PUBLIC_BASE_URL */:
|
|
26
|
+
return process.env.NEXT_PUBLIC_BASE_URL || EXTRA_ENV.BASE_URL;
|
|
27
|
+
case "REFRESH_KEY" /* REFRESH_KEY */:
|
|
28
|
+
return process.env.REFRESH_KEY;
|
|
29
|
+
case "SIGNING_KEY" /* SIGNING_KEY */:
|
|
30
|
+
return process.env.SIGNING_KEY;
|
|
31
|
+
case "INSTAGRAM_CLIENT_SECRET" /* INSTAGRAM_CLIENT_SECRET */:
|
|
32
|
+
return process.env.INSTAGRAM_CLIENT_SECRET;
|
|
33
|
+
case "INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */:
|
|
34
|
+
return process.env.INSTAGRAM_CLIENT_ID;
|
|
35
|
+
case "GOOGLE_CLIENT_SECRET" /* GOOGLE_CLIENT_SECRET */:
|
|
36
|
+
return process.env.GOOGLE_CLIENT_SECRET;
|
|
37
|
+
case "GOOGLE_CLIENT_ID" /* GOOGLE_CLIENT_ID */:
|
|
38
|
+
return process.env.GOOGLE_CLIENT_ID;
|
|
39
|
+
case "TURNSTILE_KEY" /* TURNSTILE_KEY */:
|
|
40
|
+
return process.env.TURNSTILE_KEY;
|
|
41
|
+
case "S3_ACCESS_KEY_ID" /* S3_ACCESS_KEY_ID */:
|
|
42
|
+
return process.env.S3_ACCESS_KEY_ID;
|
|
43
|
+
case "S3_ACCESS_KEY_SECRET" /* S3_ACCESS_KEY_SECRET */:
|
|
44
|
+
return process.env.S3_ACCESS_KEY_SECRET;
|
|
45
|
+
case "S3_REGION" /* S3_REGION */:
|
|
46
|
+
return process.env.S3_REGION;
|
|
47
|
+
case "S3_BUCKET" /* S3_BUCKET */:
|
|
48
|
+
return process.env.S3_BUCKET;
|
|
49
|
+
case "NODE_ENV" /* NODE_ENV */:
|
|
50
|
+
return process.env.NODE_ENV;
|
|
51
|
+
default:
|
|
52
|
+
return process.env[key];
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
function getEnv(key, skipCheck) {
|
|
56
|
+
const value = getEnvValue(key);
|
|
57
|
+
if (!skipCheck && !value) throw new Error(`${key} is not defined`);
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/auth/email/token.ts
|
|
62
|
+
function getUserIdFromAccessToken(accessToken) {
|
|
63
|
+
if (accessToken)
|
|
64
|
+
try {
|
|
65
|
+
const decoded = verify(accessToken, getEnv("SIGNING_KEY" /* SIGNING_KEY */));
|
|
66
|
+
if (typeof decoded !== "string" && typeof decoded.id === "number")
|
|
67
|
+
return decoded.id;
|
|
68
|
+
} catch (e) {
|
|
69
|
+
if (!(e instanceof JsonWebTokenError)) console.error(e, "errors");
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/socials/connect.ts
|
|
76
|
+
function setupSocialAuth({
|
|
77
|
+
providers,
|
|
78
|
+
endpoint,
|
|
79
|
+
onConnect,
|
|
80
|
+
redirectURL,
|
|
81
|
+
errorRedirectURL,
|
|
82
|
+
scopes
|
|
83
|
+
}) {
|
|
84
|
+
const handleError = (message) => NextResponse2.redirect(`${errorRedirectURL}?error=${message}`);
|
|
85
|
+
const GET = async (req, ctx) => {
|
|
86
|
+
const { platform } = await ctx.params;
|
|
87
|
+
const provider = providers.find((p) => p.platform === platform);
|
|
88
|
+
if (!provider) return handleError("Unsupported platform");
|
|
89
|
+
const code = req.nextUrl.searchParams.get("code");
|
|
90
|
+
const error = req.nextUrl.searchParams.get("error");
|
|
91
|
+
const state = req.nextUrl.searchParams.get("state");
|
|
92
|
+
if (error) return handleError(error);
|
|
93
|
+
if (!state) return handleError("Invalid request");
|
|
94
|
+
const redirectURI = `${endpoint}/${provider.platform}`;
|
|
95
|
+
if (!code) {
|
|
96
|
+
return NextResponse2.redirect(
|
|
97
|
+
provider.auth.authorizationURL({
|
|
98
|
+
state,
|
|
99
|
+
redirectURI,
|
|
100
|
+
scopes: scopes?.[provider.platform]
|
|
101
|
+
}),
|
|
102
|
+
302
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
const exchanged = await provider.auth.exchangeCode({ code, redirectURI });
|
|
106
|
+
if (!exchanged) return handleError("Couldn't connect your account");
|
|
107
|
+
const { platformUserId, ...tokens } = exchanged;
|
|
108
|
+
const profile = await provider.fetchProfile(tokens.accessToken);
|
|
109
|
+
if (!profile) return handleError("Couldn't read your profile");
|
|
110
|
+
const errorMessage = await onConnect({
|
|
111
|
+
platform: provider.platform,
|
|
112
|
+
profile: {
|
|
113
|
+
...profile,
|
|
114
|
+
platformUserId: profile.platformUserId ?? platformUserId
|
|
115
|
+
},
|
|
116
|
+
tokens,
|
|
117
|
+
userId: getUserIdFromAccessToken(state)
|
|
118
|
+
});
|
|
119
|
+
if (errorMessage) return handleError(errorMessage);
|
|
120
|
+
return NextResponse2.redirect(redirectURL);
|
|
121
|
+
};
|
|
122
|
+
return { GET };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/socials/types.ts
|
|
126
|
+
var SocialPlatform = /* @__PURE__ */ ((SocialPlatform2) => {
|
|
127
|
+
SocialPlatform2["Instagram"] = "instagram";
|
|
128
|
+
SocialPlatform2["YouTube"] = "youtube";
|
|
129
|
+
SocialPlatform2["TikTok"] = "tiktok";
|
|
130
|
+
return SocialPlatform2;
|
|
131
|
+
})(SocialPlatform || {});
|
|
132
|
+
var SocialMediaKind = /* @__PURE__ */ ((SocialMediaKind2) => {
|
|
133
|
+
SocialMediaKind2["Image"] = "image";
|
|
134
|
+
SocialMediaKind2["Video"] = "video";
|
|
135
|
+
SocialMediaKind2["Carousel"] = "carousel";
|
|
136
|
+
return SocialMediaKind2;
|
|
137
|
+
})(SocialMediaKind || {});
|
|
138
|
+
|
|
139
|
+
// src/socials/instagram/auth.ts
|
|
140
|
+
var getInstagramAuthorizationURL = (state, redirectURI, scopes = ["instagram_business_basic"]) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
|
|
141
|
+
"INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */
|
|
142
|
+
)}&response_type=code&enable_fb_login=0&force_authentication=1&scope=${scopes.join(",")}&state=${state}&redirect_uri=${redirectURI}#weblink`;
|
|
143
|
+
async function getLongLivedInstagramToken(code, redirectURI) {
|
|
144
|
+
const formData = new FormData();
|
|
145
|
+
formData.set("client_id", getEnv("INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */));
|
|
146
|
+
formData.set("client_secret", getEnv("INSTAGRAM_CLIENT_SECRET" /* INSTAGRAM_CLIENT_SECRET */));
|
|
147
|
+
formData.set("grant_type", "authorization_code");
|
|
148
|
+
formData.set("redirect_uri", redirectURI);
|
|
149
|
+
formData.set("code", code);
|
|
150
|
+
const shortRes = await fetch("https://api.instagram.com/oauth/access_token", {
|
|
151
|
+
method: "POST",
|
|
152
|
+
body: formData
|
|
153
|
+
});
|
|
154
|
+
if (!shortRes.ok) return void 0;
|
|
155
|
+
const shortResData = await shortRes.json();
|
|
156
|
+
if (!shortResData.access_token) return void 0;
|
|
157
|
+
const longRes = await fetch(
|
|
158
|
+
`https://graph.instagram.com/access_token?grant_type=ig_exchange_token&access_token=${shortResData.access_token}&client_secret=${getEnv("INSTAGRAM_CLIENT_SECRET" /* INSTAGRAM_CLIENT_SECRET */)}`
|
|
159
|
+
);
|
|
160
|
+
if (!longRes.ok) return void 0;
|
|
161
|
+
const longResData = await longRes.json();
|
|
162
|
+
return {
|
|
163
|
+
accessToken: longResData.access_token,
|
|
164
|
+
userId: shortResData.user_id,
|
|
165
|
+
expiresIn: longResData.expires_in,
|
|
166
|
+
permissions: shortResData.permissions
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
async function refreshInstagramAccessToken(token) {
|
|
170
|
+
const request = await fetch(
|
|
171
|
+
`https://graph.instagram.com/refresh_access_token?grant_type=ig_refresh_token&access_token=${token}`
|
|
172
|
+
);
|
|
173
|
+
const response = await request.json();
|
|
174
|
+
if (!response.access_token) return void 0;
|
|
175
|
+
return {
|
|
176
|
+
accessToken: response.access_token,
|
|
177
|
+
expiresIn: response.expires_in
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
1
181
|
// src/socials/meta/request.ts
|
|
2
182
|
function createGraphClient(baseURL) {
|
|
3
183
|
const buildURL = (token, path, params = {}) => {
|
|
@@ -130,25 +310,151 @@ var canPublishToInstagram = async (token) => {
|
|
|
130
310
|
return true;
|
|
131
311
|
};
|
|
132
312
|
|
|
133
|
-
// src/socials/
|
|
313
|
+
// src/socials/instagram/adapter.ts
|
|
314
|
+
var PROFILE_FIELDS = [
|
|
315
|
+
"id",
|
|
316
|
+
"user_id",
|
|
317
|
+
"username",
|
|
318
|
+
"name",
|
|
319
|
+
"profile_picture_url",
|
|
320
|
+
"followers_count",
|
|
321
|
+
"media_count"
|
|
322
|
+
];
|
|
323
|
+
var MEDIA_FIELDS = [
|
|
324
|
+
"id",
|
|
325
|
+
"caption",
|
|
326
|
+
"media_type",
|
|
327
|
+
"media_url",
|
|
328
|
+
"permalink",
|
|
329
|
+
"thumbnail_url",
|
|
330
|
+
"timestamp",
|
|
331
|
+
"like_count",
|
|
332
|
+
"comments_count"
|
|
333
|
+
];
|
|
334
|
+
var expiresAt = (seconds) => seconds ? new Date(Date.now() + seconds * 1e3) : void 0;
|
|
335
|
+
function toKind(mediaType) {
|
|
336
|
+
if (mediaType === "VIDEO") return "video" /* Video */;
|
|
337
|
+
if (mediaType === "CAROUSEL_ALBUM") return "carousel" /* Carousel */;
|
|
338
|
+
return "image" /* Image */;
|
|
339
|
+
}
|
|
340
|
+
function toPost(media) {
|
|
341
|
+
const kind = toKind(media.media_type);
|
|
342
|
+
return {
|
|
343
|
+
platform: "instagram" /* Instagram */,
|
|
344
|
+
platformMediaId: media.id,
|
|
345
|
+
permalink: media.permalink,
|
|
346
|
+
// A video's `media_url` is the .mp4 itself, so it is never a stand-in for a
|
|
347
|
+
// missing poster frame — better no thumbnail than one that can't render.
|
|
348
|
+
thumbnail: media.thumbnail_url ?? (kind === "video" /* Video */ ? null : media.media_url ?? null),
|
|
349
|
+
mediaURL: media.media_url ?? null,
|
|
350
|
+
caption: media.caption ?? null,
|
|
351
|
+
kind,
|
|
352
|
+
publishedAt: media.timestamp,
|
|
353
|
+
likes: media.like_count ?? null,
|
|
354
|
+
comments: media.comments_count ?? null,
|
|
355
|
+
// Both need the Insights API; the basic media fields carry neither.
|
|
356
|
+
views: null,
|
|
357
|
+
shares: null,
|
|
358
|
+
metadata: {}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
var InstagramProvider = {
|
|
362
|
+
platform: "instagram" /* Instagram */,
|
|
363
|
+
auth: {
|
|
364
|
+
authorizationURL: ({ state, redirectURI, scopes }) => getInstagramAuthorizationURL(
|
|
365
|
+
state,
|
|
366
|
+
redirectURI,
|
|
367
|
+
scopes
|
|
368
|
+
),
|
|
369
|
+
exchangeCode: async ({ code, redirectURI }) => {
|
|
370
|
+
const result = await getLongLivedInstagramToken(code, redirectURI);
|
|
371
|
+
if (!result) {
|
|
372
|
+
console.error("[naystack] Instagram code exchange returned no token");
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
accessToken: result.accessToken,
|
|
377
|
+
platformUserId: result.userId || null,
|
|
378
|
+
expiresAt: expiresAt(result.expiresIn),
|
|
379
|
+
scopes: result.permissions
|
|
380
|
+
};
|
|
381
|
+
},
|
|
382
|
+
refresh: async ({ accessToken }) => {
|
|
383
|
+
const refreshed = await refreshInstagramAccessToken(accessToken);
|
|
384
|
+
if (!refreshed) return null;
|
|
385
|
+
return {
|
|
386
|
+
accessToken: refreshed.accessToken,
|
|
387
|
+
expiresAt: expiresAt(refreshed.expiresIn)
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
fetchProfile: async (accessToken) => {
|
|
392
|
+
const user = await getInstagramUser(
|
|
393
|
+
accessToken,
|
|
394
|
+
void 0,
|
|
395
|
+
PROFILE_FIELDS
|
|
396
|
+
);
|
|
397
|
+
if (!user?.username) return null;
|
|
398
|
+
return {
|
|
399
|
+
platform: "instagram" /* Instagram */,
|
|
400
|
+
// `user_id` is what the OAuth exchange hands back, so preferring it keeps
|
|
401
|
+
// one account's id identical however it was read.
|
|
402
|
+
platformUserId: user.user_id || user.id || null,
|
|
403
|
+
username: user.username,
|
|
404
|
+
displayName: user.name ?? null,
|
|
405
|
+
avatar: user.profile_picture_url ?? null,
|
|
406
|
+
followers: user.followers_count ?? 0,
|
|
407
|
+
contentCount: user.media_count ?? 0,
|
|
408
|
+
metadata: {}
|
|
409
|
+
};
|
|
410
|
+
},
|
|
411
|
+
fetchMedia: async (accessToken, options) => {
|
|
412
|
+
const result = await getInstagramMedia(
|
|
413
|
+
accessToken,
|
|
414
|
+
MEDIA_FIELDS,
|
|
415
|
+
options?.limit
|
|
416
|
+
);
|
|
417
|
+
if (!result?.data) return [];
|
|
418
|
+
return result.data.map(toPost);
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
// src/socials/utils/poll.ts
|
|
134
423
|
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
135
|
-
async function
|
|
424
|
+
async function pollUntilReady(read, isPending, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
|
|
136
425
|
const deadline = Date.now() + timeoutMS;
|
|
137
|
-
let state = await
|
|
426
|
+
let state = await read();
|
|
138
427
|
if (!state) {
|
|
139
428
|
await sleep(2e3);
|
|
140
429
|
return null;
|
|
141
430
|
}
|
|
142
|
-
while (state
|
|
431
|
+
while (state && isPending(state) && Date.now() < deadline) {
|
|
143
432
|
await sleep(intervalMS);
|
|
144
|
-
state = await
|
|
433
|
+
state = await read();
|
|
145
434
|
}
|
|
146
435
|
return state;
|
|
147
436
|
}
|
|
437
|
+
async function withRetry(run, options = {}) {
|
|
438
|
+
const attempts = Math.max(1, options.attempts ?? 3);
|
|
439
|
+
const backoffMS = options.backoffMS ?? 2e3;
|
|
440
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
441
|
+
const result = await run(attempt);
|
|
442
|
+
if (result) return result;
|
|
443
|
+
if (attempt < attempts) {
|
|
444
|
+
const delayMS = backoffMS * 2 ** (attempt - 1);
|
|
445
|
+
options.onRetry?.(attempt, attempts, delayMS);
|
|
446
|
+
await sleep(delayMS);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// src/socials/meta/container.ts
|
|
148
453
|
function createPublisher(platform) {
|
|
149
454
|
const isReady = async (token, id, label, wait) => {
|
|
150
|
-
const state = await
|
|
455
|
+
const state = await pollUntilReady(
|
|
151
456
|
() => platform.getStatus(token, id),
|
|
457
|
+
(current) => current.status === "IN_PROGRESS",
|
|
152
458
|
wait
|
|
153
459
|
);
|
|
154
460
|
if (state && state.status !== "FINISHED" && state.status !== "PUBLISHED") {
|
|
@@ -159,22 +465,21 @@ function createPublisher(platform) {
|
|
|
159
465
|
}
|
|
160
466
|
return true;
|
|
161
467
|
};
|
|
162
|
-
const createReady =
|
|
163
|
-
|
|
164
|
-
const backoffMS = retry?.backoffMS ?? 2e3;
|
|
165
|
-
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
468
|
+
const createReady = (token, params, label, wait, retry) => withRetry(
|
|
469
|
+
async () => {
|
|
166
470
|
const id = await platform.createContainer(token, params);
|
|
167
|
-
if (id
|
|
168
|
-
|
|
169
|
-
|
|
471
|
+
if (!id || !await isReady(token, id, label, wait)) return null;
|
|
472
|
+
return id;
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
...retry,
|
|
476
|
+
onRetry: (attempt, attempts, delayMS) => {
|
|
170
477
|
console.warn(
|
|
171
|
-
`[naystack] ${platform.name} ${label} attempt ${attempt}/${attempts} failed \u2014 retrying in ${
|
|
478
|
+
`[naystack] ${platform.name} ${label} attempt ${attempt}/${attempts} failed \u2014 retrying in ${delayMS}ms`
|
|
172
479
|
);
|
|
173
|
-
await sleep(delay);
|
|
174
480
|
}
|
|
175
481
|
}
|
|
176
|
-
|
|
177
|
-
};
|
|
482
|
+
);
|
|
178
483
|
return {
|
|
179
484
|
/** Creates a container, waits for it to finish processing, then publishes it. */
|
|
180
485
|
publish: async (token, params, wait, retry) => {
|
|
@@ -188,21 +493,30 @@ function createPublisher(platform) {
|
|
|
188
493
|
if (!containerID) return null;
|
|
189
494
|
return platform.publish(token, containerID);
|
|
190
495
|
},
|
|
191
|
-
/**
|
|
496
|
+
/**
|
|
497
|
+
* Creates and awaits every carousel child **in parallel**, resolving to
|
|
498
|
+
* their ids in input order — or `null` if any failed.
|
|
499
|
+
*
|
|
500
|
+
* Parallel because each child is an independent create-then-poll and Meta
|
|
501
|
+
* spends ~5s accepting a single container, so ten sequential children cost
|
|
502
|
+
* ~50s of mostly waiting — past what a serverless caller can fit in its
|
|
503
|
+
* timeout. `Promise.all` preserves input order, which the carousel's slide
|
|
504
|
+
* order depends on.
|
|
505
|
+
*/
|
|
192
506
|
createChildren: async (token, items, wait, retry) => {
|
|
193
|
-
const children =
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
return children;
|
|
507
|
+
const children = await Promise.all(
|
|
508
|
+
items.map(
|
|
509
|
+
(item) => createReady(
|
|
510
|
+
token,
|
|
511
|
+
{ ...item, is_carousel_item: true },
|
|
512
|
+
"carousel item",
|
|
513
|
+
wait,
|
|
514
|
+
retry
|
|
515
|
+
)
|
|
516
|
+
)
|
|
517
|
+
);
|
|
518
|
+
const created = children.filter((childID) => childID !== null);
|
|
519
|
+
return created.length === children.length ? created : null;
|
|
206
520
|
}
|
|
207
521
|
};
|
|
208
522
|
}
|
|
@@ -328,11 +642,11 @@ var sendInstagramMessage = (token, to, text) => {
|
|
|
328
642
|
};
|
|
329
643
|
|
|
330
644
|
// src/socials/meta/webhook.ts
|
|
331
|
-
import { NextResponse } from "next/server";
|
|
645
|
+
import { NextResponse as NextResponse3 } from "next/server";
|
|
332
646
|
var verifyWebhook = (secret) => (req) => {
|
|
333
647
|
const params = req.nextUrl.searchParams;
|
|
334
648
|
if (params.get("hub.verify_token") === secret) {
|
|
335
|
-
return new
|
|
649
|
+
return new NextResponse3(params.get("hub.challenge"));
|
|
336
650
|
}
|
|
337
651
|
};
|
|
338
652
|
|
|
@@ -465,11 +779,15 @@ var setupThreadsWebhook = (options) => {
|
|
|
465
779
|
};
|
|
466
780
|
};
|
|
467
781
|
export {
|
|
782
|
+
InstagramProvider,
|
|
468
783
|
MetaMediaType,
|
|
784
|
+
SocialMediaKind,
|
|
785
|
+
SocialPlatform,
|
|
469
786
|
canPublishToInstagram,
|
|
470
787
|
createInstagramPost,
|
|
471
788
|
createThread,
|
|
472
789
|
createThreadsPost,
|
|
790
|
+
getInstagramAuthorizationURL,
|
|
473
791
|
getInstagramConversation,
|
|
474
792
|
getInstagramConversationByUser,
|
|
475
793
|
getInstagramConversations,
|
|
@@ -478,13 +796,18 @@ export {
|
|
|
478
796
|
getInstagramMedia,
|
|
479
797
|
getInstagramMessage,
|
|
480
798
|
getInstagramUser,
|
|
799
|
+
getLongLivedInstagramToken,
|
|
481
800
|
getThread,
|
|
482
801
|
getThreads,
|
|
483
802
|
getThreadsData,
|
|
484
803
|
getThreadsReplies,
|
|
804
|
+
pollUntilReady,
|
|
485
805
|
readGraphID,
|
|
806
|
+
refreshInstagramAccessToken,
|
|
486
807
|
replyToInstagramComment,
|
|
487
808
|
sendInstagramMessage,
|
|
488
809
|
setupInstagramWebhook,
|
|
489
|
-
|
|
810
|
+
setupSocialAuth,
|
|
811
|
+
setupThreadsWebhook,
|
|
812
|
+
withRetry
|
|
490
813
|
};
|