naystack 1.8.18 → 1.9.0
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 +94 -34
- package/dist/auth/client.cjs.js +2 -2
- package/dist/auth/client.esm.js +2 -2
- package/dist/auth/email/client.cjs.js +2 -2
- package/dist/auth/email/client.esm.js +2 -2
- package/dist/auth/email/index.cjs.js +2 -2
- package/dist/auth/email/index.esm.js +2 -2
- package/dist/auth/email/next.cjs.js +2 -2
- package/dist/auth/email/next.esm.js +2 -2
- package/dist/auth/email/routes/delete.cjs.js +2 -2
- package/dist/auth/email/routes/delete.esm.js +2 -2
- package/dist/auth/email/routes/get.cjs.js +2 -2
- package/dist/auth/email/routes/get.esm.js +2 -2
- package/dist/auth/email/routes/post.cjs.js +2 -2
- package/dist/auth/email/routes/post.esm.js +2 -2
- package/dist/auth/email/routes/put.cjs.js +2 -2
- package/dist/auth/email/routes/put.esm.js +2 -2
- package/dist/auth/email/token.cjs.js +2 -2
- package/dist/auth/email/token.esm.js +2 -2
- package/dist/auth/email/utils.cjs.js +2 -2
- package/dist/auth/email/utils.esm.js +2 -2
- package/dist/auth/google/get.cjs.js +2 -2
- package/dist/auth/google/get.esm.js +2 -2
- package/dist/auth/google/index.cjs.js +2 -2
- package/dist/auth/google/index.esm.js +2 -2
- package/dist/auth/index.cjs.js +4 -297
- package/dist/auth/index.d.mts +0 -5
- package/dist/auth/index.d.ts +0 -5
- package/dist/auth/index.esm.js +3 -293
- package/dist/auth/token-store.cjs.js +2 -2
- package/dist/auth/token-store.esm.js +2 -2
- package/dist/env.cjs.js +3 -3
- package/dist/env.d.mts +3 -2
- package/dist/env.d.ts +3 -2
- package/dist/env.esm.js +3 -3
- package/dist/file/client.cjs.js +2 -2
- package/dist/file/client.esm.js +2 -2
- package/dist/file/index.cjs.js +2 -2
- package/dist/file/index.esm.js +2 -2
- package/dist/file/put.cjs.js +2 -2
- package/dist/file/put.esm.js +2 -2
- package/dist/file/setup.cjs.js +2 -2
- package/dist/file/setup.esm.js +2 -2
- package/dist/file/utils.cjs.js +2 -2
- package/dist/file/utils.esm.js +2 -2
- package/dist/graphql/client.cjs.js +2 -2
- package/dist/graphql/client.esm.js +2 -2
- package/dist/graphql/index.cjs.js +2 -2
- package/dist/graphql/index.esm.js +2 -2
- package/dist/graphql/init.cjs.js +2 -2
- package/dist/graphql/init.esm.js +2 -2
- package/dist/graphql/links.cjs.js +2 -2
- package/dist/graphql/links.esm.js +2 -2
- package/dist/graphql/next.cjs.js +2 -2
- package/dist/graphql/next.esm.js +2 -2
- package/dist/graphql/server.cjs.js +2 -2
- package/dist/graphql/server.esm.js +2 -2
- package/dist/graphql/utils.cjs.js +2 -2
- package/dist/graphql/utils.esm.js +2 -2
- package/dist/socials/connect.cjs.js +151 -0
- package/dist/socials/connect.d.mts +70 -0
- package/dist/socials/connect.d.ts +70 -0
- package/dist/socials/connect.esm.js +126 -0
- package/dist/socials/index.cjs.js +367 -35
- package/dist/socials/index.d.mts +8 -2
- package/dist/socials/index.d.ts +8 -2
- package/dist/socials/index.esm.js +357 -34
- package/dist/socials/instagram/adapter.cjs.js +275 -0
- package/dist/socials/instagram/adapter.d.mts +30 -0
- package/dist/socials/instagram/adapter.d.ts +30 -0
- package/dist/socials/instagram/adapter.esm.js +248 -0
- package/dist/{auth/instagram/utils.cjs.js → socials/instagram/auth.cjs.js} +43 -41
- package/dist/socials/instagram/auth.d.mts +52 -0
- package/dist/socials/instagram/auth.d.ts +52 -0
- package/dist/{auth/instagram/utils.esm.js → socials/instagram/auth.esm.js} +37 -35
- package/dist/socials/instagram/getters.d.mts +1 -0
- package/dist/socials/instagram/getters.d.ts +1 -0
- package/dist/socials/instagram/setters.cjs.js +56 -31
- package/dist/socials/instagram/setters.d.mts +1 -0
- package/dist/socials/instagram/setters.d.ts +1 -0
- package/dist/socials/instagram/setters.esm.js +56 -31
- package/dist/socials/instagram/types.d.mts +2 -1
- package/dist/socials/instagram/types.d.ts +2 -1
- package/dist/socials/meta/container.cjs.js +57 -30
- package/dist/socials/meta/container.d.mts +15 -28
- package/dist/socials/meta/container.d.ts +15 -28
- package/dist/socials/meta/container.esm.js +56 -31
- package/dist/socials/provider.cjs.js +18 -0
- package/dist/socials/provider.d.mts +52 -0
- package/dist/socials/provider.d.ts +52 -0
- package/dist/socials/provider.esm.js +0 -0
- package/dist/socials/threads/getters.d.mts +1 -0
- package/dist/socials/threads/getters.d.ts +1 -0
- package/dist/socials/threads/setters.cjs.js +56 -31
- package/dist/socials/threads/setters.d.mts +1 -0
- package/dist/socials/threads/setters.d.ts +1 -0
- package/dist/socials/threads/setters.esm.js +56 -31
- package/dist/socials/threads/types.d.mts +2 -1
- package/dist/socials/threads/types.d.ts +2 -1
- package/dist/socials/types.cjs.js +43 -0
- package/dist/socials/types.d.mts +99 -0
- package/dist/socials/types.d.ts +99 -0
- package/dist/socials/types.esm.js +17 -0
- package/dist/socials/utils/poll.cjs.js +59 -0
- package/dist/socials/utils/poll.d.mts +85 -0
- package/dist/socials/utils/poll.d.ts +85 -0
- package/dist/socials/utils/poll.esm.js +33 -0
- package/dist/utils/client/index.cjs.js +2 -2
- package/dist/utils/client/index.esm.js +2 -2
- package/dist/utils/client/seo.cjs.js +2 -2
- package/dist/utils/client/seo.esm.js +2 -2
- package/package.json +1 -1
- package/dist/auth/instagram/index.cjs.js +0 -379
- package/dist/auth/instagram/index.d.mts +0 -58
- package/dist/auth/instagram/index.d.ts +0 -58
- package/dist/auth/instagram/index.esm.js +0 -350
- package/dist/auth/instagram/route.cjs.js +0 -359
- package/dist/auth/instagram/route.d.mts +0 -15
- package/dist/auth/instagram/route.d.ts +0 -15
- package/dist/auth/instagram/route.esm.js +0 -334
- package/dist/auth/instagram/utils.d.mts +0 -57
- package/dist/auth/instagram/utils.d.ts +0 -57
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/socials/instagram/adapter.ts
|
|
21
|
+
var adapter_exports = {};
|
|
22
|
+
__export(adapter_exports, {
|
|
23
|
+
InstagramProvider: () => InstagramProvider
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(adapter_exports);
|
|
26
|
+
|
|
27
|
+
// src/env.ts
|
|
28
|
+
var EXTRA_ENV = globalThis.__NAYSTACK_ENV__ || (globalThis.__NAYSTACK_ENV__ = {});
|
|
29
|
+
var getEnvValue = (key) => {
|
|
30
|
+
switch (key) {
|
|
31
|
+
case "NEXT_PUBLIC_GRAPHQL_ENDPOINT" /* NEXT_PUBLIC_GRAPHQL_ENDPOINT */:
|
|
32
|
+
return process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT || EXTRA_ENV.GRAPHQL_ENDPOINT;
|
|
33
|
+
case "NEXT_PUBLIC_EMAIL_AUTH_ENDPOINT" /* NEXT_PUBLIC_EMAIL_AUTH_ENDPOINT */:
|
|
34
|
+
return process.env.NEXT_PUBLIC_EMAIL_AUTH_ENDPOINT || EXTRA_ENV.EMAIL_AUTH_ENDPOINT;
|
|
35
|
+
case "NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT" /* NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT */:
|
|
36
|
+
return process.env.NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT || EXTRA_ENV.GOOGLE_AUTH_ENDPOINT;
|
|
37
|
+
case "NEXT_PUBLIC_SOCIAL_AUTH_ENDPOINT" /* NEXT_PUBLIC_SOCIAL_AUTH_ENDPOINT */:
|
|
38
|
+
return process.env.NEXT_PUBLIC_SOCIAL_AUTH_ENDPOINT || EXTRA_ENV.SOCIAL_AUTH_ENDPOINT;
|
|
39
|
+
case "NEXT_PUBLIC_FILE_ENDPOINT" /* NEXT_PUBLIC_FILE_ENDPOINT */:
|
|
40
|
+
return process.env.NEXT_PUBLIC_FILE_ENDPOINT || EXTRA_ENV.FILE_ENDPOINT;
|
|
41
|
+
case "NEXT_PUBLIC_BASE_URL" /* NEXT_PUBLIC_BASE_URL */:
|
|
42
|
+
return process.env.NEXT_PUBLIC_BASE_URL || EXTRA_ENV.BASE_URL;
|
|
43
|
+
case "REFRESH_KEY" /* REFRESH_KEY */:
|
|
44
|
+
return process.env.REFRESH_KEY;
|
|
45
|
+
case "SIGNING_KEY" /* SIGNING_KEY */:
|
|
46
|
+
return process.env.SIGNING_KEY;
|
|
47
|
+
case "INSTAGRAM_CLIENT_SECRET" /* INSTAGRAM_CLIENT_SECRET */:
|
|
48
|
+
return process.env.INSTAGRAM_CLIENT_SECRET;
|
|
49
|
+
case "INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */:
|
|
50
|
+
return process.env.INSTAGRAM_CLIENT_ID;
|
|
51
|
+
case "GOOGLE_CLIENT_SECRET" /* GOOGLE_CLIENT_SECRET */:
|
|
52
|
+
return process.env.GOOGLE_CLIENT_SECRET;
|
|
53
|
+
case "GOOGLE_CLIENT_ID" /* GOOGLE_CLIENT_ID */:
|
|
54
|
+
return process.env.GOOGLE_CLIENT_ID;
|
|
55
|
+
case "TURNSTILE_KEY" /* TURNSTILE_KEY */:
|
|
56
|
+
return process.env.TURNSTILE_KEY;
|
|
57
|
+
case "S3_ACCESS_KEY_ID" /* S3_ACCESS_KEY_ID */:
|
|
58
|
+
return process.env.S3_ACCESS_KEY_ID;
|
|
59
|
+
case "S3_ACCESS_KEY_SECRET" /* S3_ACCESS_KEY_SECRET */:
|
|
60
|
+
return process.env.S3_ACCESS_KEY_SECRET;
|
|
61
|
+
case "S3_REGION" /* S3_REGION */:
|
|
62
|
+
return process.env.S3_REGION;
|
|
63
|
+
case "S3_BUCKET" /* S3_BUCKET */:
|
|
64
|
+
return process.env.S3_BUCKET;
|
|
65
|
+
case "NODE_ENV" /* NODE_ENV */:
|
|
66
|
+
return process.env.NODE_ENV;
|
|
67
|
+
default:
|
|
68
|
+
return process.env[key];
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
function getEnv(key, skipCheck) {
|
|
72
|
+
const value = getEnvValue(key);
|
|
73
|
+
if (!skipCheck && !value) throw new Error(`${key} is not defined`);
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/socials/instagram/auth.ts
|
|
78
|
+
var getInstagramAuthorizationURL = (state, redirectURI, scopes = ["instagram_business_basic"]) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
|
|
79
|
+
"INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */
|
|
80
|
+
)}&response_type=code&enable_fb_login=0&force_authentication=1&scope=${scopes.join(",")}&state=${state}&redirect_uri=${redirectURI}#weblink`;
|
|
81
|
+
async function getLongLivedInstagramToken(code, redirectURI) {
|
|
82
|
+
const formData = new FormData();
|
|
83
|
+
formData.set("client_id", getEnv("INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */));
|
|
84
|
+
formData.set("client_secret", getEnv("INSTAGRAM_CLIENT_SECRET" /* INSTAGRAM_CLIENT_SECRET */));
|
|
85
|
+
formData.set("grant_type", "authorization_code");
|
|
86
|
+
formData.set("redirect_uri", redirectURI);
|
|
87
|
+
formData.set("code", code);
|
|
88
|
+
const shortRes = await fetch("https://api.instagram.com/oauth/access_token", {
|
|
89
|
+
method: "POST",
|
|
90
|
+
body: formData
|
|
91
|
+
});
|
|
92
|
+
if (!shortRes.ok) return void 0;
|
|
93
|
+
const shortResData = await shortRes.json();
|
|
94
|
+
if (!shortResData.access_token) return void 0;
|
|
95
|
+
const longRes = await fetch(
|
|
96
|
+
`https://graph.instagram.com/access_token?grant_type=ig_exchange_token&access_token=${shortResData.access_token}&client_secret=${getEnv("INSTAGRAM_CLIENT_SECRET" /* INSTAGRAM_CLIENT_SECRET */)}`
|
|
97
|
+
);
|
|
98
|
+
if (!longRes.ok) return void 0;
|
|
99
|
+
const longResData = await longRes.json();
|
|
100
|
+
return {
|
|
101
|
+
accessToken: longResData.access_token,
|
|
102
|
+
userId: shortResData.user_id,
|
|
103
|
+
expiresIn: longResData.expires_in,
|
|
104
|
+
permissions: shortResData.permissions
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async function refreshInstagramAccessToken(token) {
|
|
108
|
+
const request = await fetch(
|
|
109
|
+
`https://graph.instagram.com/refresh_access_token?grant_type=ig_refresh_token&access_token=${token}`
|
|
110
|
+
);
|
|
111
|
+
const response = await request.json();
|
|
112
|
+
if (!response.access_token) return void 0;
|
|
113
|
+
return {
|
|
114
|
+
accessToken: response.access_token,
|
|
115
|
+
expiresIn: response.expires_in
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/socials/meta/request.ts
|
|
120
|
+
function createGraphClient(baseURL) {
|
|
121
|
+
const buildURL = (token, path, params = {}) => {
|
|
122
|
+
const search = new URLSearchParams();
|
|
123
|
+
for (const [key, value] of Object.entries(params)) {
|
|
124
|
+
if (value !== void 0) search.set(key, String(value));
|
|
125
|
+
}
|
|
126
|
+
search.set("access_token", token);
|
|
127
|
+
return `${baseURL}/${path}?${search.toString()}`;
|
|
128
|
+
};
|
|
129
|
+
const request = async (token, path, options = {}) => {
|
|
130
|
+
const response = await fetch(buildURL(token, path, options.params), {
|
|
131
|
+
method: options.method || (options.body ? "POST" : "GET"),
|
|
132
|
+
headers: {
|
|
133
|
+
Authorization: `Bearer ${token}`,
|
|
134
|
+
...options.body ? { "Content-Type": "application/json" } : {}
|
|
135
|
+
},
|
|
136
|
+
...options.body ? { body: JSON.stringify(options.body) } : {}
|
|
137
|
+
});
|
|
138
|
+
return response.json().catch(() => null);
|
|
139
|
+
};
|
|
140
|
+
return { buildURL, request };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/socials/instagram/utils.ts
|
|
144
|
+
var client = createGraphClient("https://graph.instagram.com/v23.0");
|
|
145
|
+
var getInstagramData = client.request;
|
|
146
|
+
|
|
147
|
+
// src/socials/instagram/getters.ts
|
|
148
|
+
var getInstagramUser = (token, id, fields) => {
|
|
149
|
+
return getInstagramData(token, id || "me", {
|
|
150
|
+
params: {
|
|
151
|
+
fields: fields ? fields.join(",") : "username,followers_count,media_count"
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
var getInstagramMedia = (token, fields, limit = 12) => {
|
|
156
|
+
return getInstagramData(token, "me/media", {
|
|
157
|
+
params: {
|
|
158
|
+
fields: fields ? fields.join(",") : "like_count,comments_count,permalink",
|
|
159
|
+
limit
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// src/socials/instagram/adapter.ts
|
|
165
|
+
var PROFILE_FIELDS = [
|
|
166
|
+
"id",
|
|
167
|
+
"user_id",
|
|
168
|
+
"username",
|
|
169
|
+
"name",
|
|
170
|
+
"profile_picture_url",
|
|
171
|
+
"followers_count",
|
|
172
|
+
"media_count"
|
|
173
|
+
];
|
|
174
|
+
var MEDIA_FIELDS = [
|
|
175
|
+
"id",
|
|
176
|
+
"caption",
|
|
177
|
+
"media_type",
|
|
178
|
+
"media_url",
|
|
179
|
+
"permalink",
|
|
180
|
+
"thumbnail_url",
|
|
181
|
+
"timestamp",
|
|
182
|
+
"like_count",
|
|
183
|
+
"comments_count"
|
|
184
|
+
];
|
|
185
|
+
var expiresAt = (seconds) => seconds ? new Date(Date.now() + seconds * 1e3) : void 0;
|
|
186
|
+
function toKind(mediaType) {
|
|
187
|
+
if (mediaType === "VIDEO") return "video" /* Video */;
|
|
188
|
+
if (mediaType === "CAROUSEL_ALBUM") return "carousel" /* Carousel */;
|
|
189
|
+
return "image" /* Image */;
|
|
190
|
+
}
|
|
191
|
+
function toPost(media) {
|
|
192
|
+
const kind = toKind(media.media_type);
|
|
193
|
+
return {
|
|
194
|
+
platform: "instagram" /* Instagram */,
|
|
195
|
+
platformMediaId: media.id,
|
|
196
|
+
permalink: media.permalink,
|
|
197
|
+
// A video's `media_url` is the .mp4 itself, so it is never a stand-in for a
|
|
198
|
+
// missing poster frame — better no thumbnail than one that can't render.
|
|
199
|
+
thumbnail: media.thumbnail_url ?? (kind === "video" /* Video */ ? null : media.media_url ?? null),
|
|
200
|
+
mediaURL: media.media_url ?? null,
|
|
201
|
+
caption: media.caption ?? null,
|
|
202
|
+
kind,
|
|
203
|
+
publishedAt: media.timestamp,
|
|
204
|
+
likes: media.like_count ?? null,
|
|
205
|
+
comments: media.comments_count ?? null,
|
|
206
|
+
// Both need the Insights API; the basic media fields carry neither.
|
|
207
|
+
views: null,
|
|
208
|
+
shares: null,
|
|
209
|
+
metadata: {}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
var InstagramProvider = {
|
|
213
|
+
platform: "instagram" /* Instagram */,
|
|
214
|
+
auth: {
|
|
215
|
+
authorizationURL: ({ state, redirectURI, scopes }) => getInstagramAuthorizationURL(
|
|
216
|
+
state,
|
|
217
|
+
redirectURI,
|
|
218
|
+
scopes
|
|
219
|
+
),
|
|
220
|
+
exchangeCode: async ({ code, redirectURI }) => {
|
|
221
|
+
const result = await getLongLivedInstagramToken(code, redirectURI);
|
|
222
|
+
if (!result) {
|
|
223
|
+
console.error("[naystack] Instagram code exchange returned no token");
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
accessToken: result.accessToken,
|
|
228
|
+
platformUserId: result.userId || null,
|
|
229
|
+
expiresAt: expiresAt(result.expiresIn),
|
|
230
|
+
scopes: result.permissions
|
|
231
|
+
};
|
|
232
|
+
},
|
|
233
|
+
refresh: async ({ accessToken }) => {
|
|
234
|
+
const refreshed = await refreshInstagramAccessToken(accessToken);
|
|
235
|
+
if (!refreshed) return null;
|
|
236
|
+
return {
|
|
237
|
+
accessToken: refreshed.accessToken,
|
|
238
|
+
expiresAt: expiresAt(refreshed.expiresIn)
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
fetchProfile: async (accessToken) => {
|
|
243
|
+
const user = await getInstagramUser(
|
|
244
|
+
accessToken,
|
|
245
|
+
void 0,
|
|
246
|
+
PROFILE_FIELDS
|
|
247
|
+
);
|
|
248
|
+
if (!user?.username) return null;
|
|
249
|
+
return {
|
|
250
|
+
platform: "instagram" /* Instagram */,
|
|
251
|
+
// `user_id` is what the OAuth exchange hands back, so preferring it keeps
|
|
252
|
+
// one account's id identical however it was read.
|
|
253
|
+
platformUserId: user.user_id || user.id || null,
|
|
254
|
+
username: user.username,
|
|
255
|
+
displayName: user.name ?? null,
|
|
256
|
+
avatar: user.profile_picture_url ?? null,
|
|
257
|
+
followers: user.followers_count ?? 0,
|
|
258
|
+
contentCount: user.media_count ?? 0,
|
|
259
|
+
metadata: {}
|
|
260
|
+
};
|
|
261
|
+
},
|
|
262
|
+
fetchMedia: async (accessToken, options) => {
|
|
263
|
+
const result = await getInstagramMedia(
|
|
264
|
+
accessToken,
|
|
265
|
+
MEDIA_FIELDS,
|
|
266
|
+
options?.limit
|
|
267
|
+
);
|
|
268
|
+
if (!result?.data) return [];
|
|
269
|
+
return result.data.map(toPost);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
273
|
+
0 && (module.exports = {
|
|
274
|
+
InstagramProvider
|
|
275
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { SocialProvider } from '../provider.mjs';
|
|
2
|
+
import '../types.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Instagram as a normalized {@link SocialProvider}.
|
|
6
|
+
*
|
|
7
|
+
* A ready-made singleton — import and call it, there is nothing to construct.
|
|
8
|
+
* Credentials are read from the environment inside the `auth` methods, so
|
|
9
|
+
* `fetchProfile` and `fetchMedia` need only a token and work in a process that
|
|
10
|
+
* has no Instagram OAuth configuration at all.
|
|
11
|
+
*
|
|
12
|
+
* Wraps the platform-specific helpers rather than replacing them:
|
|
13
|
+
* {@link getInstagramUser} and {@link getInstagramMedia} remain the way to reach
|
|
14
|
+
* fields this normalization drops.
|
|
15
|
+
*
|
|
16
|
+
* Requires env vars for `auth` only: `INSTAGRAM_CLIENT_ID`, `INSTAGRAM_CLIENT_SECRET`.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { InstagramProvider } from "naystack/socials";
|
|
21
|
+
*
|
|
22
|
+
* const profile = await InstagramProvider.fetchProfile(accessToken);
|
|
23
|
+
* const posts = await InstagramProvider.fetchMedia?.(accessToken, { limit: 6 });
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @category Socials
|
|
27
|
+
*/
|
|
28
|
+
declare const InstagramProvider: SocialProvider;
|
|
29
|
+
|
|
30
|
+
export { InstagramProvider };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { SocialProvider } from '../provider.js';
|
|
2
|
+
import '../types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Instagram as a normalized {@link SocialProvider}.
|
|
6
|
+
*
|
|
7
|
+
* A ready-made singleton — import and call it, there is nothing to construct.
|
|
8
|
+
* Credentials are read from the environment inside the `auth` methods, so
|
|
9
|
+
* `fetchProfile` and `fetchMedia` need only a token and work in a process that
|
|
10
|
+
* has no Instagram OAuth configuration at all.
|
|
11
|
+
*
|
|
12
|
+
* Wraps the platform-specific helpers rather than replacing them:
|
|
13
|
+
* {@link getInstagramUser} and {@link getInstagramMedia} remain the way to reach
|
|
14
|
+
* fields this normalization drops.
|
|
15
|
+
*
|
|
16
|
+
* Requires env vars for `auth` only: `INSTAGRAM_CLIENT_ID`, `INSTAGRAM_CLIENT_SECRET`.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { InstagramProvider } from "naystack/socials";
|
|
21
|
+
*
|
|
22
|
+
* const profile = await InstagramProvider.fetchProfile(accessToken);
|
|
23
|
+
* const posts = await InstagramProvider.fetchMedia?.(accessToken, { limit: 6 });
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @category Socials
|
|
27
|
+
*/
|
|
28
|
+
declare const InstagramProvider: SocialProvider;
|
|
29
|
+
|
|
30
|
+
export { InstagramProvider };
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// src/env.ts
|
|
2
|
+
var EXTRA_ENV = globalThis.__NAYSTACK_ENV__ || (globalThis.__NAYSTACK_ENV__ = {});
|
|
3
|
+
var getEnvValue = (key) => {
|
|
4
|
+
switch (key) {
|
|
5
|
+
case "NEXT_PUBLIC_GRAPHQL_ENDPOINT" /* NEXT_PUBLIC_GRAPHQL_ENDPOINT */:
|
|
6
|
+
return process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT || EXTRA_ENV.GRAPHQL_ENDPOINT;
|
|
7
|
+
case "NEXT_PUBLIC_EMAIL_AUTH_ENDPOINT" /* NEXT_PUBLIC_EMAIL_AUTH_ENDPOINT */:
|
|
8
|
+
return process.env.NEXT_PUBLIC_EMAIL_AUTH_ENDPOINT || EXTRA_ENV.EMAIL_AUTH_ENDPOINT;
|
|
9
|
+
case "NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT" /* NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT */:
|
|
10
|
+
return process.env.NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT || EXTRA_ENV.GOOGLE_AUTH_ENDPOINT;
|
|
11
|
+
case "NEXT_PUBLIC_SOCIAL_AUTH_ENDPOINT" /* NEXT_PUBLIC_SOCIAL_AUTH_ENDPOINT */:
|
|
12
|
+
return process.env.NEXT_PUBLIC_SOCIAL_AUTH_ENDPOINT || EXTRA_ENV.SOCIAL_AUTH_ENDPOINT;
|
|
13
|
+
case "NEXT_PUBLIC_FILE_ENDPOINT" /* NEXT_PUBLIC_FILE_ENDPOINT */:
|
|
14
|
+
return process.env.NEXT_PUBLIC_FILE_ENDPOINT || EXTRA_ENV.FILE_ENDPOINT;
|
|
15
|
+
case "NEXT_PUBLIC_BASE_URL" /* NEXT_PUBLIC_BASE_URL */:
|
|
16
|
+
return process.env.NEXT_PUBLIC_BASE_URL || EXTRA_ENV.BASE_URL;
|
|
17
|
+
case "REFRESH_KEY" /* REFRESH_KEY */:
|
|
18
|
+
return process.env.REFRESH_KEY;
|
|
19
|
+
case "SIGNING_KEY" /* SIGNING_KEY */:
|
|
20
|
+
return process.env.SIGNING_KEY;
|
|
21
|
+
case "INSTAGRAM_CLIENT_SECRET" /* INSTAGRAM_CLIENT_SECRET */:
|
|
22
|
+
return process.env.INSTAGRAM_CLIENT_SECRET;
|
|
23
|
+
case "INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */:
|
|
24
|
+
return process.env.INSTAGRAM_CLIENT_ID;
|
|
25
|
+
case "GOOGLE_CLIENT_SECRET" /* GOOGLE_CLIENT_SECRET */:
|
|
26
|
+
return process.env.GOOGLE_CLIENT_SECRET;
|
|
27
|
+
case "GOOGLE_CLIENT_ID" /* GOOGLE_CLIENT_ID */:
|
|
28
|
+
return process.env.GOOGLE_CLIENT_ID;
|
|
29
|
+
case "TURNSTILE_KEY" /* TURNSTILE_KEY */:
|
|
30
|
+
return process.env.TURNSTILE_KEY;
|
|
31
|
+
case "S3_ACCESS_KEY_ID" /* S3_ACCESS_KEY_ID */:
|
|
32
|
+
return process.env.S3_ACCESS_KEY_ID;
|
|
33
|
+
case "S3_ACCESS_KEY_SECRET" /* S3_ACCESS_KEY_SECRET */:
|
|
34
|
+
return process.env.S3_ACCESS_KEY_SECRET;
|
|
35
|
+
case "S3_REGION" /* S3_REGION */:
|
|
36
|
+
return process.env.S3_REGION;
|
|
37
|
+
case "S3_BUCKET" /* S3_BUCKET */:
|
|
38
|
+
return process.env.S3_BUCKET;
|
|
39
|
+
case "NODE_ENV" /* NODE_ENV */:
|
|
40
|
+
return process.env.NODE_ENV;
|
|
41
|
+
default:
|
|
42
|
+
return process.env[key];
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
function getEnv(key, skipCheck) {
|
|
46
|
+
const value = getEnvValue(key);
|
|
47
|
+
if (!skipCheck && !value) throw new Error(`${key} is not defined`);
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/socials/instagram/auth.ts
|
|
52
|
+
var getInstagramAuthorizationURL = (state, redirectURI, scopes = ["instagram_business_basic"]) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
|
|
53
|
+
"INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */
|
|
54
|
+
)}&response_type=code&enable_fb_login=0&force_authentication=1&scope=${scopes.join(",")}&state=${state}&redirect_uri=${redirectURI}#weblink`;
|
|
55
|
+
async function getLongLivedInstagramToken(code, redirectURI) {
|
|
56
|
+
const formData = new FormData();
|
|
57
|
+
formData.set("client_id", getEnv("INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */));
|
|
58
|
+
formData.set("client_secret", getEnv("INSTAGRAM_CLIENT_SECRET" /* INSTAGRAM_CLIENT_SECRET */));
|
|
59
|
+
formData.set("grant_type", "authorization_code");
|
|
60
|
+
formData.set("redirect_uri", redirectURI);
|
|
61
|
+
formData.set("code", code);
|
|
62
|
+
const shortRes = await fetch("https://api.instagram.com/oauth/access_token", {
|
|
63
|
+
method: "POST",
|
|
64
|
+
body: formData
|
|
65
|
+
});
|
|
66
|
+
if (!shortRes.ok) return void 0;
|
|
67
|
+
const shortResData = await shortRes.json();
|
|
68
|
+
if (!shortResData.access_token) return void 0;
|
|
69
|
+
const longRes = await fetch(
|
|
70
|
+
`https://graph.instagram.com/access_token?grant_type=ig_exchange_token&access_token=${shortResData.access_token}&client_secret=${getEnv("INSTAGRAM_CLIENT_SECRET" /* INSTAGRAM_CLIENT_SECRET */)}`
|
|
71
|
+
);
|
|
72
|
+
if (!longRes.ok) return void 0;
|
|
73
|
+
const longResData = await longRes.json();
|
|
74
|
+
return {
|
|
75
|
+
accessToken: longResData.access_token,
|
|
76
|
+
userId: shortResData.user_id,
|
|
77
|
+
expiresIn: longResData.expires_in,
|
|
78
|
+
permissions: shortResData.permissions
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
async function refreshInstagramAccessToken(token) {
|
|
82
|
+
const request = await fetch(
|
|
83
|
+
`https://graph.instagram.com/refresh_access_token?grant_type=ig_refresh_token&access_token=${token}`
|
|
84
|
+
);
|
|
85
|
+
const response = await request.json();
|
|
86
|
+
if (!response.access_token) return void 0;
|
|
87
|
+
return {
|
|
88
|
+
accessToken: response.access_token,
|
|
89
|
+
expiresIn: response.expires_in
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/socials/meta/request.ts
|
|
94
|
+
function createGraphClient(baseURL) {
|
|
95
|
+
const buildURL = (token, path, params = {}) => {
|
|
96
|
+
const search = new URLSearchParams();
|
|
97
|
+
for (const [key, value] of Object.entries(params)) {
|
|
98
|
+
if (value !== void 0) search.set(key, String(value));
|
|
99
|
+
}
|
|
100
|
+
search.set("access_token", token);
|
|
101
|
+
return `${baseURL}/${path}?${search.toString()}`;
|
|
102
|
+
};
|
|
103
|
+
const request = async (token, path, options = {}) => {
|
|
104
|
+
const response = await fetch(buildURL(token, path, options.params), {
|
|
105
|
+
method: options.method || (options.body ? "POST" : "GET"),
|
|
106
|
+
headers: {
|
|
107
|
+
Authorization: `Bearer ${token}`,
|
|
108
|
+
...options.body ? { "Content-Type": "application/json" } : {}
|
|
109
|
+
},
|
|
110
|
+
...options.body ? { body: JSON.stringify(options.body) } : {}
|
|
111
|
+
});
|
|
112
|
+
return response.json().catch(() => null);
|
|
113
|
+
};
|
|
114
|
+
return { buildURL, request };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/socials/instagram/utils.ts
|
|
118
|
+
var client = createGraphClient("https://graph.instagram.com/v23.0");
|
|
119
|
+
var getInstagramData = client.request;
|
|
120
|
+
|
|
121
|
+
// src/socials/instagram/getters.ts
|
|
122
|
+
var getInstagramUser = (token, id, fields) => {
|
|
123
|
+
return getInstagramData(token, id || "me", {
|
|
124
|
+
params: {
|
|
125
|
+
fields: fields ? fields.join(",") : "username,followers_count,media_count"
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
var getInstagramMedia = (token, fields, limit = 12) => {
|
|
130
|
+
return getInstagramData(token, "me/media", {
|
|
131
|
+
params: {
|
|
132
|
+
fields: fields ? fields.join(",") : "like_count,comments_count,permalink",
|
|
133
|
+
limit
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// src/socials/instagram/adapter.ts
|
|
139
|
+
var PROFILE_FIELDS = [
|
|
140
|
+
"id",
|
|
141
|
+
"user_id",
|
|
142
|
+
"username",
|
|
143
|
+
"name",
|
|
144
|
+
"profile_picture_url",
|
|
145
|
+
"followers_count",
|
|
146
|
+
"media_count"
|
|
147
|
+
];
|
|
148
|
+
var MEDIA_FIELDS = [
|
|
149
|
+
"id",
|
|
150
|
+
"caption",
|
|
151
|
+
"media_type",
|
|
152
|
+
"media_url",
|
|
153
|
+
"permalink",
|
|
154
|
+
"thumbnail_url",
|
|
155
|
+
"timestamp",
|
|
156
|
+
"like_count",
|
|
157
|
+
"comments_count"
|
|
158
|
+
];
|
|
159
|
+
var expiresAt = (seconds) => seconds ? new Date(Date.now() + seconds * 1e3) : void 0;
|
|
160
|
+
function toKind(mediaType) {
|
|
161
|
+
if (mediaType === "VIDEO") return "video" /* Video */;
|
|
162
|
+
if (mediaType === "CAROUSEL_ALBUM") return "carousel" /* Carousel */;
|
|
163
|
+
return "image" /* Image */;
|
|
164
|
+
}
|
|
165
|
+
function toPost(media) {
|
|
166
|
+
const kind = toKind(media.media_type);
|
|
167
|
+
return {
|
|
168
|
+
platform: "instagram" /* Instagram */,
|
|
169
|
+
platformMediaId: media.id,
|
|
170
|
+
permalink: media.permalink,
|
|
171
|
+
// A video's `media_url` is the .mp4 itself, so it is never a stand-in for a
|
|
172
|
+
// missing poster frame — better no thumbnail than one that can't render.
|
|
173
|
+
thumbnail: media.thumbnail_url ?? (kind === "video" /* Video */ ? null : media.media_url ?? null),
|
|
174
|
+
mediaURL: media.media_url ?? null,
|
|
175
|
+
caption: media.caption ?? null,
|
|
176
|
+
kind,
|
|
177
|
+
publishedAt: media.timestamp,
|
|
178
|
+
likes: media.like_count ?? null,
|
|
179
|
+
comments: media.comments_count ?? null,
|
|
180
|
+
// Both need the Insights API; the basic media fields carry neither.
|
|
181
|
+
views: null,
|
|
182
|
+
shares: null,
|
|
183
|
+
metadata: {}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
var InstagramProvider = {
|
|
187
|
+
platform: "instagram" /* Instagram */,
|
|
188
|
+
auth: {
|
|
189
|
+
authorizationURL: ({ state, redirectURI, scopes }) => getInstagramAuthorizationURL(
|
|
190
|
+
state,
|
|
191
|
+
redirectURI,
|
|
192
|
+
scopes
|
|
193
|
+
),
|
|
194
|
+
exchangeCode: async ({ code, redirectURI }) => {
|
|
195
|
+
const result = await getLongLivedInstagramToken(code, redirectURI);
|
|
196
|
+
if (!result) {
|
|
197
|
+
console.error("[naystack] Instagram code exchange returned no token");
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
accessToken: result.accessToken,
|
|
202
|
+
platformUserId: result.userId || null,
|
|
203
|
+
expiresAt: expiresAt(result.expiresIn),
|
|
204
|
+
scopes: result.permissions
|
|
205
|
+
};
|
|
206
|
+
},
|
|
207
|
+
refresh: async ({ accessToken }) => {
|
|
208
|
+
const refreshed = await refreshInstagramAccessToken(accessToken);
|
|
209
|
+
if (!refreshed) return null;
|
|
210
|
+
return {
|
|
211
|
+
accessToken: refreshed.accessToken,
|
|
212
|
+
expiresAt: expiresAt(refreshed.expiresIn)
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
fetchProfile: async (accessToken) => {
|
|
217
|
+
const user = await getInstagramUser(
|
|
218
|
+
accessToken,
|
|
219
|
+
void 0,
|
|
220
|
+
PROFILE_FIELDS
|
|
221
|
+
);
|
|
222
|
+
if (!user?.username) return null;
|
|
223
|
+
return {
|
|
224
|
+
platform: "instagram" /* Instagram */,
|
|
225
|
+
// `user_id` is what the OAuth exchange hands back, so preferring it keeps
|
|
226
|
+
// one account's id identical however it was read.
|
|
227
|
+
platformUserId: user.user_id || user.id || null,
|
|
228
|
+
username: user.username,
|
|
229
|
+
displayName: user.name ?? null,
|
|
230
|
+
avatar: user.profile_picture_url ?? null,
|
|
231
|
+
followers: user.followers_count ?? 0,
|
|
232
|
+
contentCount: user.media_count ?? 0,
|
|
233
|
+
metadata: {}
|
|
234
|
+
};
|
|
235
|
+
},
|
|
236
|
+
fetchMedia: async (accessToken, options) => {
|
|
237
|
+
const result = await getInstagramMedia(
|
|
238
|
+
accessToken,
|
|
239
|
+
MEDIA_FIELDS,
|
|
240
|
+
options?.limit
|
|
241
|
+
);
|
|
242
|
+
if (!result?.data) return [];
|
|
243
|
+
return result.data.map(toPost);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
export {
|
|
247
|
+
InstagramProvider
|
|
248
|
+
};
|