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.
- package/README.md +189 -31
- package/dist/auth/index.cjs.js +158 -18
- package/dist/auth/index.d.mts +3 -1
- package/dist/auth/index.d.ts +3 -1
- package/dist/auth/index.esm.js +158 -18
- package/dist/auth/instagram/index.cjs.js +158 -18
- package/dist/auth/instagram/index.d.mts +6 -1
- package/dist/auth/instagram/index.d.ts +6 -1
- package/dist/auth/instagram/index.esm.js +158 -18
- package/dist/auth/instagram/route.cjs.js +158 -18
- package/dist/auth/instagram/route.d.mts +3 -1
- package/dist/auth/instagram/route.d.ts +3 -1
- package/dist/auth/instagram/route.esm.js +158 -18
- package/dist/auth/instagram/utils.cjs.js +2 -2
- package/dist/auth/instagram/utils.d.mts +13 -2
- package/dist/auth/instagram/utils.d.ts +13 -2
- package/dist/auth/instagram/utils.esm.js +2 -2
- package/dist/file/setup.d.mts +13 -5
- package/dist/file/setup.d.ts +13 -5
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/socials/index.cjs.js +298 -72
- package/dist/socials/index.d.mts +6 -4
- package/dist/socials/index.d.ts +6 -4
- package/dist/socials/index.esm.js +296 -72
- package/dist/socials/instagram/getters.cjs.js +76 -25
- package/dist/socials/instagram/getters.d.mts +56 -6
- package/dist/socials/instagram/getters.d.ts +56 -6
- package/dist/socials/instagram/getters.esm.js +74 -25
- package/dist/socials/instagram/setters.cjs.js +192 -14
- package/dist/socials/instagram/setters.d.mts +43 -3
- package/dist/socials/instagram/setters.d.ts +43 -3
- package/dist/socials/instagram/setters.esm.js +191 -14
- package/dist/socials/instagram/types.d.mts +48 -11
- package/dist/socials/instagram/types.d.ts +48 -11
- package/dist/socials/instagram/utils.cjs.js +28 -15
- package/dist/socials/instagram/utils.d.mts +7 -18
- package/dist/socials/instagram/utils.d.ts +7 -18
- package/dist/socials/instagram/utils.esm.js +26 -14
- package/dist/socials/instagram/webhook.cjs.js +1 -1
- package/dist/socials/instagram/webhook.esm.js +1 -1
- package/dist/socials/meta/container.cjs.js +82 -0
- package/dist/socials/meta/container.d.mts +38 -0
- package/dist/socials/meta/container.d.ts +38 -0
- package/dist/socials/meta/container.esm.js +57 -0
- package/dist/socials/meta/request.cjs.js +60 -0
- package/dist/socials/meta/request.d.mts +50 -0
- package/dist/socials/meta/request.d.ts +50 -0
- package/dist/socials/meta/request.esm.js +34 -0
- package/dist/socials/meta/types.cjs.js +18 -0
- package/dist/socials/meta/types.d.mts +47 -0
- package/dist/socials/meta/types.d.ts +47 -0
- package/dist/socials/meta/types.esm.js +0 -0
- package/dist/socials/{meta-webhook.cjs.js → meta/webhook.cjs.js} +4 -4
- package/dist/socials/{meta-webhook.esm.js → meta/webhook.esm.js} +1 -1
- package/dist/socials/threads/getters.cjs.js +36 -11
- package/dist/socials/threads/getters.d.mts +23 -2
- package/dist/socials/threads/getters.d.ts +23 -2
- package/dist/socials/threads/getters.esm.js +35 -11
- package/dist/socials/threads/setters.cjs.js +150 -44
- package/dist/socials/threads/setters.d.mts +25 -34
- package/dist/socials/threads/setters.d.ts +25 -34
- package/dist/socials/threads/setters.esm.js +149 -41
- package/dist/socials/threads/types.d.mts +40 -1
- package/dist/socials/threads/types.d.ts +40 -1
- package/dist/socials/threads/utils.cjs.js +29 -11
- package/dist/socials/threads/utils.d.mts +8 -17
- package/dist/socials/threads/utils.d.ts +8 -17
- package/dist/socials/threads/utils.esm.js +27 -10
- package/dist/socials/threads/webhook.cjs.js +1 -1
- package/dist/socials/threads/webhook.esm.js +1 -1
- package/package.json +1 -1
- /package/dist/socials/{meta-webhook.d.mts → meta/webhook.d.mts} +0 -0
- /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: {
|
|
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({
|
|
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: {
|
|
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
|
|
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
|
|
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,
|
|
278
|
-
outputOptions: { nullable: true },
|
|
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,
|
|
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,
|
|
301
|
-
authorized: true,
|
|
302
|
-
mutation: true,
|
|
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: [
|
|
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);
|
|
382
|
-
if (!ctx.userId)
|
|
383
|
-
if (!deal)
|
|
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,
|
|
460
|
-
tags: ["user"],
|
|
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({
|
|
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
|
|
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 {
|
|
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({
|
|
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 }) =>
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
720
|
-
|
|
721
|
-
|
|
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
|
-
//
|
|
724
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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!,
|
package/dist/auth/index.cjs.js
CHANGED
|
@@ -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
|
|
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/
|
|
621
|
-
function
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
package/dist/auth/index.d.mts
CHANGED
|
@@ -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';
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -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';
|