naystack 1.8.12 → 1.8.13

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 +18 -0
  51. package/dist/socials/meta/types.d.mts +47 -0
  52. package/dist/socials/meta/types.d.ts +47 -0
  53. package/dist/socials/meta/types.esm.js +0 -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
package/README.md CHANGED
@@ -39,7 +39,11 @@ export const { GET, POST, PUT, DELETE } = setupEmailAuth({
39
39
  return user;
40
40
  },
41
41
  // Create a new user with the hashed password
42
- createUser: async (data: { email: string; password: string; name: string }) => {
42
+ createUser: async (data: {
43
+ email: string;
44
+ password: string;
45
+ name: string;
46
+ }) => {
43
47
  const [user] = await db
44
48
  .insert(UserTable)
45
49
  .values(data)
@@ -73,7 +77,11 @@ Wrap your application with `AuthWrapper` in your root layout. This fetches the a
73
77
  import { AuthWrapper } from "naystack/auth/client";
74
78
  import { ApolloWrapper } from "naystack/graphql/client";
75
79
 
76
- export default function RootLayout({ children }: { children: React.ReactNode }) {
80
+ export default function RootLayout({
81
+ children,
82
+ }: {
83
+ children: React.ReactNode;
84
+ }) {
77
85
  return (
78
86
  <html lang="en">
79
87
  <body>
@@ -116,7 +124,11 @@ import { useSignUp } from "naystack/auth/client";
116
124
  function SignUpForm() {
117
125
  const signUp = useSignUp();
118
126
 
119
- const handleSubmit = async (data: { name: string; email: string; password: string }) => {
127
+ const handleSubmit = async (data: {
128
+ name: string;
129
+ email: string;
130
+ password: string;
131
+ }) => {
120
132
  const error = await signUp(data);
121
133
  if (error) {
122
134
  setMessage(error);
@@ -159,7 +171,12 @@ function LogoutButton() {
159
171
  const logout = useLogout();
160
172
 
161
173
  return (
162
- <button onClick={() => { logout(); router.push("/login"); }}>
174
+ <button
175
+ onClick={() => {
176
+ logout();
177
+ router.push("/login");
178
+ }}
179
+ >
163
180
  Log out
164
181
  </button>
165
182
  );
@@ -180,7 +197,10 @@ export const POST = async (req: NextRequest) => {
180
197
  if (!ctx?.userId) return new NextResponse("Unauthorized", { status: 401 });
181
198
 
182
199
  // ctx.userId is available for authenticated operations
183
- const chats = await db.select().from(ChatTable).where(eq(ChatTable.userId, ctx.userId));
200
+ const chats = await db
201
+ .select()
202
+ .from(ChatTable)
203
+ .where(eq(ChatTable.userId, ctx.userId));
184
204
  return NextResponse.json(chats);
185
205
  };
186
206
  ```
@@ -237,9 +257,14 @@ export const { GET } = setupInstagramAuth({
237
257
  },
238
258
  redirectURL: "/dashboard",
239
259
  errorRedirectURL: "/login",
260
+ // Default: ["instagram_business_basic"]. Publishing needs the extra scope.
261
+ scopes: ["instagram_business_basic", "instagram_business_content_publish"],
240
262
  });
241
263
  ```
242
264
 
265
+ Scopes apply to users who authorize afterwards — already-stored tokens keep the
266
+ permissions they were granted, so widening `scopes` means existing users must reconnect.
267
+
243
268
  #### `getRefreshedInstagramAccessToken(token)`
244
269
 
245
270
  Refreshes a long-lived Instagram access token. Exported separately from `naystack/auth`.
@@ -274,8 +299,8 @@ export default resolver(
274
299
  return user || null;
275
300
  },
276
301
  {
277
- output: User, // GraphQL return type (type-graphql class)
278
- outputOptions: { nullable: true }, // Return type is nullable
302
+ output: User, // GraphQL return type (type-graphql class)
303
+ outputOptions: { nullable: true }, // Return type is nullable
279
304
  },
280
305
  );
281
306
  ```
@@ -289,7 +314,7 @@ import { resolver } from "naystack/graphql";
289
314
  export default resolver(
290
315
  async (ctx, input: SubmitFeedbackInput) => {
291
316
  await db.insert(FeedbackTable).values({
292
- userId: ctx.userId, // guaranteed non-null when authorized: true
317
+ userId: ctx.userId, // guaranteed non-null when authorized: true
293
318
  score: input.score,
294
319
  text: input.text,
295
320
  });
@@ -297,9 +322,9 @@ export default resolver(
297
322
  },
298
323
  {
299
324
  output: Boolean,
300
- input: SubmitFeedbackInput, // GraphQL input type (type-graphql @InputType class)
301
- authorized: true, // Requires authenticated user (ctx.userId non-null)
302
- mutation: true, // Registers as a Mutation (default is Query)
325
+ input: SubmitFeedbackInput, // GraphQL input type (type-graphql @InputType class)
326
+ authorized: true, // Requires authenticated user (ctx.userId non-null)
327
+ mutation: true, // Registers as a Mutation (default is Query)
303
328
  },
304
329
  );
305
330
  ```
@@ -364,7 +389,12 @@ import { ChatResolvers } from "./Chat/graphql";
364
389
  import { FeedbackResolvers } from "./Feedback/graphql";
365
390
 
366
391
  export const { GET, POST } = await setupGraphQL({
367
- resolvers: [UserResolvers, UserFieldResolvers, ChatResolvers, FeedbackResolvers],
392
+ resolvers: [
393
+ UserResolvers,
394
+ UserFieldResolvers,
395
+ ChatResolvers,
396
+ FeedbackResolvers,
397
+ ],
368
398
  });
369
399
  ```
370
400
 
@@ -378,9 +408,9 @@ Use `GQLError()` to throw structured GraphQL errors from resolvers:
378
408
  import { GQLError } from "naystack/graphql";
379
409
 
380
410
  // In a resolver:
381
- if (!input.email) throw GQLError(400); // "Please provide all required inputs"
382
- if (!ctx.userId) throw GQLError(403); // "You are not allowed to perform this action"
383
- if (!deal) throw GQLError(404, "Deal not found"); // Custom message
411
+ if (!input.email) throw GQLError(400); // "Please provide all required inputs"
412
+ if (!ctx.userId) throw GQLError(403); // "You are not allowed to perform this action"
413
+ if (!deal) throw GQLError(404, "Deal not found"); // Custom message
384
414
  ```
385
415
 
386
416
  ### Type Helpers: `QueryResponseType` / `FieldResponseType`
@@ -456,8 +486,8 @@ import { query } from "naystack/graphql/server";
456
486
 
457
487
  const data = await query(GetUserDocument, {
458
488
  variables: { id: userId },
459
- revalidate: 60, // Cache for 60s (Next.js ISR)
460
- tags: ["user"], // For on-demand revalidation
489
+ revalidate: 60, // Cache for 60s (Next.js ISR)
490
+ tags: ["user"], // For on-demand revalidation
461
491
  });
462
492
  ```
463
493
 
@@ -470,7 +500,11 @@ Wrap your app with `ApolloWrapper` (inside `AuthWrapper`) so client components c
470
500
  import { AuthWrapper } from "naystack/auth/client";
471
501
  import { ApolloWrapper } from "naystack/graphql/client";
472
502
 
473
- export default function RootLayout({ children }: { children: React.ReactNode }) {
503
+ export default function RootLayout({
504
+ children,
505
+ }: {
506
+ children: React.ReactNode;
507
+ }) {
474
508
  return (
475
509
  <html lang="en">
476
510
  <body>
@@ -503,7 +537,11 @@ function SummaryCard({ type }: { type: string }) {
503
537
  }
504
538
  };
505
539
 
506
- return <button onClick={handleFetch} disabled={loading}>Get Summary</button>;
540
+ return (
541
+ <button onClick={handleFetch} disabled={loading}>
542
+ Get Summary
543
+ </button>
544
+ );
507
545
  }
508
546
  ```
509
547
 
@@ -548,17 +586,27 @@ export const { PUT } = setupFileUpload({
548
586
  // Called after each successful upload. Return value is sent in the response as `onUploadResponse`.
549
587
  onUpload: async ({ url, type, userId, data }) => {
550
588
  if (type === "DealDocument" && url) {
551
- const payload = data as { dealId: number; fileName: string; category: string };
589
+ const payload = data as {
590
+ dealId: number;
591
+ fileName: string;
592
+ category: string;
593
+ };
552
594
  const [row] = await db
553
595
  .insert(DealDocumentsTable)
554
- .values({ dealId: payload.dealId, fileURL: url, fileName: payload.fileName, category: payload.category })
596
+ .values({
597
+ dealId: payload.dealId,
598
+ fileURL: url,
599
+ fileName: payload.fileName,
600
+ category: payload.category,
601
+ })
555
602
  .returning();
556
603
  return row ?? {};
557
604
  }
558
605
  return {};
559
606
  },
560
607
  // Optional: customize the S3 key (defaults to UUID)
561
- getKey: async ({ type, userId }) => `${type}/${userId}/${crypto.randomUUID()}`,
608
+ getKey: async ({ type, userId }) =>
609
+ `${type}/${userId}/${crypto.randomUUID()}`,
562
610
  });
563
611
  ```
564
612
 
@@ -593,7 +641,12 @@ function FileUploader({ dealId }: { dealId: number }) {
593
641
  }
594
642
  };
595
643
 
596
- return <input type="file" onChange={(e) => e.target.files?.[0] && handleUpload(e.target.files[0])} />;
644
+ return (
645
+ <input
646
+ type="file"
647
+ onChange={(e) => e.target.files?.[0] && handleUpload(e.target.files[0])}
648
+ />
649
+ );
597
650
  }
598
651
  ```
599
652
 
@@ -652,10 +705,93 @@ function ResponsiveNav() {
652
705
 
653
706
  ## 5. Social APIs
654
707
 
655
- Simplified access to Instagram Graph API and Threads API.
708
+ Publish to and read from Instagram and Threads.
709
+
710
+ Each platform exposes **one publishing method** that picks the right media type from
711
+ what you pass. Meta's container/upload/publish dance underneath is an implementation
712
+ detail — you never touch it.
713
+
714
+ | Path | What it is |
715
+ | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
716
+ | `socials/meta/` | Meta's Graph protocol — request client, container polling, webhook verification. Shared by Instagram and Threads only |
717
+ | `socials/instagram/`, `socials/threads/` | One folder per platform: getters, setters, types |
718
+
719
+ A platform that isn't on Meta's Graph API (X, LinkedIn) brings its own client and
720
+ exposes its own `create<Platform>Post`; it doesn't touch `socials/meta/`.
656
721
 
657
722
  ### Instagram
658
723
 
724
+ `createInstagramPost(token, input)` — feed image, reel, story or carousel:
725
+
726
+ | `media` | Result |
727
+ | ----------------------- | ----------------------------- |
728
+ | one image | feed image |
729
+ | one video | reel |
730
+ | 2–10 items | carousel (counts as one post) |
731
+ | any, with `story: true` | story, from the first item |
732
+
733
+ ```typescript
734
+ import { createInstagramPost } from "naystack/socials";
735
+
736
+ // Feed image — JPEG only
737
+ await createInstagramPost(accessToken, {
738
+ media: { url: "https://cdn.example.com/launch.jpg", type: "image" },
739
+ caption: "New campaign is live 🎉",
740
+ });
741
+
742
+ // Reel — 9:16, 5–90s, H.264/HEVC to reach the Reels tab
743
+ await createInstagramPost(accessToken, {
744
+ media: { url: "https://cdn.example.com/promo.mp4", type: "video" },
745
+ caption: "Behind the scenes",
746
+ shareToFeed: true,
747
+ thumbOffset: 1500, // or coverURL
748
+ });
749
+
750
+ // Story — gone in 24h
751
+ await createInstagramPost(accessToken, {
752
+ media: { url: "https://cdn.example.com/story.mp4", type: "video" },
753
+ story: true,
754
+ });
755
+
756
+ // Carousel
757
+ await createInstagramPost(accessToken, {
758
+ caption: "Campaign recap",
759
+ media: [
760
+ { url: "https://cdn.example.com/1.jpg", type: "image" },
761
+ { url: "https://cdn.example.com/2.mp4", type: "video" },
762
+ ],
763
+ });
764
+ ```
765
+
766
+ `canPublishToInstagram(token)` answers whether a token is allowed to publish. Meta
767
+ exposes no scope-listing endpoint for Instagram Login tokens, so it probes the
768
+ publishing quota — a read-only endpoint needing exactly the same
769
+ `instagram_business_content_publish` scope. Nothing is posted:
770
+
771
+ ```typescript
772
+ import { canPublishToInstagram } from "naystack/socials";
773
+
774
+ if (!(await canPublishToInstagram(process.env.INSTAGRAM_ACCESS_TOKEN!))) {
775
+ throw new Error(
776
+ "Instagram token cannot publish — reconnect with the publish scope",
777
+ );
778
+ }
779
+ ```
780
+
781
+ It rejects rather than answering `false` if the request itself fails, so a network blip
782
+ is never reported as a missing permission. For a one-off manual check, Meta's
783
+ [Access Token Debugger](https://developers.facebook.com/tools/debug/accesstoken/) shows
784
+ a token's scopes in the browser.
785
+
786
+ `createInstagramPost` returns the published media id, or `null` if any step failed — the
787
+ API's own error is logged. Media URLs must be publicly reachable (Instagram downloads them server-side)
788
+ and images must be JPEG. Publishing needs a token with the
789
+ `instagram_business_content_publish` scope and an Instagram professional account;
790
+ Instagram allows 100 published posts per rolling 24 hours. Tune container polling with
791
+ `wait: { intervalMS, timeoutMS }`.
792
+
793
+ Reading and messaging:
794
+
659
795
  ```typescript
660
796
  import {
661
797
  getInstagramUser,
@@ -706,6 +842,9 @@ export const { GET, POST } = setupInstagramWebhook({
706
842
 
707
843
  ### Threads
708
844
 
845
+ `createThreadsPost(token, input)` — no media is a text post, one item is a single
846
+ image or video, 2–20 is a carousel:
847
+
709
848
  ```typescript
710
849
  import {
711
850
  getThread,
@@ -716,20 +855,39 @@ import {
716
855
  setupThreadsWebhook,
717
856
  } from "naystack/socials";
718
857
 
719
- // Fetch user's threads
720
- const threads = await getThreads(accessToken);
721
- // => [{ text: "Hello world", permalink: "...", username: "johndoe" }]
858
+ // Text post — a bare string works
859
+ await createThreadsPost(accessToken, "Hello from Naystack!");
860
+
861
+ // Media, replies, reply controls
862
+ await createThreadsPost(accessToken, {
863
+ text: "Campaign is live",
864
+ media: { url: "https://cdn.example.com/campaign.jpg", type: "image" },
865
+ replyControl: "everyone",
866
+ });
722
867
 
723
- // Create and publish a single post
724
- const postId = await createThreadsPost(accessToken, "Hello from Naystack!");
868
+ // Carousel
869
+ await createThreadsPost(accessToken, {
870
+ text: "Campaign recap",
871
+ media: [
872
+ { url: "https://cdn.example.com/1.jpg", type: "image" },
873
+ { url: "https://cdn.example.com/2.mp4", type: "video" },
874
+ ],
875
+ });
725
876
 
726
- // Create a thread (sequence of posts)
877
+ // A thread each post replies to the previous one
727
878
  const firstPostId = await createThread(accessToken, [
728
879
  "First post in thread",
729
880
  "Second post (reply to first)",
730
- "Third post (reply to second)",
881
+ {
882
+ text: "Third, with a picture",
883
+ media: { url: "https://cdn.example.com/3.jpg", type: "image" },
884
+ },
731
885
  ]);
732
886
 
887
+ // Fetch user's threads
888
+ const threads = await getThreads(accessToken);
889
+ // => [{ text: "Hello world", permalink: "...", username: "johndoe" }]
890
+
733
891
  // Webhook setup (app/api/webhooks/threads/route.ts)
734
892
  export const { GET, POST } = setupThreadsWebhook({
735
893
  secret: process.env.WEBHOOK_SECRET!,
@@ -613,40 +613,177 @@ async function getLongLivedToken(code, redirectURL, clientId, clientSecret) {
613
613
  }
614
614
  }
615
615
  }
616
- var getInstagramAuthorizationURL = (token) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
616
+ var getInstagramAuthorizationURL = (token, scopes = ["instagram_business_basic"]) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
617
617
  "INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */
618
- )}&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`;
618
+ )}&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`;
619
619
 
620
- // src/socials/instagram/utils.ts
621
- function getInstagramURL(token, path, params) {
622
- return `https://graph.instagram.com/v23.0/${path}?${Object.keys(params).map((key) => `${key}=${params[key]}`).join("&")}&access_token=${token}`;
623
- }
624
- function getInstagramData(token, path, params = {}, postData) {
625
- return fetch(getInstagramURL(token, path, params), {
626
- method: postData ? "POST" : "GET",
627
- body: JSON.stringify(postData),
628
- headers: {
629
- Authorization: `Bearer ${token}`,
630
- "Content-Type": "application/json"
620
+ // src/socials/meta/request.ts
621
+ function createGraphClient(baseURL) {
622
+ const buildURL = (token, path, params = {}) => {
623
+ const search = new URLSearchParams();
624
+ for (const [key, value] of Object.entries(params)) {
625
+ if (value !== void 0) search.set(key, String(value));
631
626
  }
632
- }).then((res) => res.json());
627
+ search.set("access_token", token);
628
+ return `${baseURL}/${path}?${search.toString()}`;
629
+ };
630
+ const request = async (token, path, options = {}) => {
631
+ const response = await fetch(buildURL(token, path, options.params), {
632
+ method: options.method || (options.body ? "POST" : "GET"),
633
+ headers: {
634
+ Authorization: `Bearer ${token}`,
635
+ ...options.body ? { "Content-Type": "application/json" } : {}
636
+ },
637
+ ...options.body ? { body: JSON.stringify(options.body) } : {}
638
+ });
639
+ return response.json().catch(() => null);
640
+ };
641
+ return { buildURL, request };
642
+ }
643
+ function readGraphID(label, result) {
644
+ if (result?.error) {
645
+ console.error(`[naystack] ${label} failed: ${result.error.message}`);
646
+ return null;
647
+ }
648
+ return result?.id || null;
633
649
  }
634
650
 
651
+ // src/socials/instagram/utils.ts
652
+ var client = createGraphClient("https://graph.instagram.com/v23.0");
653
+ var getInstagramData = client.request;
654
+
635
655
  // src/socials/instagram/getters.ts
636
656
  var getInstagramUser = (token, id, fields) => {
637
657
  return getInstagramData(token, id || "me", {
638
- fields: fields ? fields.join(",") : "username,followers_count,media_count"
658
+ params: {
659
+ fields: fields ? fields.join(",") : "username,followers_count,media_count"
660
+ }
639
661
  });
640
662
  };
663
+ var getInstagramContainerStatus = async (token, id) => {
664
+ const result = await getInstagramData(token, id, { params: { fields: "status_code,status" } });
665
+ if (!result?.status_code) return null;
666
+ return {
667
+ status: result.status_code,
668
+ error: result.status_code === "ERROR" ? result.status : void 0
669
+ };
670
+ };
641
671
 
642
- // src/socials/meta-webhook.ts
672
+ // src/socials/meta/container.ts
673
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
674
+ async function waitForContainer(getState, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
675
+ const deadline = Date.now() + timeoutMS;
676
+ let state = await getState();
677
+ if (!state) {
678
+ await sleep(2e3);
679
+ return null;
680
+ }
681
+ while (state?.status === "IN_PROGRESS" && Date.now() < deadline) {
682
+ await sleep(intervalMS);
683
+ state = await getState();
684
+ }
685
+ return state;
686
+ }
687
+ function createPublisher(platform) {
688
+ const isReady = async (token, id, label, wait) => {
689
+ const state = await waitForContainer(
690
+ () => platform.getStatus(token, id),
691
+ wait
692
+ );
693
+ if (state && state.status !== "FINISHED" && state.status !== "PUBLISHED") {
694
+ console.error(
695
+ `[naystack] ${platform.name} ${label} ${id} is ${state.status}${state.error ? `: ${state.error}` : ""}`
696
+ );
697
+ return false;
698
+ }
699
+ return true;
700
+ };
701
+ return {
702
+ /** Creates a container, waits for it to finish processing, then publishes it. */
703
+ publish: async (token, params, wait) => {
704
+ const containerID = await platform.createContainer(token, params);
705
+ if (!containerID) return null;
706
+ if (!await isReady(token, containerID, "container", wait)) return null;
707
+ return platform.publish(token, containerID);
708
+ },
709
+ /** Creates and awaits every carousel child, resolving to their ids — or `null` if any failed. */
710
+ createChildren: async (token, items, wait) => {
711
+ const children = [];
712
+ for (const item of items) {
713
+ const childID = await platform.createContainer(token, {
714
+ ...item,
715
+ is_carousel_item: true
716
+ });
717
+ if (!childID) return null;
718
+ if (!await isReady(token, childID, "carousel item", wait))
719
+ return null;
720
+ children.push(childID);
721
+ }
722
+ return children;
723
+ }
724
+ };
725
+ }
726
+
727
+ // src/socials/instagram/setters.ts
728
+ var publisher = createPublisher({
729
+ name: "Instagram",
730
+ getStatus: getInstagramContainerStatus,
731
+ createContainer: async (token, params) => readGraphID(
732
+ "Instagram media container",
733
+ await getInstagramData(token, "me/media", {
734
+ params,
735
+ method: "POST"
736
+ })
737
+ ),
738
+ publish: async (token, creationID) => readGraphID(
739
+ "Instagram publish",
740
+ await getInstagramData(token, "me/media_publish", {
741
+ params: { creation_id: creationID },
742
+ method: "POST"
743
+ })
744
+ )
745
+ });
746
+
747
+ // src/socials/meta/webhook.ts
643
748
  var import_server7 = require("next/server");
644
749
 
750
+ // src/socials/threads/utils.ts
751
+ var client2 = createGraphClient("https://graph.threads.net/v1.0");
752
+ var getThreadsData = client2.request;
753
+
754
+ // src/socials/threads/getters.ts
755
+ var getThreadsContainerStatus = async (token, id) => {
756
+ const result = await getThreadsData(token, id, { params: { fields: "status,error_message" } });
757
+ if (!result?.status) return null;
758
+ return { status: result.status, error: result.error_message };
759
+ };
760
+
761
+ // src/socials/threads/setters.ts
762
+ var publisher2 = createPublisher({
763
+ name: "Threads",
764
+ getStatus: getThreadsContainerStatus,
765
+ createContainer: async (token, params) => readGraphID(
766
+ "Threads container",
767
+ await getThreadsData(token, "me/threads", {
768
+ params,
769
+ method: "POST"
770
+ })
771
+ ),
772
+ publish: async (token, creationID) => readGraphID(
773
+ "Threads publish",
774
+ await getThreadsData(token, "me/threads_publish", {
775
+ params: { creation_id: creationID },
776
+ method: "POST"
777
+ })
778
+ )
779
+ });
780
+
645
781
  // src/auth/instagram/route.ts
646
782
  var getInstagramRoute = ({
647
783
  redirectURL,
648
784
  errorRedirectURL,
649
- onUser
785
+ onUser,
786
+ scopes
650
787
  }) => {
651
788
  const handleError2 = (message) => import_server8.NextResponse.redirect(`${errorRedirectURL}?error=${message}`);
652
789
  return async (req) => {
@@ -656,7 +793,10 @@ var getInstagramRoute = ({
656
793
  if (error) return handleError2(error);
657
794
  if (!accessCode) {
658
795
  if (!stateToken) return handleError2("Invalid request");
659
- return import_server8.NextResponse.redirect(getInstagramAuthorizationURL(stateToken), 302);
796
+ return import_server8.NextResponse.redirect(
797
+ getInstagramAuthorizationURL(stateToken, scopes),
798
+ 302
799
+ );
660
800
  }
661
801
  if (!stateToken) return handleError2("Invalid request");
662
802
  const instagramData = await getLongLivedToken(
@@ -5,12 +5,14 @@ export { getRefreshToken } from './utils/token.mjs';
5
5
  export { AuthFetch, AuthWrapper } from './email/next.mjs';
6
6
  export { checkAuthStatus, getTokenizedResponse } from './email/token.mjs';
7
7
  export { getContext } from './email/utils.mjs';
8
- export { getInstagramAuthorizationURL, getRefreshedInstagramAccessToken } from './instagram/utils.mjs';
8
+ export { InstagramScope, getInstagramAuthorizationURL, getRefreshedInstagramAccessToken } from './instagram/utils.mjs';
9
9
  import 'next/server';
10
10
  import './email/types.mjs';
11
11
  import './types.mjs';
12
12
  import 'googleapis';
13
13
  import '../socials/instagram/types.mjs';
14
+ import '../socials/meta/container.mjs';
15
+ import '../socials/meta/types.mjs';
14
16
  import 'react';
15
17
  import './email/client.mjs';
16
18
  import 'naystack/auth/token-store';
@@ -5,12 +5,14 @@ export { getRefreshToken } from './utils/token.js';
5
5
  export { AuthFetch, AuthWrapper } from './email/next.js';
6
6
  export { checkAuthStatus, getTokenizedResponse } from './email/token.js';
7
7
  export { getContext } from './email/utils.js';
8
- export { getInstagramAuthorizationURL, getRefreshedInstagramAccessToken } from './instagram/utils.js';
8
+ export { InstagramScope, getInstagramAuthorizationURL, getRefreshedInstagramAccessToken } from './instagram/utils.js';
9
9
  import 'next/server';
10
10
  import './email/types.js';
11
11
  import './types.js';
12
12
  import 'googleapis';
13
13
  import '../socials/instagram/types.js';
14
+ import '../socials/meta/container.js';
15
+ import '../socials/meta/types.js';
14
16
  import 'react';
15
17
  import './email/client.js';
16
18
  import 'naystack/auth/token-store';