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
@@ -125,40 +125,177 @@ async function getLongLivedToken(code, redirectURL, clientId, clientSecret) {
125
125
  }
126
126
  }
127
127
  }
128
- var getInstagramAuthorizationURL = (token) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
128
+ var getInstagramAuthorizationURL = (token, scopes = ["instagram_business_basic"]) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
129
129
  "INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */
130
- )}&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`;
130
+ )}&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`;
131
131
 
132
- // src/socials/instagram/utils.ts
133
- function getInstagramURL(token, path, params) {
134
- return `https://graph.instagram.com/v23.0/${path}?${Object.keys(params).map((key) => `${key}=${params[key]}`).join("&")}&access_token=${token}`;
135
- }
136
- function getInstagramData(token, path, params = {}, postData) {
137
- return fetch(getInstagramURL(token, path, params), {
138
- method: postData ? "POST" : "GET",
139
- body: JSON.stringify(postData),
140
- headers: {
141
- Authorization: `Bearer ${token}`,
142
- "Content-Type": "application/json"
132
+ // src/socials/meta/request.ts
133
+ function createGraphClient(baseURL) {
134
+ const buildURL = (token, path, params = {}) => {
135
+ const search = new URLSearchParams();
136
+ for (const [key, value] of Object.entries(params)) {
137
+ if (value !== void 0) search.set(key, String(value));
143
138
  }
144
- }).then((res) => res.json());
139
+ search.set("access_token", token);
140
+ return `${baseURL}/${path}?${search.toString()}`;
141
+ };
142
+ const request = async (token, path, options = {}) => {
143
+ const response = await fetch(buildURL(token, path, options.params), {
144
+ method: options.method || (options.body ? "POST" : "GET"),
145
+ headers: {
146
+ Authorization: `Bearer ${token}`,
147
+ ...options.body ? { "Content-Type": "application/json" } : {}
148
+ },
149
+ ...options.body ? { body: JSON.stringify(options.body) } : {}
150
+ });
151
+ return response.json().catch(() => null);
152
+ };
153
+ return { buildURL, request };
145
154
  }
155
+ function readGraphID(label, result) {
156
+ if (result?.error) {
157
+ console.error(`[naystack] ${label} failed: ${result.error.message}`);
158
+ return null;
159
+ }
160
+ return result?.id || null;
161
+ }
162
+
163
+ // src/socials/instagram/utils.ts
164
+ var client = createGraphClient("https://graph.instagram.com/v23.0");
165
+ var getInstagramData = client.request;
146
166
 
147
167
  // src/socials/instagram/getters.ts
148
168
  var getInstagramUser = (token, id, fields) => {
149
169
  return getInstagramData(token, id || "me", {
150
- fields: fields ? fields.join(",") : "username,followers_count,media_count"
170
+ params: {
171
+ fields: fields ? fields.join(",") : "username,followers_count,media_count"
172
+ }
151
173
  });
152
174
  };
175
+ var getInstagramContainerStatus = async (token, id) => {
176
+ const result = await getInstagramData(token, id, { params: { fields: "status_code,status" } });
177
+ if (!result?.status_code) return null;
178
+ return {
179
+ status: result.status_code,
180
+ error: result.status_code === "ERROR" ? result.status : void 0
181
+ };
182
+ };
153
183
 
154
- // src/socials/meta-webhook.ts
184
+ // src/socials/meta/container.ts
185
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
186
+ async function waitForContainer(getState, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
187
+ const deadline = Date.now() + timeoutMS;
188
+ let state = await getState();
189
+ if (!state) {
190
+ await sleep(2e3);
191
+ return null;
192
+ }
193
+ while (state?.status === "IN_PROGRESS" && Date.now() < deadline) {
194
+ await sleep(intervalMS);
195
+ state = await getState();
196
+ }
197
+ return state;
198
+ }
199
+ function createPublisher(platform) {
200
+ const isReady = async (token, id, label, wait) => {
201
+ const state = await waitForContainer(
202
+ () => platform.getStatus(token, id),
203
+ wait
204
+ );
205
+ if (state && state.status !== "FINISHED" && state.status !== "PUBLISHED") {
206
+ console.error(
207
+ `[naystack] ${platform.name} ${label} ${id} is ${state.status}${state.error ? `: ${state.error}` : ""}`
208
+ );
209
+ return false;
210
+ }
211
+ return true;
212
+ };
213
+ return {
214
+ /** Creates a container, waits for it to finish processing, then publishes it. */
215
+ publish: async (token, params, wait) => {
216
+ const containerID = await platform.createContainer(token, params);
217
+ if (!containerID) return null;
218
+ if (!await isReady(token, containerID, "container", wait)) return null;
219
+ return platform.publish(token, containerID);
220
+ },
221
+ /** Creates and awaits every carousel child, resolving to their ids — or `null` if any failed. */
222
+ createChildren: async (token, items, wait) => {
223
+ const children = [];
224
+ for (const item of items) {
225
+ const childID = await platform.createContainer(token, {
226
+ ...item,
227
+ is_carousel_item: true
228
+ });
229
+ if (!childID) return null;
230
+ if (!await isReady(token, childID, "carousel item", wait))
231
+ return null;
232
+ children.push(childID);
233
+ }
234
+ return children;
235
+ }
236
+ };
237
+ }
238
+
239
+ // src/socials/instagram/setters.ts
240
+ var publisher = createPublisher({
241
+ name: "Instagram",
242
+ getStatus: getInstagramContainerStatus,
243
+ createContainer: async (token, params) => readGraphID(
244
+ "Instagram media container",
245
+ await getInstagramData(token, "me/media", {
246
+ params,
247
+ method: "POST"
248
+ })
249
+ ),
250
+ publish: async (token, creationID) => readGraphID(
251
+ "Instagram publish",
252
+ await getInstagramData(token, "me/media_publish", {
253
+ params: { creation_id: creationID },
254
+ method: "POST"
255
+ })
256
+ )
257
+ });
258
+
259
+ // src/socials/meta/webhook.ts
155
260
  var import_server2 = require("next/server");
156
261
 
262
+ // src/socials/threads/utils.ts
263
+ var client2 = createGraphClient("https://graph.threads.net/v1.0");
264
+ var getThreadsData = client2.request;
265
+
266
+ // src/socials/threads/getters.ts
267
+ var getThreadsContainerStatus = async (token, id) => {
268
+ const result = await getThreadsData(token, id, { params: { fields: "status,error_message" } });
269
+ if (!result?.status) return null;
270
+ return { status: result.status, error: result.error_message };
271
+ };
272
+
273
+ // src/socials/threads/setters.ts
274
+ var publisher2 = createPublisher({
275
+ name: "Threads",
276
+ getStatus: getThreadsContainerStatus,
277
+ createContainer: async (token, params) => readGraphID(
278
+ "Threads container",
279
+ await getThreadsData(token, "me/threads", {
280
+ params,
281
+ method: "POST"
282
+ })
283
+ ),
284
+ publish: async (token, creationID) => readGraphID(
285
+ "Threads publish",
286
+ await getThreadsData(token, "me/threads_publish", {
287
+ params: { creation_id: creationID },
288
+ method: "POST"
289
+ })
290
+ )
291
+ });
292
+
157
293
  // src/auth/instagram/route.ts
158
294
  var getInstagramRoute = ({
159
295
  redirectURL,
160
296
  errorRedirectURL,
161
- onUser
297
+ onUser,
298
+ scopes
162
299
  }) => {
163
300
  const handleError = (message) => import_server3.NextResponse.redirect(`${errorRedirectURL}?error=${message}`);
164
301
  return async (req) => {
@@ -168,7 +305,10 @@ var getInstagramRoute = ({
168
305
  if (error) return handleError(error);
169
306
  if (!accessCode) {
170
307
  if (!stateToken) return handleError("Invalid request");
171
- return import_server3.NextResponse.redirect(getInstagramAuthorizationURL(stateToken), 302);
308
+ return import_server3.NextResponse.redirect(
309
+ getInstagramAuthorizationURL(stateToken, scopes),
310
+ 302
311
+ );
172
312
  }
173
313
  if (!stateToken) return handleError("Invalid request");
174
314
  const instagramData = await getLongLivedToken(
@@ -2,12 +2,14 @@ import { NextRequest, NextResponse } from 'next/server';
2
2
  import { SetupInstagramAuthOptions } from './index.mjs';
3
3
  import './utils.mjs';
4
4
  import '../../socials/instagram/types.mjs';
5
+ import '../../socials/meta/container.mjs';
6
+ import '../../socials/meta/types.mjs';
5
7
 
6
8
  /**
7
9
  * Returns the GET route handler for Instagram OAuth callback.
8
10
  * @param options - SetupInstagramAuthOptions (onUser, redirect URLs)
9
11
  * @returns Async route handler for the OAuth callback
10
12
  */
11
- declare const getInstagramRoute: ({ redirectURL, errorRedirectURL, onUser, }: SetupInstagramAuthOptions) => (req: NextRequest) => Promise<NextResponse<unknown>>;
13
+ declare const getInstagramRoute: ({ redirectURL, errorRedirectURL, onUser, scopes, }: SetupInstagramAuthOptions) => (req: NextRequest) => Promise<NextResponse<unknown>>;
12
14
 
13
15
  export { getInstagramRoute };
@@ -2,12 +2,14 @@ import { NextRequest, NextResponse } from 'next/server';
2
2
  import { SetupInstagramAuthOptions } from './index.js';
3
3
  import './utils.js';
4
4
  import '../../socials/instagram/types.js';
5
+ import '../../socials/meta/container.js';
6
+ import '../../socials/meta/types.js';
5
7
 
6
8
  /**
7
9
  * Returns the GET route handler for Instagram OAuth callback.
8
10
  * @param options - SetupInstagramAuthOptions (onUser, redirect URLs)
9
11
  * @returns Async route handler for the OAuth callback
10
12
  */
11
- declare const getInstagramRoute: ({ redirectURL, errorRedirectURL, onUser, }: SetupInstagramAuthOptions) => (req: NextRequest) => Promise<NextResponse<unknown>>;
13
+ declare const getInstagramRoute: ({ redirectURL, errorRedirectURL, onUser, scopes, }: SetupInstagramAuthOptions) => (req: NextRequest) => Promise<NextResponse<unknown>>;
12
14
 
13
15
  export { getInstagramRoute };
@@ -101,40 +101,177 @@ async function getLongLivedToken(code, redirectURL, clientId, clientSecret) {
101
101
  }
102
102
  }
103
103
  }
104
- var getInstagramAuthorizationURL = (token) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
104
+ var getInstagramAuthorizationURL = (token, scopes = ["instagram_business_basic"]) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
105
105
  "INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */
106
- )}&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`;
106
+ )}&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`;
107
107
 
108
- // src/socials/instagram/utils.ts
109
- function getInstagramURL(token, path, params) {
110
- return `https://graph.instagram.com/v23.0/${path}?${Object.keys(params).map((key) => `${key}=${params[key]}`).join("&")}&access_token=${token}`;
111
- }
112
- function getInstagramData(token, path, params = {}, postData) {
113
- return fetch(getInstagramURL(token, path, params), {
114
- method: postData ? "POST" : "GET",
115
- body: JSON.stringify(postData),
116
- headers: {
117
- Authorization: `Bearer ${token}`,
118
- "Content-Type": "application/json"
108
+ // src/socials/meta/request.ts
109
+ function createGraphClient(baseURL) {
110
+ const buildURL = (token, path, params = {}) => {
111
+ const search = new URLSearchParams();
112
+ for (const [key, value] of Object.entries(params)) {
113
+ if (value !== void 0) search.set(key, String(value));
119
114
  }
120
- }).then((res) => res.json());
115
+ search.set("access_token", token);
116
+ return `${baseURL}/${path}?${search.toString()}`;
117
+ };
118
+ const request = async (token, path, options = {}) => {
119
+ const response = await fetch(buildURL(token, path, options.params), {
120
+ method: options.method || (options.body ? "POST" : "GET"),
121
+ headers: {
122
+ Authorization: `Bearer ${token}`,
123
+ ...options.body ? { "Content-Type": "application/json" } : {}
124
+ },
125
+ ...options.body ? { body: JSON.stringify(options.body) } : {}
126
+ });
127
+ return response.json().catch(() => null);
128
+ };
129
+ return { buildURL, request };
121
130
  }
131
+ function readGraphID(label, result) {
132
+ if (result?.error) {
133
+ console.error(`[naystack] ${label} failed: ${result.error.message}`);
134
+ return null;
135
+ }
136
+ return result?.id || null;
137
+ }
138
+
139
+ // src/socials/instagram/utils.ts
140
+ var client = createGraphClient("https://graph.instagram.com/v23.0");
141
+ var getInstagramData = client.request;
122
142
 
123
143
  // src/socials/instagram/getters.ts
124
144
  var getInstagramUser = (token, id, fields) => {
125
145
  return getInstagramData(token, id || "me", {
126
- fields: fields ? fields.join(",") : "username,followers_count,media_count"
146
+ params: {
147
+ fields: fields ? fields.join(",") : "username,followers_count,media_count"
148
+ }
127
149
  });
128
150
  };
151
+ var getInstagramContainerStatus = async (token, id) => {
152
+ const result = await getInstagramData(token, id, { params: { fields: "status_code,status" } });
153
+ if (!result?.status_code) return null;
154
+ return {
155
+ status: result.status_code,
156
+ error: result.status_code === "ERROR" ? result.status : void 0
157
+ };
158
+ };
129
159
 
130
- // src/socials/meta-webhook.ts
160
+ // src/socials/meta/container.ts
161
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
162
+ async function waitForContainer(getState, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
163
+ const deadline = Date.now() + timeoutMS;
164
+ let state = await getState();
165
+ if (!state) {
166
+ await sleep(2e3);
167
+ return null;
168
+ }
169
+ while (state?.status === "IN_PROGRESS" && Date.now() < deadline) {
170
+ await sleep(intervalMS);
171
+ state = await getState();
172
+ }
173
+ return state;
174
+ }
175
+ function createPublisher(platform) {
176
+ const isReady = async (token, id, label, wait) => {
177
+ const state = await waitForContainer(
178
+ () => platform.getStatus(token, id),
179
+ wait
180
+ );
181
+ if (state && state.status !== "FINISHED" && state.status !== "PUBLISHED") {
182
+ console.error(
183
+ `[naystack] ${platform.name} ${label} ${id} is ${state.status}${state.error ? `: ${state.error}` : ""}`
184
+ );
185
+ return false;
186
+ }
187
+ return true;
188
+ };
189
+ return {
190
+ /** Creates a container, waits for it to finish processing, then publishes it. */
191
+ publish: async (token, params, wait) => {
192
+ const containerID = await platform.createContainer(token, params);
193
+ if (!containerID) return null;
194
+ if (!await isReady(token, containerID, "container", wait)) return null;
195
+ return platform.publish(token, containerID);
196
+ },
197
+ /** Creates and awaits every carousel child, resolving to their ids — or `null` if any failed. */
198
+ createChildren: async (token, items, wait) => {
199
+ const children = [];
200
+ for (const item of items) {
201
+ const childID = await platform.createContainer(token, {
202
+ ...item,
203
+ is_carousel_item: true
204
+ });
205
+ if (!childID) return null;
206
+ if (!await isReady(token, childID, "carousel item", wait))
207
+ return null;
208
+ children.push(childID);
209
+ }
210
+ return children;
211
+ }
212
+ };
213
+ }
214
+
215
+ // src/socials/instagram/setters.ts
216
+ var publisher = createPublisher({
217
+ name: "Instagram",
218
+ getStatus: getInstagramContainerStatus,
219
+ createContainer: async (token, params) => readGraphID(
220
+ "Instagram media container",
221
+ await getInstagramData(token, "me/media", {
222
+ params,
223
+ method: "POST"
224
+ })
225
+ ),
226
+ publish: async (token, creationID) => readGraphID(
227
+ "Instagram publish",
228
+ await getInstagramData(token, "me/media_publish", {
229
+ params: { creation_id: creationID },
230
+ method: "POST"
231
+ })
232
+ )
233
+ });
234
+
235
+ // src/socials/meta/webhook.ts
131
236
  import { NextResponse as NextResponse2 } from "next/server";
132
237
 
238
+ // src/socials/threads/utils.ts
239
+ var client2 = createGraphClient("https://graph.threads.net/v1.0");
240
+ var getThreadsData = client2.request;
241
+
242
+ // src/socials/threads/getters.ts
243
+ var getThreadsContainerStatus = async (token, id) => {
244
+ const result = await getThreadsData(token, id, { params: { fields: "status,error_message" } });
245
+ if (!result?.status) return null;
246
+ return { status: result.status, error: result.error_message };
247
+ };
248
+
249
+ // src/socials/threads/setters.ts
250
+ var publisher2 = createPublisher({
251
+ name: "Threads",
252
+ getStatus: getThreadsContainerStatus,
253
+ createContainer: async (token, params) => readGraphID(
254
+ "Threads container",
255
+ await getThreadsData(token, "me/threads", {
256
+ params,
257
+ method: "POST"
258
+ })
259
+ ),
260
+ publish: async (token, creationID) => readGraphID(
261
+ "Threads publish",
262
+ await getThreadsData(token, "me/threads_publish", {
263
+ params: { creation_id: creationID },
264
+ method: "POST"
265
+ })
266
+ )
267
+ });
268
+
133
269
  // src/auth/instagram/route.ts
134
270
  var getInstagramRoute = ({
135
271
  redirectURL,
136
272
  errorRedirectURL,
137
- onUser
273
+ onUser,
274
+ scopes
138
275
  }) => {
139
276
  const handleError = (message) => NextResponse3.redirect(`${errorRedirectURL}?error=${message}`);
140
277
  return async (req) => {
@@ -144,7 +281,10 @@ var getInstagramRoute = ({
144
281
  if (error) return handleError(error);
145
282
  if (!accessCode) {
146
283
  if (!stateToken) return handleError("Invalid request");
147
- return NextResponse3.redirect(getInstagramAuthorizationURL(stateToken), 302);
284
+ return NextResponse3.redirect(
285
+ getInstagramAuthorizationURL(stateToken, scopes),
286
+ 302
287
+ );
148
288
  }
149
289
  if (!stateToken) return handleError("Invalid request");
150
290
  const instagramData = await getLongLivedToken(
@@ -112,9 +112,9 @@ async function getLongLivedToken(code, redirectURL, clientId, clientSecret) {
112
112
  }
113
113
  }
114
114
  }
115
- var getInstagramAuthorizationURL = (token) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
115
+ var getInstagramAuthorizationURL = (token, scopes = ["instagram_business_basic"]) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
116
116
  "INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */
117
- )}&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`;
117
+ )}&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`;
118
118
  // Annotate the CommonJS export names for ESM import in node:
119
119
  0 && (module.exports = {
120
120
  getInstagramAuthorizationURL,
@@ -1,3 +1,13 @@
1
+ /**
2
+ * Permissions requestable during Instagram Login OAuth. `instagram_business_basic`
3
+ * is enough to read a profile; publishing needs `instagram_business_content_publish`.
4
+ *
5
+ * Widening the scopes only affects users who authorize afterwards — tokens already
6
+ * stored keep the permissions they were granted, so existing users must reconnect.
7
+ *
8
+ * @category Auth
9
+ */
10
+ type InstagramScope = "instagram_business_basic" | "instagram_business_content_publish" | "instagram_business_manage_messages" | "instagram_business_manage_comments" | "instagram_business_manage_insights";
1
11
  /**
2
12
  * Exchanges a long-lived Instagram token for a refreshed token.
3
13
  * @param token - Current long-lived access token
@@ -29,6 +39,7 @@ declare function getLongLivedToken(code: string, redirectURL: string, clientId:
29
39
  * simply built on the server instead of shipped in the client bundle.
30
40
  *
31
41
  * @param token - The state token to embed in the authorization URL (typically the user's session or access token).
42
+ * @param scopes - Permissions to request. Default: `["instagram_business_basic"]`.
32
43
  * @returns The full Instagram OAuth authorization URL.
33
44
  *
34
45
  * @example
@@ -41,6 +52,6 @@ declare function getLongLivedToken(code: string, redirectURL: string, clientId:
41
52
  *
42
53
  * @category Auth
43
54
  */
44
- declare const getInstagramAuthorizationURL: (token: string) => string;
55
+ declare const getInstagramAuthorizationURL: (token: string, scopes?: InstagramScope[]) => string;
45
56
 
46
- export { getInstagramAuthorizationURL, getLongLivedToken, getRefreshedInstagramAccessToken };
57
+ export { type InstagramScope, getInstagramAuthorizationURL, getLongLivedToken, getRefreshedInstagramAccessToken };
@@ -1,3 +1,13 @@
1
+ /**
2
+ * Permissions requestable during Instagram Login OAuth. `instagram_business_basic`
3
+ * is enough to read a profile; publishing needs `instagram_business_content_publish`.
4
+ *
5
+ * Widening the scopes only affects users who authorize afterwards — tokens already
6
+ * stored keep the permissions they were granted, so existing users must reconnect.
7
+ *
8
+ * @category Auth
9
+ */
10
+ type InstagramScope = "instagram_business_basic" | "instagram_business_content_publish" | "instagram_business_manage_messages" | "instagram_business_manage_comments" | "instagram_business_manage_insights";
1
11
  /**
2
12
  * Exchanges a long-lived Instagram token for a refreshed token.
3
13
  * @param token - Current long-lived access token
@@ -29,6 +39,7 @@ declare function getLongLivedToken(code: string, redirectURL: string, clientId:
29
39
  * simply built on the server instead of shipped in the client bundle.
30
40
  *
31
41
  * @param token - The state token to embed in the authorization URL (typically the user's session or access token).
42
+ * @param scopes - Permissions to request. Default: `["instagram_business_basic"]`.
32
43
  * @returns The full Instagram OAuth authorization URL.
33
44
  *
34
45
  * @example
@@ -41,6 +52,6 @@ declare function getLongLivedToken(code: string, redirectURL: string, clientId:
41
52
  *
42
53
  * @category Auth
43
54
  */
44
- declare const getInstagramAuthorizationURL: (token: string) => string;
55
+ declare const getInstagramAuthorizationURL: (token: string, scopes?: InstagramScope[]) => string;
45
56
 
46
- export { getInstagramAuthorizationURL, getLongLivedToken, getRefreshedInstagramAccessToken };
57
+ export { type InstagramScope, getInstagramAuthorizationURL, getLongLivedToken, getRefreshedInstagramAccessToken };
@@ -84,9 +84,9 @@ async function getLongLivedToken(code, redirectURL, clientId, clientSecret) {
84
84
  }
85
85
  }
86
86
  }
87
- var getInstagramAuthorizationURL = (token) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
87
+ var getInstagramAuthorizationURL = (token, scopes = ["instagram_business_basic"]) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
88
88
  "INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */
89
- )}&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`;
89
+ )}&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`;
90
90
  export {
91
91
  getInstagramAuthorizationURL,
92
92
  getLongLivedToken,
@@ -5,6 +5,12 @@ import '@aws-sdk/client-s3';
5
5
  /**
6
6
  * Options for setting up file upload via {@link setupFileUpload}.
7
7
  *
8
+ * Every callback receives the same `{ type, userId, data }` context. **`data` is
9
+ * optional** — it is whatever the caller passed as `config.data` to
10
+ * `useFileUpload`, and the client only appends the form field when a value is
11
+ * given, so the route parses it to `undefined` otherwise. Narrow it before use;
12
+ * destructuring it unguarded throws at runtime.
13
+ *
8
14
  * @property getKey - Optional. Given `{ type, userId, data }`, returns the S3 object key for the upload. If omitted, a UUID is generated.
9
15
  * @property processFile - Optional. Given the uploaded `file` and `{ type, userId, data }`, returns the blob to
10
16
  * store in its place — e.g. re-encoding an image, or stripping metadata. Runs after authentication and before the
@@ -22,23 +28,23 @@ interface SetupFileUploadOptions {
22
28
  getKey?: (data: {
23
29
  type: string;
24
30
  userId: number;
25
- data: object;
31
+ data?: object;
26
32
  }) => Promise<string>;
27
33
  processFile?: (file: File, data: {
28
34
  type: string;
29
35
  userId: number;
30
- data: object;
36
+ data?: object;
31
37
  }) => Promise<Blob>;
32
38
  putOptions?: (data: {
33
39
  type: string;
34
40
  userId: number;
35
- data: object;
41
+ data?: object;
36
42
  }) => PutOverrides;
37
43
  onUpload: (data: {
38
44
  url: string | null;
39
45
  type: string;
40
46
  userId: number;
41
- data: object;
47
+ data?: object;
42
48
  }) => Promise<object>;
43
49
  }
44
50
  /**
@@ -65,7 +71,9 @@ interface SetupFileUploadOptions {
65
71
  * export const { PUT } = setupFileUpload({
66
72
  * onUpload: async ({ url, type, userId, data }) => {
67
73
  * if (type === "DealDocument" && url) {
68
- * const payload = data as { dealId: number; fileName: string };
74
+ * // `data` is absent unless the client sent one check before reading it.
75
+ * const payload = data as { dealId?: number; fileName?: string } | undefined;
76
+ * if (!payload?.dealId) return {};
69
77
  * const [row] = await db.insert(DocumentsTable).values({
70
78
  * dealId: payload.dealId, fileURL: url, fileName: payload.fileName,
71
79
  * }).returning();
@@ -5,6 +5,12 @@ import '@aws-sdk/client-s3';
5
5
  /**
6
6
  * Options for setting up file upload via {@link setupFileUpload}.
7
7
  *
8
+ * Every callback receives the same `{ type, userId, data }` context. **`data` is
9
+ * optional** — it is whatever the caller passed as `config.data` to
10
+ * `useFileUpload`, and the client only appends the form field when a value is
11
+ * given, so the route parses it to `undefined` otherwise. Narrow it before use;
12
+ * destructuring it unguarded throws at runtime.
13
+ *
8
14
  * @property getKey - Optional. Given `{ type, userId, data }`, returns the S3 object key for the upload. If omitted, a UUID is generated.
9
15
  * @property processFile - Optional. Given the uploaded `file` and `{ type, userId, data }`, returns the blob to
10
16
  * store in its place — e.g. re-encoding an image, or stripping metadata. Runs after authentication and before the
@@ -22,23 +28,23 @@ interface SetupFileUploadOptions {
22
28
  getKey?: (data: {
23
29
  type: string;
24
30
  userId: number;
25
- data: object;
31
+ data?: object;
26
32
  }) => Promise<string>;
27
33
  processFile?: (file: File, data: {
28
34
  type: string;
29
35
  userId: number;
30
- data: object;
36
+ data?: object;
31
37
  }) => Promise<Blob>;
32
38
  putOptions?: (data: {
33
39
  type: string;
34
40
  userId: number;
35
- data: object;
41
+ data?: object;
36
42
  }) => PutOverrides;
37
43
  onUpload: (data: {
38
44
  url: string | null;
39
45
  type: string;
40
46
  userId: number;
41
- data: object;
47
+ data?: object;
42
48
  }) => Promise<object>;
43
49
  }
44
50
  /**
@@ -65,7 +71,9 @@ interface SetupFileUploadOptions {
65
71
  * export const { PUT } = setupFileUpload({
66
72
  * onUpload: async ({ url, type, userId, data }) => {
67
73
  * if (type === "DealDocument" && url) {
68
- * const payload = data as { dealId: number; fileName: string };
74
+ * // `data` is absent unless the client sent one check before reading it.
75
+ * const payload = data as { dealId?: number; fileName?: string } | undefined;
76
+ * if (!payload?.dealId) return {};
69
77
  * const [row] = await db.insert(DocumentsTable).values({
70
78
  * dealId: payload.dealId, fileURL: url, fileName: payload.fileName,
71
79
  * }).returning();
package/dist/index.d.mts CHANGED
@@ -10,7 +10,7 @@
10
10
  * - `naystack/file` — S3 file upload setup
11
11
  * - `naystack/file/client` — Client-side file upload hook (useFileUpload)
12
12
  * - `naystack/client` — Client hooks (useVisibility, useBreakpoint) and SEO (setupSEO)
13
- * - `naystack/socials` — Instagram and Threads API helpers
13
+ * - `naystack/socials` — publish to and read from Instagram and Threads
14
14
  *
15
15
  * @module
16
16
  */
package/dist/index.d.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * - `naystack/file` — S3 file upload setup
11
11
  * - `naystack/file/client` — Client-side file upload hook (useFileUpload)
12
12
  * - `naystack/client` — Client hooks (useVisibility, useBreakpoint) and SEO (setupSEO)
13
- * - `naystack/socials` — Instagram and Threads API helpers
13
+ * - `naystack/socials` — publish to and read from Instagram and Threads
14
14
  *
15
15
  * @module
16
16
  */