naystack 1.8.19 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +99 -34
  2. package/dist/auth/client.cjs.js +2 -2
  3. package/dist/auth/client.esm.js +2 -2
  4. package/dist/auth/email/client.cjs.js +2 -2
  5. package/dist/auth/email/client.esm.js +2 -2
  6. package/dist/auth/email/index.cjs.js +2 -2
  7. package/dist/auth/email/index.esm.js +2 -2
  8. package/dist/auth/email/next.cjs.js +2 -2
  9. package/dist/auth/email/next.esm.js +2 -2
  10. package/dist/auth/email/routes/delete.cjs.js +2 -2
  11. package/dist/auth/email/routes/delete.esm.js +2 -2
  12. package/dist/auth/email/routes/get.cjs.js +2 -2
  13. package/dist/auth/email/routes/get.esm.js +2 -2
  14. package/dist/auth/email/routes/post.cjs.js +2 -2
  15. package/dist/auth/email/routes/post.esm.js +2 -2
  16. package/dist/auth/email/routes/put.cjs.js +2 -2
  17. package/dist/auth/email/routes/put.esm.js +2 -2
  18. package/dist/auth/email/token.cjs.js +2 -2
  19. package/dist/auth/email/token.esm.js +2 -2
  20. package/dist/auth/email/utils.cjs.js +2 -2
  21. package/dist/auth/email/utils.esm.js +2 -2
  22. package/dist/auth/google/get.cjs.js +2 -2
  23. package/dist/auth/google/get.esm.js +2 -2
  24. package/dist/auth/google/index.cjs.js +2 -2
  25. package/dist/auth/google/index.esm.js +2 -2
  26. package/dist/auth/index.cjs.js +4 -306
  27. package/dist/auth/index.d.mts +0 -5
  28. package/dist/auth/index.d.ts +0 -5
  29. package/dist/auth/index.esm.js +3 -302
  30. package/dist/auth/token-store.cjs.js +2 -2
  31. package/dist/auth/token-store.esm.js +2 -2
  32. package/dist/env.cjs.js +3 -3
  33. package/dist/env.d.mts +3 -2
  34. package/dist/env.d.ts +3 -2
  35. package/dist/env.esm.js +3 -3
  36. package/dist/file/client.cjs.js +2 -2
  37. package/dist/file/client.esm.js +2 -2
  38. package/dist/file/index.cjs.js +2 -2
  39. package/dist/file/index.esm.js +2 -2
  40. package/dist/file/put.cjs.js +2 -2
  41. package/dist/file/put.esm.js +2 -2
  42. package/dist/file/setup.cjs.js +2 -2
  43. package/dist/file/setup.esm.js +2 -2
  44. package/dist/file/utils.cjs.js +2 -2
  45. package/dist/file/utils.esm.js +2 -2
  46. package/dist/graphql/client.cjs.js +2 -2
  47. package/dist/graphql/client.esm.js +2 -2
  48. package/dist/graphql/index.cjs.js +2 -2
  49. package/dist/graphql/index.esm.js +2 -2
  50. package/dist/graphql/init.cjs.js +2 -2
  51. package/dist/graphql/init.esm.js +2 -2
  52. package/dist/graphql/links.cjs.js +2 -2
  53. package/dist/graphql/links.esm.js +2 -2
  54. package/dist/graphql/next.cjs.js +2 -2
  55. package/dist/graphql/next.esm.js +2 -2
  56. package/dist/graphql/server.cjs.js +2 -2
  57. package/dist/graphql/server.esm.js +2 -2
  58. package/dist/graphql/utils.cjs.js +2 -2
  59. package/dist/graphql/utils.esm.js +2 -2
  60. package/dist/socials/connect.cjs.js +151 -0
  61. package/dist/socials/connect.d.mts +70 -0
  62. package/dist/socials/connect.d.ts +70 -0
  63. package/dist/socials/connect.esm.js +126 -0
  64. package/dist/socials/index.cjs.js +345 -21
  65. package/dist/socials/index.d.mts +8 -2
  66. package/dist/socials/index.d.ts +8 -2
  67. package/dist/socials/index.esm.js +335 -20
  68. package/dist/socials/instagram/adapter.cjs.js +276 -0
  69. package/dist/socials/instagram/adapter.d.mts +30 -0
  70. package/dist/socials/instagram/adapter.d.ts +30 -0
  71. package/dist/socials/instagram/adapter.esm.js +249 -0
  72. package/dist/{auth/instagram/utils.cjs.js → socials/instagram/auth.cjs.js} +43 -41
  73. package/dist/socials/instagram/auth.d.mts +52 -0
  74. package/dist/socials/instagram/auth.d.ts +52 -0
  75. package/dist/{auth/instagram/utils.esm.js → socials/instagram/auth.esm.js} +37 -35
  76. package/dist/socials/instagram/getters.d.mts +1 -0
  77. package/dist/socials/instagram/getters.d.ts +1 -0
  78. package/dist/socials/instagram/setters.cjs.js +33 -17
  79. package/dist/socials/instagram/setters.d.mts +1 -0
  80. package/dist/socials/instagram/setters.d.ts +1 -0
  81. package/dist/socials/instagram/setters.esm.js +33 -17
  82. package/dist/socials/instagram/types.d.mts +2 -1
  83. package/dist/socials/instagram/types.d.ts +2 -1
  84. package/dist/socials/meta/container.cjs.js +34 -16
  85. package/dist/socials/meta/container.d.mts +5 -27
  86. package/dist/socials/meta/container.d.ts +5 -27
  87. package/dist/socials/meta/container.esm.js +33 -17
  88. package/dist/socials/provider.cjs.js +18 -0
  89. package/dist/socials/provider.d.mts +54 -0
  90. package/dist/socials/provider.d.ts +54 -0
  91. package/dist/socials/provider.esm.js +0 -0
  92. package/dist/socials/threads/getters.d.mts +1 -0
  93. package/dist/socials/threads/getters.d.ts +1 -0
  94. package/dist/socials/threads/setters.cjs.js +33 -17
  95. package/dist/socials/threads/setters.d.mts +1 -0
  96. package/dist/socials/threads/setters.d.ts +1 -0
  97. package/dist/socials/threads/setters.esm.js +33 -17
  98. package/dist/socials/threads/types.d.mts +2 -1
  99. package/dist/socials/threads/types.d.ts +2 -1
  100. package/dist/socials/types.cjs.js +43 -0
  101. package/dist/socials/types.d.mts +99 -0
  102. package/dist/socials/types.d.ts +99 -0
  103. package/dist/socials/types.esm.js +17 -0
  104. package/dist/socials/utils/poll.cjs.js +59 -0
  105. package/dist/socials/utils/poll.d.mts +85 -0
  106. package/dist/socials/utils/poll.d.ts +85 -0
  107. package/dist/socials/utils/poll.esm.js +33 -0
  108. package/dist/utils/client/index.cjs.js +2 -2
  109. package/dist/utils/client/index.esm.js +2 -2
  110. package/dist/utils/client/seo.cjs.js +2 -2
  111. package/dist/utils/client/seo.esm.js +2 -2
  112. package/package.json +1 -1
  113. package/dist/auth/instagram/index.cjs.js +0 -388
  114. package/dist/auth/instagram/index.d.mts +0 -58
  115. package/dist/auth/instagram/index.d.ts +0 -58
  116. package/dist/auth/instagram/index.esm.js +0 -359
  117. package/dist/auth/instagram/route.cjs.js +0 -368
  118. package/dist/auth/instagram/route.d.mts +0 -15
  119. package/dist/auth/instagram/route.d.ts +0 -15
  120. package/dist/auth/instagram/route.esm.js +0 -343
  121. package/dist/auth/instagram/utils.d.mts +0 -57
  122. 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,152 @@ var canPublishToInstagram = async (token) => {
130
310
  return true;
131
311
  };
132
312
 
133
- // src/socials/meta/container.ts
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
+ profileURL: (username) => `https://instagram.com/${username}`,
392
+ fetchProfile: async (accessToken) => {
393
+ const user = await getInstagramUser(
394
+ accessToken,
395
+ void 0,
396
+ PROFILE_FIELDS
397
+ );
398
+ if (!user?.username) return null;
399
+ return {
400
+ platform: "instagram" /* Instagram */,
401
+ // `user_id` is what the OAuth exchange hands back, so preferring it keeps
402
+ // one account's id identical however it was read.
403
+ platformUserId: user.user_id || user.id || null,
404
+ username: user.username,
405
+ displayName: user.name ?? null,
406
+ avatar: user.profile_picture_url ?? null,
407
+ followers: user.followers_count ?? 0,
408
+ contentCount: user.media_count ?? 0,
409
+ metadata: {}
410
+ };
411
+ },
412
+ fetchMedia: async (accessToken, options) => {
413
+ const result = await getInstagramMedia(
414
+ accessToken,
415
+ MEDIA_FIELDS,
416
+ options?.limit
417
+ );
418
+ if (!result?.data) return [];
419
+ return result.data.map(toPost);
420
+ }
421
+ };
422
+
423
+ // src/socials/utils/poll.ts
134
424
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
135
- async function waitForContainer(getState, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
425
+ async function pollUntilReady(read, isPending, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
136
426
  const deadline = Date.now() + timeoutMS;
137
- let state = await getState();
427
+ let state = await read();
138
428
  if (!state) {
139
429
  await sleep(2e3);
140
430
  return null;
141
431
  }
142
- while (state?.status === "IN_PROGRESS" && Date.now() < deadline) {
432
+ while (state && isPending(state) && Date.now() < deadline) {
143
433
  await sleep(intervalMS);
144
- state = await getState();
434
+ state = await read();
145
435
  }
146
436
  return state;
147
437
  }
438
+ async function withRetry(run, options = {}) {
439
+ const attempts = Math.max(1, options.attempts ?? 3);
440
+ const backoffMS = options.backoffMS ?? 2e3;
441
+ for (let attempt = 1; attempt <= attempts; attempt++) {
442
+ const result = await run(attempt);
443
+ if (result) return result;
444
+ if (attempt < attempts) {
445
+ const delayMS = backoffMS * 2 ** (attempt - 1);
446
+ options.onRetry?.(attempt, attempts, delayMS);
447
+ await sleep(delayMS);
448
+ }
449
+ }
450
+ return null;
451
+ }
452
+
453
+ // src/socials/meta/container.ts
148
454
  function createPublisher(platform) {
149
455
  const isReady = async (token, id, label, wait) => {
150
- const state = await waitForContainer(
456
+ const state = await pollUntilReady(
151
457
  () => platform.getStatus(token, id),
458
+ (current) => current.status === "IN_PROGRESS",
152
459
  wait
153
460
  );
154
461
  if (state && state.status !== "FINISHED" && state.status !== "PUBLISHED") {
@@ -159,22 +466,21 @@ function createPublisher(platform) {
159
466
  }
160
467
  return true;
161
468
  };
162
- const createReady = async (token, params, label, wait, retry) => {
163
- const attempts = Math.max(1, retry?.attempts ?? 3);
164
- const backoffMS = retry?.backoffMS ?? 2e3;
165
- for (let attempt = 1; attempt <= attempts; attempt++) {
469
+ const createReady = (token, params, label, wait, retry) => withRetry(
470
+ async () => {
166
471
  const id = await platform.createContainer(token, params);
167
- if (id && await isReady(token, id, label, wait)) return id;
168
- if (attempt < attempts) {
169
- const delay = backoffMS * 2 ** (attempt - 1);
472
+ if (!id || !await isReady(token, id, label, wait)) return null;
473
+ return id;
474
+ },
475
+ {
476
+ ...retry,
477
+ onRetry: (attempt, attempts, delayMS) => {
170
478
  console.warn(
171
- `[naystack] ${platform.name} ${label} attempt ${attempt}/${attempts} failed \u2014 retrying in ${delay}ms`
479
+ `[naystack] ${platform.name} ${label} attempt ${attempt}/${attempts} failed \u2014 retrying in ${delayMS}ms`
172
480
  );
173
- await sleep(delay);
174
481
  }
175
482
  }
176
- return null;
177
- };
483
+ );
178
484
  return {
179
485
  /** Creates a container, waits for it to finish processing, then publishes it. */
180
486
  publish: async (token, params, wait, retry) => {
@@ -337,11 +643,11 @@ var sendInstagramMessage = (token, to, text) => {
337
643
  };
338
644
 
339
645
  // src/socials/meta/webhook.ts
340
- import { NextResponse } from "next/server";
646
+ import { NextResponse as NextResponse3 } from "next/server";
341
647
  var verifyWebhook = (secret) => (req) => {
342
648
  const params = req.nextUrl.searchParams;
343
649
  if (params.get("hub.verify_token") === secret) {
344
- return new NextResponse(params.get("hub.challenge"));
650
+ return new NextResponse3(params.get("hub.challenge"));
345
651
  }
346
652
  };
347
653
 
@@ -474,11 +780,15 @@ var setupThreadsWebhook = (options) => {
474
780
  };
475
781
  };
476
782
  export {
783
+ InstagramProvider,
477
784
  MetaMediaType,
785
+ SocialMediaKind,
786
+ SocialPlatform,
478
787
  canPublishToInstagram,
479
788
  createInstagramPost,
480
789
  createThread,
481
790
  createThreadsPost,
791
+ getInstagramAuthorizationURL,
482
792
  getInstagramConversation,
483
793
  getInstagramConversationByUser,
484
794
  getInstagramConversations,
@@ -487,13 +797,18 @@ export {
487
797
  getInstagramMedia,
488
798
  getInstagramMessage,
489
799
  getInstagramUser,
800
+ getLongLivedInstagramToken,
490
801
  getThread,
491
802
  getThreads,
492
803
  getThreadsData,
493
804
  getThreadsReplies,
805
+ pollUntilReady,
494
806
  readGraphID,
807
+ refreshInstagramAccessToken,
495
808
  replyToInstagramComment,
496
809
  sendInstagramMessage,
497
810
  setupInstagramWebhook,
498
- setupThreadsWebhook
811
+ setupSocialAuth,
812
+ setupThreadsWebhook,
813
+ withRetry
499
814
  };
@@ -0,0 +1,276 @@
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/instagram/adapter.ts
21
+ var adapter_exports = {};
22
+ __export(adapter_exports, {
23
+ InstagramProvider: () => InstagramProvider
24
+ });
25
+ module.exports = __toCommonJS(adapter_exports);
26
+
27
+ // src/env.ts
28
+ var EXTRA_ENV = globalThis.__NAYSTACK_ENV__ || (globalThis.__NAYSTACK_ENV__ = {});
29
+ var getEnvValue = (key) => {
30
+ switch (key) {
31
+ case "NEXT_PUBLIC_GRAPHQL_ENDPOINT" /* NEXT_PUBLIC_GRAPHQL_ENDPOINT */:
32
+ return process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT || EXTRA_ENV.GRAPHQL_ENDPOINT;
33
+ case "NEXT_PUBLIC_EMAIL_AUTH_ENDPOINT" /* NEXT_PUBLIC_EMAIL_AUTH_ENDPOINT */:
34
+ return process.env.NEXT_PUBLIC_EMAIL_AUTH_ENDPOINT || EXTRA_ENV.EMAIL_AUTH_ENDPOINT;
35
+ case "NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT" /* NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT */:
36
+ return process.env.NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT || EXTRA_ENV.GOOGLE_AUTH_ENDPOINT;
37
+ case "NEXT_PUBLIC_SOCIAL_AUTH_ENDPOINT" /* NEXT_PUBLIC_SOCIAL_AUTH_ENDPOINT */:
38
+ return process.env.NEXT_PUBLIC_SOCIAL_AUTH_ENDPOINT || EXTRA_ENV.SOCIAL_AUTH_ENDPOINT;
39
+ case "NEXT_PUBLIC_FILE_ENDPOINT" /* NEXT_PUBLIC_FILE_ENDPOINT */:
40
+ return process.env.NEXT_PUBLIC_FILE_ENDPOINT || EXTRA_ENV.FILE_ENDPOINT;
41
+ case "NEXT_PUBLIC_BASE_URL" /* NEXT_PUBLIC_BASE_URL */:
42
+ return process.env.NEXT_PUBLIC_BASE_URL || EXTRA_ENV.BASE_URL;
43
+ case "REFRESH_KEY" /* REFRESH_KEY */:
44
+ return process.env.REFRESH_KEY;
45
+ case "SIGNING_KEY" /* SIGNING_KEY */:
46
+ return process.env.SIGNING_KEY;
47
+ case "INSTAGRAM_CLIENT_SECRET" /* INSTAGRAM_CLIENT_SECRET */:
48
+ return process.env.INSTAGRAM_CLIENT_SECRET;
49
+ case "INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */:
50
+ return process.env.INSTAGRAM_CLIENT_ID;
51
+ case "GOOGLE_CLIENT_SECRET" /* GOOGLE_CLIENT_SECRET */:
52
+ return process.env.GOOGLE_CLIENT_SECRET;
53
+ case "GOOGLE_CLIENT_ID" /* GOOGLE_CLIENT_ID */:
54
+ return process.env.GOOGLE_CLIENT_ID;
55
+ case "TURNSTILE_KEY" /* TURNSTILE_KEY */:
56
+ return process.env.TURNSTILE_KEY;
57
+ case "S3_ACCESS_KEY_ID" /* S3_ACCESS_KEY_ID */:
58
+ return process.env.S3_ACCESS_KEY_ID;
59
+ case "S3_ACCESS_KEY_SECRET" /* S3_ACCESS_KEY_SECRET */:
60
+ return process.env.S3_ACCESS_KEY_SECRET;
61
+ case "S3_REGION" /* S3_REGION */:
62
+ return process.env.S3_REGION;
63
+ case "S3_BUCKET" /* S3_BUCKET */:
64
+ return process.env.S3_BUCKET;
65
+ case "NODE_ENV" /* NODE_ENV */:
66
+ return process.env.NODE_ENV;
67
+ default:
68
+ return process.env[key];
69
+ }
70
+ };
71
+ function getEnv(key, skipCheck) {
72
+ const value = getEnvValue(key);
73
+ if (!skipCheck && !value) throw new Error(`${key} is not defined`);
74
+ return value;
75
+ }
76
+
77
+ // src/socials/instagram/auth.ts
78
+ var getInstagramAuthorizationURL = (state, redirectURI, scopes = ["instagram_business_basic"]) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
79
+ "INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */
80
+ )}&response_type=code&enable_fb_login=0&force_authentication=1&scope=${scopes.join(",")}&state=${state}&redirect_uri=${redirectURI}#weblink`;
81
+ async function getLongLivedInstagramToken(code, redirectURI) {
82
+ const formData = new FormData();
83
+ formData.set("client_id", getEnv("INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */));
84
+ formData.set("client_secret", getEnv("INSTAGRAM_CLIENT_SECRET" /* INSTAGRAM_CLIENT_SECRET */));
85
+ formData.set("grant_type", "authorization_code");
86
+ formData.set("redirect_uri", redirectURI);
87
+ formData.set("code", code);
88
+ const shortRes = await fetch("https://api.instagram.com/oauth/access_token", {
89
+ method: "POST",
90
+ body: formData
91
+ });
92
+ if (!shortRes.ok) return void 0;
93
+ const shortResData = await shortRes.json();
94
+ if (!shortResData.access_token) return void 0;
95
+ const longRes = await fetch(
96
+ `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 */)}`
97
+ );
98
+ if (!longRes.ok) return void 0;
99
+ const longResData = await longRes.json();
100
+ return {
101
+ accessToken: longResData.access_token,
102
+ userId: shortResData.user_id,
103
+ expiresIn: longResData.expires_in,
104
+ permissions: shortResData.permissions
105
+ };
106
+ }
107
+ async function refreshInstagramAccessToken(token) {
108
+ const request = await fetch(
109
+ `https://graph.instagram.com/refresh_access_token?grant_type=ig_refresh_token&access_token=${token}`
110
+ );
111
+ const response = await request.json();
112
+ if (!response.access_token) return void 0;
113
+ return {
114
+ accessToken: response.access_token,
115
+ expiresIn: response.expires_in
116
+ };
117
+ }
118
+
119
+ // src/socials/meta/request.ts
120
+ function createGraphClient(baseURL) {
121
+ const buildURL = (token, path, params = {}) => {
122
+ const search = new URLSearchParams();
123
+ for (const [key, value] of Object.entries(params)) {
124
+ if (value !== void 0) search.set(key, String(value));
125
+ }
126
+ search.set("access_token", token);
127
+ return `${baseURL}/${path}?${search.toString()}`;
128
+ };
129
+ const request = async (token, path, options = {}) => {
130
+ const response = await fetch(buildURL(token, path, options.params), {
131
+ method: options.method || (options.body ? "POST" : "GET"),
132
+ headers: {
133
+ Authorization: `Bearer ${token}`,
134
+ ...options.body ? { "Content-Type": "application/json" } : {}
135
+ },
136
+ ...options.body ? { body: JSON.stringify(options.body) } : {}
137
+ });
138
+ return response.json().catch(() => null);
139
+ };
140
+ return { buildURL, request };
141
+ }
142
+
143
+ // src/socials/instagram/utils.ts
144
+ var client = createGraphClient("https://graph.instagram.com/v23.0");
145
+ var getInstagramData = client.request;
146
+
147
+ // src/socials/instagram/getters.ts
148
+ var getInstagramUser = (token, id, fields) => {
149
+ return getInstagramData(token, id || "me", {
150
+ params: {
151
+ fields: fields ? fields.join(",") : "username,followers_count,media_count"
152
+ }
153
+ });
154
+ };
155
+ var getInstagramMedia = (token, fields, limit = 12) => {
156
+ return getInstagramData(token, "me/media", {
157
+ params: {
158
+ fields: fields ? fields.join(",") : "like_count,comments_count,permalink",
159
+ limit
160
+ }
161
+ });
162
+ };
163
+
164
+ // src/socials/instagram/adapter.ts
165
+ var PROFILE_FIELDS = [
166
+ "id",
167
+ "user_id",
168
+ "username",
169
+ "name",
170
+ "profile_picture_url",
171
+ "followers_count",
172
+ "media_count"
173
+ ];
174
+ var MEDIA_FIELDS = [
175
+ "id",
176
+ "caption",
177
+ "media_type",
178
+ "media_url",
179
+ "permalink",
180
+ "thumbnail_url",
181
+ "timestamp",
182
+ "like_count",
183
+ "comments_count"
184
+ ];
185
+ var expiresAt = (seconds) => seconds ? new Date(Date.now() + seconds * 1e3) : void 0;
186
+ function toKind(mediaType) {
187
+ if (mediaType === "VIDEO") return "video" /* Video */;
188
+ if (mediaType === "CAROUSEL_ALBUM") return "carousel" /* Carousel */;
189
+ return "image" /* Image */;
190
+ }
191
+ function toPost(media) {
192
+ const kind = toKind(media.media_type);
193
+ return {
194
+ platform: "instagram" /* Instagram */,
195
+ platformMediaId: media.id,
196
+ permalink: media.permalink,
197
+ // A video's `media_url` is the .mp4 itself, so it is never a stand-in for a
198
+ // missing poster frame — better no thumbnail than one that can't render.
199
+ thumbnail: media.thumbnail_url ?? (kind === "video" /* Video */ ? null : media.media_url ?? null),
200
+ mediaURL: media.media_url ?? null,
201
+ caption: media.caption ?? null,
202
+ kind,
203
+ publishedAt: media.timestamp,
204
+ likes: media.like_count ?? null,
205
+ comments: media.comments_count ?? null,
206
+ // Both need the Insights API; the basic media fields carry neither.
207
+ views: null,
208
+ shares: null,
209
+ metadata: {}
210
+ };
211
+ }
212
+ var InstagramProvider = {
213
+ platform: "instagram" /* Instagram */,
214
+ auth: {
215
+ authorizationURL: ({ state, redirectURI, scopes }) => getInstagramAuthorizationURL(
216
+ state,
217
+ redirectURI,
218
+ scopes
219
+ ),
220
+ exchangeCode: async ({ code, redirectURI }) => {
221
+ const result = await getLongLivedInstagramToken(code, redirectURI);
222
+ if (!result) {
223
+ console.error("[naystack] Instagram code exchange returned no token");
224
+ return null;
225
+ }
226
+ return {
227
+ accessToken: result.accessToken,
228
+ platformUserId: result.userId || null,
229
+ expiresAt: expiresAt(result.expiresIn),
230
+ scopes: result.permissions
231
+ };
232
+ },
233
+ refresh: async ({ accessToken }) => {
234
+ const refreshed = await refreshInstagramAccessToken(accessToken);
235
+ if (!refreshed) return null;
236
+ return {
237
+ accessToken: refreshed.accessToken,
238
+ expiresAt: expiresAt(refreshed.expiresIn)
239
+ };
240
+ }
241
+ },
242
+ profileURL: (username) => `https://instagram.com/${username}`,
243
+ fetchProfile: async (accessToken) => {
244
+ const user = await getInstagramUser(
245
+ accessToken,
246
+ void 0,
247
+ PROFILE_FIELDS
248
+ );
249
+ if (!user?.username) return null;
250
+ return {
251
+ platform: "instagram" /* Instagram */,
252
+ // `user_id` is what the OAuth exchange hands back, so preferring it keeps
253
+ // one account's id identical however it was read.
254
+ platformUserId: user.user_id || user.id || null,
255
+ username: user.username,
256
+ displayName: user.name ?? null,
257
+ avatar: user.profile_picture_url ?? null,
258
+ followers: user.followers_count ?? 0,
259
+ contentCount: user.media_count ?? 0,
260
+ metadata: {}
261
+ };
262
+ },
263
+ fetchMedia: async (accessToken, options) => {
264
+ const result = await getInstagramMedia(
265
+ accessToken,
266
+ MEDIA_FIELDS,
267
+ options?.limit
268
+ );
269
+ if (!result?.data) return [];
270
+ return result.data.map(toPost);
271
+ }
272
+ };
273
+ // Annotate the CommonJS export names for ESM import in node:
274
+ 0 && (module.exports = {
275
+ InstagramProvider
276
+ });