naystack 1.8.12 → 1.8.14

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 (74) hide show
  1. package/README.md +189 -31
  2. package/dist/auth/index.cjs.js +158 -18
  3. package/dist/auth/index.d.mts +3 -1
  4. package/dist/auth/index.d.ts +3 -1
  5. package/dist/auth/index.esm.js +158 -18
  6. package/dist/auth/instagram/index.cjs.js +158 -18
  7. package/dist/auth/instagram/index.d.mts +6 -1
  8. package/dist/auth/instagram/index.d.ts +6 -1
  9. package/dist/auth/instagram/index.esm.js +158 -18
  10. package/dist/auth/instagram/route.cjs.js +158 -18
  11. package/dist/auth/instagram/route.d.mts +3 -1
  12. package/dist/auth/instagram/route.d.ts +3 -1
  13. package/dist/auth/instagram/route.esm.js +158 -18
  14. package/dist/auth/instagram/utils.cjs.js +2 -2
  15. package/dist/auth/instagram/utils.d.mts +13 -2
  16. package/dist/auth/instagram/utils.d.ts +13 -2
  17. package/dist/auth/instagram/utils.esm.js +2 -2
  18. package/dist/file/setup.d.mts +13 -5
  19. package/dist/file/setup.d.ts +13 -5
  20. package/dist/index.d.mts +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/socials/index.cjs.js +298 -72
  23. package/dist/socials/index.d.mts +6 -4
  24. package/dist/socials/index.d.ts +6 -4
  25. package/dist/socials/index.esm.js +296 -72
  26. package/dist/socials/instagram/getters.cjs.js +76 -25
  27. package/dist/socials/instagram/getters.d.mts +56 -6
  28. package/dist/socials/instagram/getters.d.ts +56 -6
  29. package/dist/socials/instagram/getters.esm.js +74 -25
  30. package/dist/socials/instagram/setters.cjs.js +192 -14
  31. package/dist/socials/instagram/setters.d.mts +43 -3
  32. package/dist/socials/instagram/setters.d.ts +43 -3
  33. package/dist/socials/instagram/setters.esm.js +191 -14
  34. package/dist/socials/instagram/types.d.mts +48 -11
  35. package/dist/socials/instagram/types.d.ts +48 -11
  36. package/dist/socials/instagram/utils.cjs.js +28 -15
  37. package/dist/socials/instagram/utils.d.mts +7 -18
  38. package/dist/socials/instagram/utils.d.ts +7 -18
  39. package/dist/socials/instagram/utils.esm.js +26 -14
  40. package/dist/socials/instagram/webhook.cjs.js +1 -1
  41. package/dist/socials/instagram/webhook.esm.js +1 -1
  42. package/dist/socials/meta/container.cjs.js +82 -0
  43. package/dist/socials/meta/container.d.mts +38 -0
  44. package/dist/socials/meta/container.d.ts +38 -0
  45. package/dist/socials/meta/container.esm.js +57 -0
  46. package/dist/socials/meta/request.cjs.js +60 -0
  47. package/dist/socials/meta/request.d.mts +50 -0
  48. package/dist/socials/meta/request.d.ts +50 -0
  49. package/dist/socials/meta/request.esm.js +34 -0
  50. package/dist/socials/meta/types.cjs.js +34 -0
  51. package/dist/socials/meta/types.d.mts +51 -0
  52. package/dist/socials/meta/types.d.ts +51 -0
  53. package/dist/socials/meta/types.esm.js +9 -0
  54. package/dist/socials/{meta-webhook.cjs.js → meta/webhook.cjs.js} +4 -4
  55. package/dist/socials/{meta-webhook.esm.js → meta/webhook.esm.js} +1 -1
  56. package/dist/socials/threads/getters.cjs.js +36 -11
  57. package/dist/socials/threads/getters.d.mts +23 -2
  58. package/dist/socials/threads/getters.d.ts +23 -2
  59. package/dist/socials/threads/getters.esm.js +35 -11
  60. package/dist/socials/threads/setters.cjs.js +150 -44
  61. package/dist/socials/threads/setters.d.mts +25 -34
  62. package/dist/socials/threads/setters.d.ts +25 -34
  63. package/dist/socials/threads/setters.esm.js +149 -41
  64. package/dist/socials/threads/types.d.mts +40 -1
  65. package/dist/socials/threads/types.d.ts +40 -1
  66. package/dist/socials/threads/utils.cjs.js +29 -11
  67. package/dist/socials/threads/utils.d.mts +8 -17
  68. package/dist/socials/threads/utils.d.ts +8 -17
  69. package/dist/socials/threads/utils.esm.js +27 -10
  70. package/dist/socials/threads/webhook.cjs.js +1 -1
  71. package/dist/socials/threads/webhook.esm.js +1 -1
  72. package/package.json +1 -1
  73. /package/dist/socials/{meta-webhook.d.mts → meta/webhook.d.mts} +0 -0
  74. /package/dist/socials/{meta-webhook.d.ts → meta/webhook.d.ts} +0 -0
@@ -571,40 +571,177 @@ async function getLongLivedToken(code, redirectURL, clientId, clientSecret) {
571
571
  }
572
572
  }
573
573
  }
574
- var getInstagramAuthorizationURL = (token) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
574
+ var getInstagramAuthorizationURL = (token, scopes = ["instagram_business_basic"]) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
575
575
  "INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */
576
- )}&response_type=code&enable_fb_login=0&force_authentication=1&scope=instagram_business_basic&state=${token}&redirect_uri=${getEnv("NEXT_PUBLIC_INSTAGRAM_AUTH_ENDPOINT" /* NEXT_PUBLIC_INSTAGRAM_AUTH_ENDPOINT */)}#weblink`;
576
+ )}&response_type=code&enable_fb_login=0&force_authentication=1&scope=${scopes.join(",")}&state=${token}&redirect_uri=${getEnv("NEXT_PUBLIC_INSTAGRAM_AUTH_ENDPOINT" /* NEXT_PUBLIC_INSTAGRAM_AUTH_ENDPOINT */)}#weblink`;
577
577
 
578
- // src/socials/instagram/utils.ts
579
- function getInstagramURL(token, path, params) {
580
- return `https://graph.instagram.com/v23.0/${path}?${Object.keys(params).map((key) => `${key}=${params[key]}`).join("&")}&access_token=${token}`;
581
- }
582
- function getInstagramData(token, path, params = {}, postData) {
583
- return fetch(getInstagramURL(token, path, params), {
584
- method: postData ? "POST" : "GET",
585
- body: JSON.stringify(postData),
586
- headers: {
587
- Authorization: `Bearer ${token}`,
588
- "Content-Type": "application/json"
578
+ // src/socials/meta/request.ts
579
+ function createGraphClient(baseURL) {
580
+ const buildURL = (token, path, params = {}) => {
581
+ const search = new URLSearchParams();
582
+ for (const [key, value] of Object.entries(params)) {
583
+ if (value !== void 0) search.set(key, String(value));
589
584
  }
590
- }).then((res) => res.json());
585
+ search.set("access_token", token);
586
+ return `${baseURL}/${path}?${search.toString()}`;
587
+ };
588
+ const request = async (token, path, options = {}) => {
589
+ const response = await fetch(buildURL(token, path, options.params), {
590
+ method: options.method || (options.body ? "POST" : "GET"),
591
+ headers: {
592
+ Authorization: `Bearer ${token}`,
593
+ ...options.body ? { "Content-Type": "application/json" } : {}
594
+ },
595
+ ...options.body ? { body: JSON.stringify(options.body) } : {}
596
+ });
597
+ return response.json().catch(() => null);
598
+ };
599
+ return { buildURL, request };
600
+ }
601
+ function readGraphID(label, result) {
602
+ if (result?.error) {
603
+ console.error(`[naystack] ${label} failed: ${result.error.message}`);
604
+ return null;
605
+ }
606
+ return result?.id || null;
591
607
  }
592
608
 
609
+ // src/socials/instagram/utils.ts
610
+ var client = createGraphClient("https://graph.instagram.com/v23.0");
611
+ var getInstagramData = client.request;
612
+
593
613
  // src/socials/instagram/getters.ts
594
614
  var getInstagramUser = (token, id, fields) => {
595
615
  return getInstagramData(token, id || "me", {
596
- fields: fields ? fields.join(",") : "username,followers_count,media_count"
616
+ params: {
617
+ fields: fields ? fields.join(",") : "username,followers_count,media_count"
618
+ }
597
619
  });
598
620
  };
621
+ var getInstagramContainerStatus = async (token, id) => {
622
+ const result = await getInstagramData(token, id, { params: { fields: "status_code,status" } });
623
+ if (!result?.status_code) return null;
624
+ return {
625
+ status: result.status_code,
626
+ error: result.status_code === "ERROR" ? result.status : void 0
627
+ };
628
+ };
599
629
 
600
- // src/socials/meta-webhook.ts
630
+ // src/socials/meta/container.ts
631
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
632
+ async function waitForContainer(getState, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
633
+ const deadline = Date.now() + timeoutMS;
634
+ let state = await getState();
635
+ if (!state) {
636
+ await sleep(2e3);
637
+ return null;
638
+ }
639
+ while (state?.status === "IN_PROGRESS" && Date.now() < deadline) {
640
+ await sleep(intervalMS);
641
+ state = await getState();
642
+ }
643
+ return state;
644
+ }
645
+ function createPublisher(platform) {
646
+ const isReady = async (token, id, label, wait) => {
647
+ const state = await waitForContainer(
648
+ () => platform.getStatus(token, id),
649
+ wait
650
+ );
651
+ if (state && state.status !== "FINISHED" && state.status !== "PUBLISHED") {
652
+ console.error(
653
+ `[naystack] ${platform.name} ${label} ${id} is ${state.status}${state.error ? `: ${state.error}` : ""}`
654
+ );
655
+ return false;
656
+ }
657
+ return true;
658
+ };
659
+ return {
660
+ /** Creates a container, waits for it to finish processing, then publishes it. */
661
+ publish: async (token, params, wait) => {
662
+ const containerID = await platform.createContainer(token, params);
663
+ if (!containerID) return null;
664
+ if (!await isReady(token, containerID, "container", wait)) return null;
665
+ return platform.publish(token, containerID);
666
+ },
667
+ /** Creates and awaits every carousel child, resolving to their ids — or `null` if any failed. */
668
+ createChildren: async (token, items, wait) => {
669
+ const children = [];
670
+ for (const item of items) {
671
+ const childID = await platform.createContainer(token, {
672
+ ...item,
673
+ is_carousel_item: true
674
+ });
675
+ if (!childID) return null;
676
+ if (!await isReady(token, childID, "carousel item", wait))
677
+ return null;
678
+ children.push(childID);
679
+ }
680
+ return children;
681
+ }
682
+ };
683
+ }
684
+
685
+ // src/socials/instagram/setters.ts
686
+ var publisher = createPublisher({
687
+ name: "Instagram",
688
+ getStatus: getInstagramContainerStatus,
689
+ createContainer: async (token, params) => readGraphID(
690
+ "Instagram media container",
691
+ await getInstagramData(token, "me/media", {
692
+ params,
693
+ method: "POST"
694
+ })
695
+ ),
696
+ publish: async (token, creationID) => readGraphID(
697
+ "Instagram publish",
698
+ await getInstagramData(token, "me/media_publish", {
699
+ params: { creation_id: creationID },
700
+ method: "POST"
701
+ })
702
+ )
703
+ });
704
+
705
+ // src/socials/meta/webhook.ts
601
706
  import { NextResponse as NextResponse6 } from "next/server";
602
707
 
708
+ // src/socials/threads/utils.ts
709
+ var client2 = createGraphClient("https://graph.threads.net/v1.0");
710
+ var getThreadsData = client2.request;
711
+
712
+ // src/socials/threads/getters.ts
713
+ var getThreadsContainerStatus = async (token, id) => {
714
+ const result = await getThreadsData(token, id, { params: { fields: "status,error_message" } });
715
+ if (!result?.status) return null;
716
+ return { status: result.status, error: result.error_message };
717
+ };
718
+
719
+ // src/socials/threads/setters.ts
720
+ var publisher2 = createPublisher({
721
+ name: "Threads",
722
+ getStatus: getThreadsContainerStatus,
723
+ createContainer: async (token, params) => readGraphID(
724
+ "Threads container",
725
+ await getThreadsData(token, "me/threads", {
726
+ params,
727
+ method: "POST"
728
+ })
729
+ ),
730
+ publish: async (token, creationID) => readGraphID(
731
+ "Threads publish",
732
+ await getThreadsData(token, "me/threads_publish", {
733
+ params: { creation_id: creationID },
734
+ method: "POST"
735
+ })
736
+ )
737
+ });
738
+
603
739
  // src/auth/instagram/route.ts
604
740
  var getInstagramRoute = ({
605
741
  redirectURL,
606
742
  errorRedirectURL,
607
- onUser
743
+ onUser,
744
+ scopes
608
745
  }) => {
609
746
  const handleError2 = (message) => NextResponse7.redirect(`${errorRedirectURL}?error=${message}`);
610
747
  return async (req) => {
@@ -614,7 +751,10 @@ var getInstagramRoute = ({
614
751
  if (error) return handleError2(error);
615
752
  if (!accessCode) {
616
753
  if (!stateToken) return handleError2("Invalid request");
617
- return NextResponse7.redirect(getInstagramAuthorizationURL(stateToken), 302);
754
+ return NextResponse7.redirect(
755
+ getInstagramAuthorizationURL(stateToken, scopes),
756
+ 302
757
+ );
618
758
  }
619
759
  if (!stateToken) return handleError2("Invalid request");
620
760
  const instagramData = await getLongLivedToken(
@@ -136,40 +136,177 @@ async function getLongLivedToken(code, redirectURL, clientId, clientSecret) {
136
136
  }
137
137
  }
138
138
  }
139
- var getInstagramAuthorizationURL = (token) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
139
+ var getInstagramAuthorizationURL = (token, scopes = ["instagram_business_basic"]) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
140
140
  "INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */
141
- )}&response_type=code&enable_fb_login=0&force_authentication=1&scope=instagram_business_basic&state=${token}&redirect_uri=${getEnv("NEXT_PUBLIC_INSTAGRAM_AUTH_ENDPOINT" /* NEXT_PUBLIC_INSTAGRAM_AUTH_ENDPOINT */)}#weblink`;
141
+ )}&response_type=code&enable_fb_login=0&force_authentication=1&scope=${scopes.join(",")}&state=${token}&redirect_uri=${getEnv("NEXT_PUBLIC_INSTAGRAM_AUTH_ENDPOINT" /* NEXT_PUBLIC_INSTAGRAM_AUTH_ENDPOINT */)}#weblink`;
142
142
 
143
- // src/socials/instagram/utils.ts
144
- function getInstagramURL(token, path, params) {
145
- return `https://graph.instagram.com/v23.0/${path}?${Object.keys(params).map((key) => `${key}=${params[key]}`).join("&")}&access_token=${token}`;
146
- }
147
- function getInstagramData(token, path, params = {}, postData) {
148
- return fetch(getInstagramURL(token, path, params), {
149
- method: postData ? "POST" : "GET",
150
- body: JSON.stringify(postData),
151
- headers: {
152
- Authorization: `Bearer ${token}`,
153
- "Content-Type": "application/json"
143
+ // src/socials/meta/request.ts
144
+ function createGraphClient(baseURL) {
145
+ const buildURL = (token, path, params = {}) => {
146
+ const search = new URLSearchParams();
147
+ for (const [key, value] of Object.entries(params)) {
148
+ if (value !== void 0) search.set(key, String(value));
154
149
  }
155
- }).then((res) => res.json());
150
+ search.set("access_token", token);
151
+ return `${baseURL}/${path}?${search.toString()}`;
152
+ };
153
+ const request = async (token, path, options = {}) => {
154
+ const response = await fetch(buildURL(token, path, options.params), {
155
+ method: options.method || (options.body ? "POST" : "GET"),
156
+ headers: {
157
+ Authorization: `Bearer ${token}`,
158
+ ...options.body ? { "Content-Type": "application/json" } : {}
159
+ },
160
+ ...options.body ? { body: JSON.stringify(options.body) } : {}
161
+ });
162
+ return response.json().catch(() => null);
163
+ };
164
+ return { buildURL, request };
156
165
  }
166
+ function readGraphID(label, result) {
167
+ if (result?.error) {
168
+ console.error(`[naystack] ${label} failed: ${result.error.message}`);
169
+ return null;
170
+ }
171
+ return result?.id || null;
172
+ }
173
+
174
+ // src/socials/instagram/utils.ts
175
+ var client = createGraphClient("https://graph.instagram.com/v23.0");
176
+ var getInstagramData = client.request;
157
177
 
158
178
  // src/socials/instagram/getters.ts
159
179
  var getInstagramUser = (token, id, fields) => {
160
180
  return getInstagramData(token, id || "me", {
161
- fields: fields ? fields.join(",") : "username,followers_count,media_count"
181
+ params: {
182
+ fields: fields ? fields.join(",") : "username,followers_count,media_count"
183
+ }
162
184
  });
163
185
  };
186
+ var getInstagramContainerStatus = async (token, id) => {
187
+ const result = await getInstagramData(token, id, { params: { fields: "status_code,status" } });
188
+ if (!result?.status_code) return null;
189
+ return {
190
+ status: result.status_code,
191
+ error: result.status_code === "ERROR" ? result.status : void 0
192
+ };
193
+ };
164
194
 
165
- // src/socials/meta-webhook.ts
195
+ // src/socials/meta/container.ts
196
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
197
+ async function waitForContainer(getState, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
198
+ const deadline = Date.now() + timeoutMS;
199
+ let state = await getState();
200
+ if (!state) {
201
+ await sleep(2e3);
202
+ return null;
203
+ }
204
+ while (state?.status === "IN_PROGRESS" && Date.now() < deadline) {
205
+ await sleep(intervalMS);
206
+ state = await getState();
207
+ }
208
+ return state;
209
+ }
210
+ function createPublisher(platform) {
211
+ const isReady = async (token, id, label, wait) => {
212
+ const state = await waitForContainer(
213
+ () => platform.getStatus(token, id),
214
+ wait
215
+ );
216
+ if (state && state.status !== "FINISHED" && state.status !== "PUBLISHED") {
217
+ console.error(
218
+ `[naystack] ${platform.name} ${label} ${id} is ${state.status}${state.error ? `: ${state.error}` : ""}`
219
+ );
220
+ return false;
221
+ }
222
+ return true;
223
+ };
224
+ return {
225
+ /** Creates a container, waits for it to finish processing, then publishes it. */
226
+ publish: async (token, params, wait) => {
227
+ const containerID = await platform.createContainer(token, params);
228
+ if (!containerID) return null;
229
+ if (!await isReady(token, containerID, "container", wait)) return null;
230
+ return platform.publish(token, containerID);
231
+ },
232
+ /** Creates and awaits every carousel child, resolving to their ids — or `null` if any failed. */
233
+ createChildren: async (token, items, wait) => {
234
+ const children = [];
235
+ for (const item of items) {
236
+ const childID = await platform.createContainer(token, {
237
+ ...item,
238
+ is_carousel_item: true
239
+ });
240
+ if (!childID) return null;
241
+ if (!await isReady(token, childID, "carousel item", wait))
242
+ return null;
243
+ children.push(childID);
244
+ }
245
+ return children;
246
+ }
247
+ };
248
+ }
249
+
250
+ // src/socials/instagram/setters.ts
251
+ var publisher = createPublisher({
252
+ name: "Instagram",
253
+ getStatus: getInstagramContainerStatus,
254
+ createContainer: async (token, params) => readGraphID(
255
+ "Instagram media container",
256
+ await getInstagramData(token, "me/media", {
257
+ params,
258
+ method: "POST"
259
+ })
260
+ ),
261
+ publish: async (token, creationID) => readGraphID(
262
+ "Instagram publish",
263
+ await getInstagramData(token, "me/media_publish", {
264
+ params: { creation_id: creationID },
265
+ method: "POST"
266
+ })
267
+ )
268
+ });
269
+
270
+ // src/socials/meta/webhook.ts
166
271
  var import_server2 = require("next/server");
167
272
 
273
+ // src/socials/threads/utils.ts
274
+ var client2 = createGraphClient("https://graph.threads.net/v1.0");
275
+ var getThreadsData = client2.request;
276
+
277
+ // src/socials/threads/getters.ts
278
+ var getThreadsContainerStatus = async (token, id) => {
279
+ const result = await getThreadsData(token, id, { params: { fields: "status,error_message" } });
280
+ if (!result?.status) return null;
281
+ return { status: result.status, error: result.error_message };
282
+ };
283
+
284
+ // src/socials/threads/setters.ts
285
+ var publisher2 = createPublisher({
286
+ name: "Threads",
287
+ getStatus: getThreadsContainerStatus,
288
+ createContainer: async (token, params) => readGraphID(
289
+ "Threads container",
290
+ await getThreadsData(token, "me/threads", {
291
+ params,
292
+ method: "POST"
293
+ })
294
+ ),
295
+ publish: async (token, creationID) => readGraphID(
296
+ "Threads publish",
297
+ await getThreadsData(token, "me/threads_publish", {
298
+ params: { creation_id: creationID },
299
+ method: "POST"
300
+ })
301
+ )
302
+ });
303
+
168
304
  // src/auth/instagram/route.ts
169
305
  var getInstagramRoute = ({
170
306
  redirectURL,
171
307
  errorRedirectURL,
172
- onUser
308
+ onUser,
309
+ scopes
173
310
  }) => {
174
311
  const handleError = (message) => import_server3.NextResponse.redirect(`${errorRedirectURL}?error=${message}`);
175
312
  return async (req) => {
@@ -179,7 +316,10 @@ var getInstagramRoute = ({
179
316
  if (error) return handleError(error);
180
317
  if (!accessCode) {
181
318
  if (!stateToken) return handleError("Invalid request");
182
- return import_server3.NextResponse.redirect(getInstagramAuthorizationURL(stateToken), 302);
319
+ return import_server3.NextResponse.redirect(
320
+ getInstagramAuthorizationURL(stateToken, scopes),
321
+ 302
322
+ );
183
323
  }
184
324
  if (!stateToken) return handleError("Invalid request");
185
325
  const instagramData = await getLongLivedToken(
@@ -1,6 +1,9 @@
1
1
  import * as next_server from 'next/server';
2
+ import { InstagramScope } from './utils.mjs';
2
3
  export { getInstagramAuthorizationURL, getRefreshedInstagramAccessToken } from './utils.mjs';
3
4
  import { InstagramUser } from '../../socials/instagram/types.mjs';
5
+ import '../../socials/meta/container.mjs';
6
+ import '../../socials/meta/types.mjs';
4
7
 
5
8
  /**
6
9
  * Options for initializing Instagram OAuth via {@link setupInstagramAuth}.
@@ -8,6 +11,7 @@ import { InstagramUser } from '../../socials/instagram/types.mjs';
8
11
  * @property onUser - Called with `(instagramUser, appUserId, accessToken)` after successful OAuth. Return a string to show as error (redirects to `errorRedirectURL`); return `void` on success.
9
12
  * @property redirectURL - Where to redirect after successful Instagram auth.
10
13
  * @property errorRedirectURL - Where to redirect on error (with `?error=` query param).
14
+ * @property scopes - Permissions to request. Default: `["instagram_business_basic"]`. Add `"instagram_business_content_publish"` to publish on the user's behalf.
11
15
  *
12
16
  * @category Auth
13
17
  */
@@ -15,6 +19,7 @@ interface SetupInstagramAuthOptions {
15
19
  onUser: (data: InstagramUser, id: number | null, accessToken: string) => Promise<string | void>;
16
20
  redirectURL: string;
17
21
  errorRedirectURL: string;
22
+ scopes?: InstagramScope[];
18
23
  }
19
24
  /**
20
25
  * Initializes Instagram OAuth. Returns a GET handler for the OAuth callback and a helper to refresh long-lived tokens.
@@ -50,4 +55,4 @@ declare function setupInstagramAuth(props: SetupInstagramAuthOptions): {
50
55
  GET: (req: next_server.NextRequest) => Promise<next_server.NextResponse<unknown>>;
51
56
  };
52
57
 
53
- export { type SetupInstagramAuthOptions, setupInstagramAuth };
58
+ export { InstagramScope, type SetupInstagramAuthOptions, setupInstagramAuth };
@@ -1,6 +1,9 @@
1
1
  import * as next_server from 'next/server';
2
+ import { InstagramScope } from './utils.js';
2
3
  export { getInstagramAuthorizationURL, getRefreshedInstagramAccessToken } from './utils.js';
3
4
  import { InstagramUser } from '../../socials/instagram/types.js';
5
+ import '../../socials/meta/container.js';
6
+ import '../../socials/meta/types.js';
4
7
 
5
8
  /**
6
9
  * Options for initializing Instagram OAuth via {@link setupInstagramAuth}.
@@ -8,6 +11,7 @@ import { InstagramUser } from '../../socials/instagram/types.js';
8
11
  * @property onUser - Called with `(instagramUser, appUserId, accessToken)` after successful OAuth. Return a string to show as error (redirects to `errorRedirectURL`); return `void` on success.
9
12
  * @property redirectURL - Where to redirect after successful Instagram auth.
10
13
  * @property errorRedirectURL - Where to redirect on error (with `?error=` query param).
14
+ * @property scopes - Permissions to request. Default: `["instagram_business_basic"]`. Add `"instagram_business_content_publish"` to publish on the user's behalf.
11
15
  *
12
16
  * @category Auth
13
17
  */
@@ -15,6 +19,7 @@ interface SetupInstagramAuthOptions {
15
19
  onUser: (data: InstagramUser, id: number | null, accessToken: string) => Promise<string | void>;
16
20
  redirectURL: string;
17
21
  errorRedirectURL: string;
22
+ scopes?: InstagramScope[];
18
23
  }
19
24
  /**
20
25
  * Initializes Instagram OAuth. Returns a GET handler for the OAuth callback and a helper to refresh long-lived tokens.
@@ -50,4 +55,4 @@ declare function setupInstagramAuth(props: SetupInstagramAuthOptions): {
50
55
  GET: (req: next_server.NextRequest) => Promise<next_server.NextResponse<unknown>>;
51
56
  };
52
57
 
53
- export { type SetupInstagramAuthOptions, setupInstagramAuth };
58
+ export { InstagramScope, type SetupInstagramAuthOptions, setupInstagramAuth };
@@ -108,40 +108,177 @@ async function getLongLivedToken(code, redirectURL, clientId, clientSecret) {
108
108
  }
109
109
  }
110
110
  }
111
- var getInstagramAuthorizationURL = (token) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
111
+ var getInstagramAuthorizationURL = (token, scopes = ["instagram_business_basic"]) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
112
112
  "INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */
113
- )}&response_type=code&enable_fb_login=0&force_authentication=1&scope=instagram_business_basic&state=${token}&redirect_uri=${getEnv("NEXT_PUBLIC_INSTAGRAM_AUTH_ENDPOINT" /* NEXT_PUBLIC_INSTAGRAM_AUTH_ENDPOINT */)}#weblink`;
113
+ )}&response_type=code&enable_fb_login=0&force_authentication=1&scope=${scopes.join(",")}&state=${token}&redirect_uri=${getEnv("NEXT_PUBLIC_INSTAGRAM_AUTH_ENDPOINT" /* NEXT_PUBLIC_INSTAGRAM_AUTH_ENDPOINT */)}#weblink`;
114
114
 
115
- // src/socials/instagram/utils.ts
116
- function getInstagramURL(token, path, params) {
117
- return `https://graph.instagram.com/v23.0/${path}?${Object.keys(params).map((key) => `${key}=${params[key]}`).join("&")}&access_token=${token}`;
118
- }
119
- function getInstagramData(token, path, params = {}, postData) {
120
- return fetch(getInstagramURL(token, path, params), {
121
- method: postData ? "POST" : "GET",
122
- body: JSON.stringify(postData),
123
- headers: {
124
- Authorization: `Bearer ${token}`,
125
- "Content-Type": "application/json"
115
+ // src/socials/meta/request.ts
116
+ function createGraphClient(baseURL) {
117
+ const buildURL = (token, path, params = {}) => {
118
+ const search = new URLSearchParams();
119
+ for (const [key, value] of Object.entries(params)) {
120
+ if (value !== void 0) search.set(key, String(value));
126
121
  }
127
- }).then((res) => res.json());
122
+ search.set("access_token", token);
123
+ return `${baseURL}/${path}?${search.toString()}`;
124
+ };
125
+ const request = async (token, path, options = {}) => {
126
+ const response = await fetch(buildURL(token, path, options.params), {
127
+ method: options.method || (options.body ? "POST" : "GET"),
128
+ headers: {
129
+ Authorization: `Bearer ${token}`,
130
+ ...options.body ? { "Content-Type": "application/json" } : {}
131
+ },
132
+ ...options.body ? { body: JSON.stringify(options.body) } : {}
133
+ });
134
+ return response.json().catch(() => null);
135
+ };
136
+ return { buildURL, request };
128
137
  }
138
+ function readGraphID(label, result) {
139
+ if (result?.error) {
140
+ console.error(`[naystack] ${label} failed: ${result.error.message}`);
141
+ return null;
142
+ }
143
+ return result?.id || null;
144
+ }
145
+
146
+ // src/socials/instagram/utils.ts
147
+ var client = createGraphClient("https://graph.instagram.com/v23.0");
148
+ var getInstagramData = client.request;
129
149
 
130
150
  // src/socials/instagram/getters.ts
131
151
  var getInstagramUser = (token, id, fields) => {
132
152
  return getInstagramData(token, id || "me", {
133
- fields: fields ? fields.join(",") : "username,followers_count,media_count"
153
+ params: {
154
+ fields: fields ? fields.join(",") : "username,followers_count,media_count"
155
+ }
134
156
  });
135
157
  };
158
+ var getInstagramContainerStatus = async (token, id) => {
159
+ const result = await getInstagramData(token, id, { params: { fields: "status_code,status" } });
160
+ if (!result?.status_code) return null;
161
+ return {
162
+ status: result.status_code,
163
+ error: result.status_code === "ERROR" ? result.status : void 0
164
+ };
165
+ };
136
166
 
137
- // src/socials/meta-webhook.ts
167
+ // src/socials/meta/container.ts
168
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
169
+ async function waitForContainer(getState, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
170
+ const deadline = Date.now() + timeoutMS;
171
+ let state = await getState();
172
+ if (!state) {
173
+ await sleep(2e3);
174
+ return null;
175
+ }
176
+ while (state?.status === "IN_PROGRESS" && Date.now() < deadline) {
177
+ await sleep(intervalMS);
178
+ state = await getState();
179
+ }
180
+ return state;
181
+ }
182
+ function createPublisher(platform) {
183
+ const isReady = async (token, id, label, wait) => {
184
+ const state = await waitForContainer(
185
+ () => platform.getStatus(token, id),
186
+ wait
187
+ );
188
+ if (state && state.status !== "FINISHED" && state.status !== "PUBLISHED") {
189
+ console.error(
190
+ `[naystack] ${platform.name} ${label} ${id} is ${state.status}${state.error ? `: ${state.error}` : ""}`
191
+ );
192
+ return false;
193
+ }
194
+ return true;
195
+ };
196
+ return {
197
+ /** Creates a container, waits for it to finish processing, then publishes it. */
198
+ publish: async (token, params, wait) => {
199
+ const containerID = await platform.createContainer(token, params);
200
+ if (!containerID) return null;
201
+ if (!await isReady(token, containerID, "container", wait)) return null;
202
+ return platform.publish(token, containerID);
203
+ },
204
+ /** Creates and awaits every carousel child, resolving to their ids — or `null` if any failed. */
205
+ createChildren: async (token, items, wait) => {
206
+ const children = [];
207
+ for (const item of items) {
208
+ const childID = await platform.createContainer(token, {
209
+ ...item,
210
+ is_carousel_item: true
211
+ });
212
+ if (!childID) return null;
213
+ if (!await isReady(token, childID, "carousel item", wait))
214
+ return null;
215
+ children.push(childID);
216
+ }
217
+ return children;
218
+ }
219
+ };
220
+ }
221
+
222
+ // src/socials/instagram/setters.ts
223
+ var publisher = createPublisher({
224
+ name: "Instagram",
225
+ getStatus: getInstagramContainerStatus,
226
+ createContainer: async (token, params) => readGraphID(
227
+ "Instagram media container",
228
+ await getInstagramData(token, "me/media", {
229
+ params,
230
+ method: "POST"
231
+ })
232
+ ),
233
+ publish: async (token, creationID) => readGraphID(
234
+ "Instagram publish",
235
+ await getInstagramData(token, "me/media_publish", {
236
+ params: { creation_id: creationID },
237
+ method: "POST"
238
+ })
239
+ )
240
+ });
241
+
242
+ // src/socials/meta/webhook.ts
138
243
  import { NextResponse as NextResponse2 } from "next/server";
139
244
 
245
+ // src/socials/threads/utils.ts
246
+ var client2 = createGraphClient("https://graph.threads.net/v1.0");
247
+ var getThreadsData = client2.request;
248
+
249
+ // src/socials/threads/getters.ts
250
+ var getThreadsContainerStatus = async (token, id) => {
251
+ const result = await getThreadsData(token, id, { params: { fields: "status,error_message" } });
252
+ if (!result?.status) return null;
253
+ return { status: result.status, error: result.error_message };
254
+ };
255
+
256
+ // src/socials/threads/setters.ts
257
+ var publisher2 = createPublisher({
258
+ name: "Threads",
259
+ getStatus: getThreadsContainerStatus,
260
+ createContainer: async (token, params) => readGraphID(
261
+ "Threads container",
262
+ await getThreadsData(token, "me/threads", {
263
+ params,
264
+ method: "POST"
265
+ })
266
+ ),
267
+ publish: async (token, creationID) => readGraphID(
268
+ "Threads publish",
269
+ await getThreadsData(token, "me/threads_publish", {
270
+ params: { creation_id: creationID },
271
+ method: "POST"
272
+ })
273
+ )
274
+ });
275
+
140
276
  // src/auth/instagram/route.ts
141
277
  var getInstagramRoute = ({
142
278
  redirectURL,
143
279
  errorRedirectURL,
144
- onUser
280
+ onUser,
281
+ scopes
145
282
  }) => {
146
283
  const handleError = (message) => NextResponse3.redirect(`${errorRedirectURL}?error=${message}`);
147
284
  return async (req) => {
@@ -151,7 +288,10 @@ var getInstagramRoute = ({
151
288
  if (error) return handleError(error);
152
289
  if (!accessCode) {
153
290
  if (!stateToken) return handleError("Invalid request");
154
- return NextResponse3.redirect(getInstagramAuthorizationURL(stateToken), 302);
291
+ return NextResponse3.redirect(
292
+ getInstagramAuthorizationURL(stateToken, scopes),
293
+ 302
294
+ );
155
295
  }
156
296
  if (!stateToken) return handleError("Invalid request");
157
297
  const instagramData = await getLongLivedToken(