naystack 1.8.19 → 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 -306
- package/dist/auth/index.d.mts +0 -5
- package/dist/auth/index.d.ts +0 -5
- package/dist/auth/index.esm.js +3 -302
- 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 +344 -21
- package/dist/socials/index.d.mts +8 -2
- package/dist/socials/index.d.ts +8 -2
- package/dist/socials/index.esm.js +334 -20
- 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 +33 -17
- 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 +33 -17
- 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 +34 -16
- package/dist/socials/meta/container.d.mts +5 -27
- package/dist/socials/meta/container.d.ts +5 -27
- package/dist/socials/meta/container.esm.js +33 -17
- 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 +33 -17
- 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 +33 -17
- 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 -388
- 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 -359
- package/dist/auth/instagram/route.cjs.js +0 -368
- 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 -343
- package/dist/auth/instagram/utils.d.mts +0 -57
- package/dist/auth/instagram/utils.d.ts +0 -57
|
@@ -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
|
+
};
|
|
@@ -17,14 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
17
|
};
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
|
|
20
|
-
// src/
|
|
21
|
-
var
|
|
22
|
-
__export(
|
|
20
|
+
// src/socials/instagram/auth.ts
|
|
21
|
+
var auth_exports = {};
|
|
22
|
+
__export(auth_exports, {
|
|
23
23
|
getInstagramAuthorizationURL: () => getInstagramAuthorizationURL,
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
getLongLivedInstagramToken: () => getLongLivedInstagramToken,
|
|
25
|
+
refreshInstagramAccessToken: () => refreshInstagramAccessToken
|
|
26
26
|
});
|
|
27
|
-
module.exports = __toCommonJS(
|
|
27
|
+
module.exports = __toCommonJS(auth_exports);
|
|
28
28
|
|
|
29
29
|
// src/env.ts
|
|
30
30
|
var EXTRA_ENV = globalThis.__NAYSTACK_ENV__ || (globalThis.__NAYSTACK_ENV__ = {});
|
|
@@ -36,8 +36,8 @@ var getEnvValue = (key) => {
|
|
|
36
36
|
return process.env.NEXT_PUBLIC_EMAIL_AUTH_ENDPOINT || EXTRA_ENV.EMAIL_AUTH_ENDPOINT;
|
|
37
37
|
case "NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT" /* NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT */:
|
|
38
38
|
return process.env.NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT || EXTRA_ENV.GOOGLE_AUTH_ENDPOINT;
|
|
39
|
-
case "
|
|
40
|
-
return process.env.
|
|
39
|
+
case "NEXT_PUBLIC_SOCIAL_AUTH_ENDPOINT" /* NEXT_PUBLIC_SOCIAL_AUTH_ENDPOINT */:
|
|
40
|
+
return process.env.NEXT_PUBLIC_SOCIAL_AUTH_ENDPOINT || EXTRA_ENV.SOCIAL_AUTH_ENDPOINT;
|
|
41
41
|
case "NEXT_PUBLIC_FILE_ENDPOINT" /* NEXT_PUBLIC_FILE_ENDPOINT */:
|
|
42
42
|
return process.env.NEXT_PUBLIC_FILE_ENDPOINT || EXTRA_ENV.FILE_ENDPOINT;
|
|
43
43
|
case "NEXT_PUBLIC_BASE_URL" /* NEXT_PUBLIC_BASE_URL */:
|
|
@@ -76,48 +76,50 @@ function getEnv(key, skipCheck) {
|
|
|
76
76
|
return value;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
// src/
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const response = await request.json();
|
|
85
|
-
return response.access_token;
|
|
86
|
-
}
|
|
87
|
-
async function getLongLivedToken(code, redirectURL, clientId, clientSecret) {
|
|
79
|
+
// src/socials/instagram/auth.ts
|
|
80
|
+
var getInstagramAuthorizationURL = (state, redirectURI, scopes = ["instagram_business_basic"]) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
|
|
81
|
+
"INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */
|
|
82
|
+
)}&response_type=code&enable_fb_login=0&force_authentication=1&scope=${scopes.join(",")}&state=${state}&redirect_uri=${redirectURI}#weblink`;
|
|
83
|
+
async function getLongLivedInstagramToken(code, redirectURI) {
|
|
88
84
|
const formData = new FormData();
|
|
89
|
-
formData.set("client_id",
|
|
90
|
-
formData.set("client_secret",
|
|
85
|
+
formData.set("client_id", getEnv("INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */));
|
|
86
|
+
formData.set("client_secret", getEnv("INSTAGRAM_CLIENT_SECRET" /* INSTAGRAM_CLIENT_SECRET */));
|
|
91
87
|
formData.set("grant_type", "authorization_code");
|
|
92
|
-
formData.set("redirect_uri",
|
|
88
|
+
formData.set("redirect_uri", redirectURI);
|
|
93
89
|
formData.set("code", code);
|
|
94
90
|
const shortRes = await fetch("https://api.instagram.com/oauth/access_token", {
|
|
95
91
|
method: "POST",
|
|
96
92
|
body: formData
|
|
97
93
|
});
|
|
98
|
-
if (shortRes.ok)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
94
|
+
if (!shortRes.ok) return void 0;
|
|
95
|
+
const shortResData = await shortRes.json();
|
|
96
|
+
if (!shortResData.access_token) return void 0;
|
|
97
|
+
const longRes = await fetch(
|
|
98
|
+
`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 */)}`
|
|
99
|
+
);
|
|
100
|
+
if (!longRes.ok) return void 0;
|
|
101
|
+
const longResData = await longRes.json();
|
|
102
|
+
return {
|
|
103
|
+
accessToken: longResData.access_token,
|
|
104
|
+
userId: shortResData.user_id,
|
|
105
|
+
expiresIn: longResData.expires_in,
|
|
106
|
+
permissions: shortResData.permissions
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
async function refreshInstagramAccessToken(token) {
|
|
110
|
+
const request = await fetch(
|
|
111
|
+
`https://graph.instagram.com/refresh_access_token?grant_type=ig_refresh_token&access_token=${token}`
|
|
112
|
+
);
|
|
113
|
+
const response = await request.json();
|
|
114
|
+
if (!response.access_token) return void 0;
|
|
115
|
+
return {
|
|
116
|
+
accessToken: response.access_token,
|
|
117
|
+
expiresIn: response.expires_in
|
|
118
|
+
};
|
|
114
119
|
}
|
|
115
|
-
var getInstagramAuthorizationURL = (token, scopes = ["instagram_business_basic"]) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
|
|
116
|
-
"INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */
|
|
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
120
|
// Annotate the CommonJS export names for ESM import in node:
|
|
119
121
|
0 && (module.exports = {
|
|
120
122
|
getInstagramAuthorizationURL,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
getLongLivedInstagramToken,
|
|
124
|
+
refreshInstagramAccessToken
|
|
123
125
|
});
|
|
@@ -0,0 +1,52 @@
|
|
|
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 Socials
|
|
9
|
+
*/
|
|
10
|
+
type InstagramScope = "instagram_business_basic" | "instagram_business_content_publish" | "instagram_business_manage_messages" | "instagram_business_manage_comments" | "instagram_business_manage_insights";
|
|
11
|
+
/**
|
|
12
|
+
* Builds the Instagram OAuth authorization URL.
|
|
13
|
+
*
|
|
14
|
+
* **Server-only** — reads the non-public `INSTAGRAM_CLIENT_ID`. The client id still
|
|
15
|
+
* ends up in the URL the browser follows; it is simply built here rather than
|
|
16
|
+
* shipped in the client bundle.
|
|
17
|
+
*
|
|
18
|
+
* @param state - Passed back by Instagram on the callback; typically the logged-in user's access token.
|
|
19
|
+
* @param redirectURI - Where Instagram sends the user back. Must match a URI registered with Meta verbatim.
|
|
20
|
+
* @param scopes - Default: `["instagram_business_basic"]`.
|
|
21
|
+
*
|
|
22
|
+
* @category Socials
|
|
23
|
+
*/
|
|
24
|
+
declare const getInstagramAuthorizationURL: (state: string, redirectURI: string, scopes?: InstagramScope[]) => string;
|
|
25
|
+
/**
|
|
26
|
+
* Exchanges an OAuth code for a long-lived Instagram access token.
|
|
27
|
+
*
|
|
28
|
+
* @param code - Authorization code from the callback.
|
|
29
|
+
* @param redirectURI - The same URI used to obtain the code.
|
|
30
|
+
* @returns `{ accessToken, userId, expiresIn, permissions }`, or `undefined` if either exchange failed.
|
|
31
|
+
*
|
|
32
|
+
* @category Socials
|
|
33
|
+
*/
|
|
34
|
+
declare function getLongLivedInstagramToken(code: string, redirectURI: string): Promise<{
|
|
35
|
+
accessToken: string;
|
|
36
|
+
userId: string | undefined;
|
|
37
|
+
expiresIn: number;
|
|
38
|
+
permissions: string[] | undefined;
|
|
39
|
+
} | undefined>;
|
|
40
|
+
/**
|
|
41
|
+
* Refreshes a long-lived Instagram token.
|
|
42
|
+
*
|
|
43
|
+
* @returns `{ accessToken, expiresIn }` (seconds), or `undefined` if the refresh failed.
|
|
44
|
+
*
|
|
45
|
+
* @category Socials
|
|
46
|
+
*/
|
|
47
|
+
declare function refreshInstagramAccessToken(token: string): Promise<{
|
|
48
|
+
accessToken: string;
|
|
49
|
+
expiresIn: number | undefined;
|
|
50
|
+
} | undefined>;
|
|
51
|
+
|
|
52
|
+
export { type InstagramScope, getInstagramAuthorizationURL, getLongLivedInstagramToken, refreshInstagramAccessToken };
|
|
@@ -0,0 +1,52 @@
|
|
|
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 Socials
|
|
9
|
+
*/
|
|
10
|
+
type InstagramScope = "instagram_business_basic" | "instagram_business_content_publish" | "instagram_business_manage_messages" | "instagram_business_manage_comments" | "instagram_business_manage_insights";
|
|
11
|
+
/**
|
|
12
|
+
* Builds the Instagram OAuth authorization URL.
|
|
13
|
+
*
|
|
14
|
+
* **Server-only** — reads the non-public `INSTAGRAM_CLIENT_ID`. The client id still
|
|
15
|
+
* ends up in the URL the browser follows; it is simply built here rather than
|
|
16
|
+
* shipped in the client bundle.
|
|
17
|
+
*
|
|
18
|
+
* @param state - Passed back by Instagram on the callback; typically the logged-in user's access token.
|
|
19
|
+
* @param redirectURI - Where Instagram sends the user back. Must match a URI registered with Meta verbatim.
|
|
20
|
+
* @param scopes - Default: `["instagram_business_basic"]`.
|
|
21
|
+
*
|
|
22
|
+
* @category Socials
|
|
23
|
+
*/
|
|
24
|
+
declare const getInstagramAuthorizationURL: (state: string, redirectURI: string, scopes?: InstagramScope[]) => string;
|
|
25
|
+
/**
|
|
26
|
+
* Exchanges an OAuth code for a long-lived Instagram access token.
|
|
27
|
+
*
|
|
28
|
+
* @param code - Authorization code from the callback.
|
|
29
|
+
* @param redirectURI - The same URI used to obtain the code.
|
|
30
|
+
* @returns `{ accessToken, userId, expiresIn, permissions }`, or `undefined` if either exchange failed.
|
|
31
|
+
*
|
|
32
|
+
* @category Socials
|
|
33
|
+
*/
|
|
34
|
+
declare function getLongLivedInstagramToken(code: string, redirectURI: string): Promise<{
|
|
35
|
+
accessToken: string;
|
|
36
|
+
userId: string | undefined;
|
|
37
|
+
expiresIn: number;
|
|
38
|
+
permissions: string[] | undefined;
|
|
39
|
+
} | undefined>;
|
|
40
|
+
/**
|
|
41
|
+
* Refreshes a long-lived Instagram token.
|
|
42
|
+
*
|
|
43
|
+
* @returns `{ accessToken, expiresIn }` (seconds), or `undefined` if the refresh failed.
|
|
44
|
+
*
|
|
45
|
+
* @category Socials
|
|
46
|
+
*/
|
|
47
|
+
declare function refreshInstagramAccessToken(token: string): Promise<{
|
|
48
|
+
accessToken: string;
|
|
49
|
+
expiresIn: number | undefined;
|
|
50
|
+
} | undefined>;
|
|
51
|
+
|
|
52
|
+
export { type InstagramScope, getInstagramAuthorizationURL, getLongLivedInstagramToken, refreshInstagramAccessToken };
|
|
@@ -8,8 +8,8 @@ var getEnvValue = (key) => {
|
|
|
8
8
|
return process.env.NEXT_PUBLIC_EMAIL_AUTH_ENDPOINT || EXTRA_ENV.EMAIL_AUTH_ENDPOINT;
|
|
9
9
|
case "NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT" /* NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT */:
|
|
10
10
|
return process.env.NEXT_PUBLIC_GOOGLE_AUTH_ENDPOINT || EXTRA_ENV.GOOGLE_AUTH_ENDPOINT;
|
|
11
|
-
case "
|
|
12
|
-
return process.env.
|
|
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
13
|
case "NEXT_PUBLIC_FILE_ENDPOINT" /* NEXT_PUBLIC_FILE_ENDPOINT */:
|
|
14
14
|
return process.env.NEXT_PUBLIC_FILE_ENDPOINT || EXTRA_ENV.FILE_ENDPOINT;
|
|
15
15
|
case "NEXT_PUBLIC_BASE_URL" /* NEXT_PUBLIC_BASE_URL */:
|
|
@@ -48,47 +48,49 @@ function getEnv(key, skipCheck) {
|
|
|
48
48
|
return value;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
// src/
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const response = await request.json();
|
|
57
|
-
return response.access_token;
|
|
58
|
-
}
|
|
59
|
-
async function getLongLivedToken(code, redirectURL, clientId, clientSecret) {
|
|
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) {
|
|
60
56
|
const formData = new FormData();
|
|
61
|
-
formData.set("client_id",
|
|
62
|
-
formData.set("client_secret",
|
|
57
|
+
formData.set("client_id", getEnv("INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */));
|
|
58
|
+
formData.set("client_secret", getEnv("INSTAGRAM_CLIENT_SECRET" /* INSTAGRAM_CLIENT_SECRET */));
|
|
63
59
|
formData.set("grant_type", "authorization_code");
|
|
64
|
-
formData.set("redirect_uri",
|
|
60
|
+
formData.set("redirect_uri", redirectURI);
|
|
65
61
|
formData.set("code", code);
|
|
66
62
|
const shortRes = await fetch("https://api.instagram.com/oauth/access_token", {
|
|
67
63
|
method: "POST",
|
|
68
64
|
body: formData
|
|
69
65
|
});
|
|
70
|
-
if (shortRes.ok)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
+
};
|
|
86
91
|
}
|
|
87
|
-
var getInstagramAuthorizationURL = (token, scopes = ["instagram_business_basic"]) => `https://www.instagram.com/oauth/authorize/?client_id=${getEnv(
|
|
88
|
-
"INSTAGRAM_CLIENT_ID" /* INSTAGRAM_CLIENT_ID */
|
|
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
92
|
export {
|
|
91
93
|
getInstagramAuthorizationURL,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
+
getLongLivedInstagramToken,
|
|
95
|
+
refreshInstagramAccessToken
|
|
94
96
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { GraphError, ContainerState } from '../meta/types.mjs';
|
|
2
2
|
import { InstagramConversation, InstagramMedia, InstagramMessage, InstagramUser } from './types.mjs';
|
|
3
3
|
import '../meta/container.mjs';
|
|
4
|
+
import '../utils/poll.mjs';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Fetches an Instagram user's profile. Defaults to the authenticated user (`"me"`).
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { GraphError, ContainerState } from '../meta/types.js';
|
|
2
2
|
import { InstagramConversation, InstagramMedia, InstagramMessage, InstagramUser } from './types.js';
|
|
3
3
|
import '../meta/container.js';
|
|
4
|
+
import '../utils/poll.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Fetches an Instagram user's profile. Defaults to the authenticated user (`"me"`).
|