@insforge/sdk 1.4.2 → 1.4.4
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 +141 -135
- package/SDK-REFERENCE.md +146 -168
- package/dist/{client-BR9o-WUm.d.ts → client-BZyLCjO1.d.mts} +62 -122
- package/dist/{client-C-qBRoea.d.mts → client-Dh7GOydb.d.ts} +62 -122
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +585 -428
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +588 -428
- package/dist/index.mjs.map +1 -1
- package/dist/{middleware-Tu_RlUAt.d.ts → middleware-CicBLgnO.d.ts} +1 -1
- package/dist/{middleware-K59XjpUX.d.mts → middleware-o3zPqPvY.d.mts} +1 -1
- package/dist/ssr/middleware.d.mts +2 -2
- package/dist/ssr/middleware.d.ts +2 -2
- package/dist/ssr/middleware.js +71 -52
- package/dist/ssr/middleware.js.map +1 -1
- package/dist/ssr/middleware.mjs +71 -52
- package/dist/ssr/middleware.mjs.map +1 -1
- package/dist/ssr.d.mts +4 -4
- package/dist/ssr.d.ts +4 -4
- package/dist/ssr.js +680 -544
- package/dist/ssr.js.map +1 -1
- package/dist/ssr.mjs +684 -545
- package/dist/ssr.mjs.map +1 -1
- package/dist/{types-Dk-44JJf.d.mts → types-MKmYAYeg.d.mts} +12 -0
- package/dist/{types-Dk-44JJf.d.ts → types-MKmYAYeg.d.ts} +12 -0
- package/package.json +12 -5
package/README.md
CHANGED
|
@@ -33,11 +33,11 @@ yarn add @insforge/sdk
|
|
|
33
33
|
### Initialize the Client
|
|
34
34
|
|
|
35
35
|
```javascript
|
|
36
|
-
import { createClient } from
|
|
36
|
+
import { createClient } from '@insforge/sdk';
|
|
37
37
|
|
|
38
38
|
const insforge = createClient({
|
|
39
|
-
baseUrl:
|
|
40
|
-
anonKey:
|
|
39
|
+
baseUrl: 'http://localhost:7130', // Your InsForge backend URL
|
|
40
|
+
anonKey: 'your-anon-key', // Optional public anon key
|
|
41
41
|
});
|
|
42
42
|
```
|
|
43
43
|
|
|
@@ -46,10 +46,10 @@ const insforge = createClient({
|
|
|
46
46
|
Use `createAdminClient()` only in trusted server code that needs project-admin privileges:
|
|
47
47
|
|
|
48
48
|
```typescript
|
|
49
|
-
import { createAdminClient } from
|
|
49
|
+
import { createAdminClient } from '@insforge/sdk';
|
|
50
50
|
|
|
51
51
|
const admin = createAdminClient({
|
|
52
|
-
baseUrl:
|
|
52
|
+
baseUrl: 'http://localhost:7130',
|
|
53
53
|
apiKey: process.env.INSFORGE_API_KEY!,
|
|
54
54
|
});
|
|
55
55
|
```
|
|
@@ -62,7 +62,7 @@ In edge functions or other server code that receives a user's JWT, seed the clie
|
|
|
62
62
|
|
|
63
63
|
```javascript
|
|
64
64
|
const insforge = createClient({
|
|
65
|
-
baseUrl:
|
|
65
|
+
baseUrl: 'http://localhost:7130',
|
|
66
66
|
accessToken: userJwt, // e.g. from the request's Authorization header
|
|
67
67
|
});
|
|
68
68
|
```
|
|
@@ -74,22 +74,22 @@ All requests run as that user (RLS applies). The token is used as-is — the SDK
|
|
|
74
74
|
```javascript
|
|
75
75
|
// Sign up a new user
|
|
76
76
|
const { data, error } = await insforge.auth.signUp({
|
|
77
|
-
email:
|
|
78
|
-
password:
|
|
79
|
-
name:
|
|
80
|
-
redirectTo:
|
|
77
|
+
email: 'user@example.com',
|
|
78
|
+
password: 'securePassword123',
|
|
79
|
+
name: 'John Doe', // optional
|
|
80
|
+
redirectTo: 'http://localhost:3000/sign-in', // optional, recommended for link-based verification
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
// Sign in with email/password
|
|
84
84
|
const { data, error } = await insforge.auth.signInWithPassword({
|
|
85
|
-
email:
|
|
86
|
-
password:
|
|
85
|
+
email: 'user@example.com',
|
|
86
|
+
password: 'securePassword123',
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
// OAuth authentication (built-in or custom provider key)
|
|
90
|
-
await insforge.auth.signInWithOAuth(
|
|
91
|
-
redirectTo:
|
|
92
|
-
additionalParams: { prompt:
|
|
90
|
+
await insforge.auth.signInWithOAuth('google', {
|
|
91
|
+
redirectTo: 'http://localhost:3000/dashboard',
|
|
92
|
+
additionalParams: { prompt: 'select_account' },
|
|
93
93
|
});
|
|
94
94
|
// additionalParams is for provider-specific hints only.
|
|
95
95
|
// Do not pass client_id, scope, redirect_uri, code_challenge, state, or response_type;
|
|
@@ -99,13 +99,13 @@ await insforge.auth.signInWithOAuth("google", {
|
|
|
99
99
|
const { data: currentUser } = await insforge.auth.getCurrentUser();
|
|
100
100
|
|
|
101
101
|
// Get any user's profile by ID (public endpoint)
|
|
102
|
-
const { data: profile, error } = await insforge.auth.getProfile(
|
|
102
|
+
const { data: profile, error } = await insforge.auth.getProfile('user-id-here');
|
|
103
103
|
|
|
104
104
|
// Update current user's profile (requires authentication)
|
|
105
105
|
const { data: updatedProfile, error } = await insforge.auth.setProfile({
|
|
106
|
-
displayName:
|
|
107
|
-
bio:
|
|
108
|
-
avatarUrl:
|
|
106
|
+
displayName: 'John Doe',
|
|
107
|
+
bio: 'Software developer',
|
|
108
|
+
avatarUrl: 'https://example.com/avatar.jpg',
|
|
109
109
|
});
|
|
110
110
|
|
|
111
111
|
// Sign out
|
|
@@ -117,31 +117,31 @@ await insforge.auth.signOut();
|
|
|
117
117
|
```javascript
|
|
118
118
|
// Resend a verification email
|
|
119
119
|
await insforge.auth.resendVerificationEmail({
|
|
120
|
-
email:
|
|
121
|
-
redirectTo:
|
|
120
|
+
email: 'user@example.com',
|
|
121
|
+
redirectTo: 'http://localhost:3000/sign-in', // optional, recommended for link-based verification
|
|
122
122
|
});
|
|
123
123
|
|
|
124
124
|
// Verify email with a 6-digit code
|
|
125
125
|
await insforge.auth.verifyEmail({
|
|
126
|
-
email:
|
|
127
|
-
otp:
|
|
126
|
+
email: 'user@example.com',
|
|
127
|
+
otp: '123456',
|
|
128
128
|
});
|
|
129
129
|
|
|
130
130
|
// Send password reset email
|
|
131
131
|
await insforge.auth.sendResetPasswordEmail({
|
|
132
|
-
email:
|
|
133
|
-
redirectTo:
|
|
132
|
+
email: 'user@example.com',
|
|
133
|
+
redirectTo: 'http://localhost:3000/reset-password', // optional, recommended for link-based reset
|
|
134
134
|
});
|
|
135
135
|
|
|
136
136
|
// Code-based reset flow: exchange the code, then reset the password
|
|
137
137
|
const { data: resetToken } = await insforge.auth.exchangeResetPasswordToken({
|
|
138
|
-
email:
|
|
139
|
-
code:
|
|
138
|
+
email: 'user@example.com',
|
|
139
|
+
code: '123456',
|
|
140
140
|
});
|
|
141
141
|
|
|
142
142
|
if (resetToken) {
|
|
143
143
|
await insforge.auth.resetPassword({
|
|
144
|
-
newPassword:
|
|
144
|
+
newPassword: 'newSecurePassword123',
|
|
145
145
|
otp: resetToken.token,
|
|
146
146
|
});
|
|
147
147
|
}
|
|
@@ -163,55 +163,67 @@ Those backend endpoints validate the token first, then redirect the browser to y
|
|
|
163
163
|
```javascript
|
|
164
164
|
// Insert data
|
|
165
165
|
const { data, error } = await insforge.database
|
|
166
|
-
.from(
|
|
167
|
-
.insert([{ title:
|
|
166
|
+
.from('posts')
|
|
167
|
+
.insert([{ title: 'My First Post', content: 'Hello World!' }]);
|
|
168
168
|
|
|
169
169
|
// Query data
|
|
170
|
-
const { data, error } = await insforge.database
|
|
171
|
-
.from("posts")
|
|
172
|
-
.select("*")
|
|
173
|
-
.eq("author_id", userId);
|
|
170
|
+
const { data, error } = await insforge.database.from('posts').select('*').eq('author_id', userId);
|
|
174
171
|
|
|
175
172
|
// Update data
|
|
176
173
|
const { data, error } = await insforge.database
|
|
177
|
-
.from(
|
|
178
|
-
.update({ title:
|
|
179
|
-
.eq(
|
|
174
|
+
.from('posts')
|
|
175
|
+
.update({ title: 'Updated Title' })
|
|
176
|
+
.eq('id', postId);
|
|
180
177
|
|
|
181
178
|
// Delete data
|
|
182
|
-
const { data, error } = await insforge.database
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
179
|
+
const { data, error } = await insforge.database.from('posts').delete().eq('id', postId);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
#### Selecting a schema
|
|
183
|
+
|
|
184
|
+
Queries hit the `public` schema by default. Target a custom schema by chaining `.schema()` — the table name stays bare (it maps to PostgREST's `Accept-Profile`/`Content-Profile` header):
|
|
185
|
+
|
|
186
|
+
```javascript
|
|
187
|
+
const { data } = await insforge.database.schema('analytics').from('events').select('*');
|
|
188
|
+
|
|
189
|
+
await insforge.database.schema('analytics').rpc('rollup', { day: '2026-01-01' });
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Or set a default schema for every query when creating the client:
|
|
193
|
+
|
|
194
|
+
```javascript
|
|
195
|
+
const insforge = createClient({
|
|
196
|
+
baseUrl: '...',
|
|
197
|
+
anonKey: '...',
|
|
198
|
+
db: { schema: 'analytics' },
|
|
199
|
+
});
|
|
186
200
|
```
|
|
187
201
|
|
|
202
|
+
The schema must be exposed by the backend, and access is still gated by grants + RLS like `public`. Older backends that don't expose the schema return PostgREST `PGRST106` rather than silently falling back.
|
|
203
|
+
|
|
188
204
|
### File Storage
|
|
189
205
|
|
|
190
206
|
```javascript
|
|
191
207
|
// Upload a file
|
|
192
208
|
const file = document.querySelector('input[type="file"]').files[0];
|
|
193
|
-
const { data, error } = await insforge.storage.from(
|
|
209
|
+
const { data, error } = await insforge.storage.from('avatars').upload(file);
|
|
194
210
|
|
|
195
211
|
// Download a file
|
|
196
|
-
const { data, error } = await insforge.storage
|
|
197
|
-
.from("avatars")
|
|
198
|
-
.download("user-avatar.png");
|
|
212
|
+
const { data, error } = await insforge.storage.from('avatars').download('user-avatar.png');
|
|
199
213
|
|
|
200
214
|
// Delete a file
|
|
201
|
-
const { data, error } = await insforge.storage
|
|
202
|
-
.from("avatars")
|
|
203
|
-
.remove(["user-avatar.png"]);
|
|
215
|
+
const { data, error } = await insforge.storage.from('avatars').remove(['user-avatar.png']);
|
|
204
216
|
|
|
205
217
|
// List files
|
|
206
|
-
const { data, error } = await insforge.storage.from(
|
|
218
|
+
const { data, error } = await insforge.storage.from('avatars').list();
|
|
207
219
|
```
|
|
208
220
|
|
|
209
221
|
### Edge Functions
|
|
210
222
|
|
|
211
223
|
```javascript
|
|
212
224
|
// Invoke an edge function
|
|
213
|
-
const { data, error } = await insforge.functions.invoke(
|
|
214
|
-
body: { key:
|
|
225
|
+
const { data, error } = await insforge.functions.invoke('my-function', {
|
|
226
|
+
body: { key: 'value' },
|
|
215
227
|
});
|
|
216
228
|
```
|
|
217
229
|
|
|
@@ -219,60 +231,58 @@ const { data, error } = await insforge.functions.invoke("my-function", {
|
|
|
219
231
|
|
|
220
232
|
```javascript
|
|
221
233
|
// Create and redirect to a Stripe Checkout Session
|
|
222
|
-
const { data, error } = await insforge.payments.stripe.createCheckoutSession(
|
|
223
|
-
|
|
224
|
-
{
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
idempotencyKey: "cart_123",
|
|
230
|
-
},
|
|
231
|
-
);
|
|
234
|
+
const { data, error } = await insforge.payments.stripe.createCheckoutSession('test', {
|
|
235
|
+
mode: 'payment',
|
|
236
|
+
lineItems: [{ priceId: 'price_123', quantity: 1 }],
|
|
237
|
+
successUrl: `${window.location.origin}/success`,
|
|
238
|
+
cancelUrl: `${window.location.origin}/pricing`,
|
|
239
|
+
idempotencyKey: 'cart_123',
|
|
240
|
+
});
|
|
232
241
|
|
|
233
242
|
if (!error && data?.checkoutSession.url) {
|
|
234
243
|
window.location.assign(data.checkoutSession.url);
|
|
235
244
|
}
|
|
236
245
|
|
|
237
246
|
// Create a subscription checkout for an app billing subject
|
|
238
|
-
const { data: subscriptionCheckout } =
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
247
|
+
const { data: subscriptionCheckout } = await insforge.payments.stripe.createCheckoutSession(
|
|
248
|
+
'test',
|
|
249
|
+
{
|
|
250
|
+
mode: 'subscription',
|
|
251
|
+
subject: { type: 'team', id: 'team_123' },
|
|
252
|
+
lineItems: [{ priceId: 'price_monthly_123', quantity: 1 }],
|
|
243
253
|
successUrl: `${window.location.origin}/billing/success`,
|
|
244
254
|
cancelUrl: `${window.location.origin}/billing`,
|
|
245
|
-
}
|
|
255
|
+
}
|
|
256
|
+
);
|
|
246
257
|
|
|
247
258
|
if (subscriptionCheckout?.checkoutSession.url) {
|
|
248
259
|
window.location.assign(subscriptionCheckout.checkoutSession.url);
|
|
249
260
|
}
|
|
250
261
|
|
|
251
262
|
// Let an authenticated customer manage their subscription in Stripe Billing Portal
|
|
252
|
-
const { data: portal } =
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
});
|
|
263
|
+
const { data: portal } = await insforge.payments.stripe.createCustomerPortalSession('test', {
|
|
264
|
+
subject: { type: 'team', id: 'team_123' },
|
|
265
|
+
returnUrl: `${window.location.origin}/billing`,
|
|
266
|
+
});
|
|
257
267
|
|
|
258
268
|
if (portal?.customerPortalSession.url) {
|
|
259
269
|
window.location.assign(portal.customerPortalSession.url);
|
|
260
270
|
}
|
|
261
271
|
|
|
262
272
|
// Create a Razorpay order, then pass checkoutOptions to Razorpay Checkout.js
|
|
263
|
-
const { data: order } = await insforge.payments.razorpay.createOrder(
|
|
273
|
+
const { data: order } = await insforge.payments.razorpay.createOrder('test', {
|
|
264
274
|
amount: 200000,
|
|
265
|
-
currency:
|
|
266
|
-
subject: { type:
|
|
267
|
-
customerEmail:
|
|
268
|
-
notes: { order_id:
|
|
275
|
+
currency: 'INR',
|
|
276
|
+
subject: { type: 'team', id: 'team_123' },
|
|
277
|
+
customerEmail: 'ada@example.com',
|
|
278
|
+
notes: { order_id: 'order_123' },
|
|
269
279
|
});
|
|
270
280
|
|
|
271
281
|
if (order) {
|
|
272
282
|
const checkout = new Razorpay({
|
|
273
283
|
...order.checkoutOptions,
|
|
274
284
|
handler: async (response) => {
|
|
275
|
-
await insforge.payments.razorpay.verifyOrder(
|
|
285
|
+
await insforge.payments.razorpay.verifyOrder('test', {
|
|
276
286
|
orderId: response.razorpay_order_id,
|
|
277
287
|
paymentId: response.razorpay_payment_id,
|
|
278
288
|
signature: response.razorpay_signature,
|
|
@@ -283,13 +293,12 @@ if (order) {
|
|
|
283
293
|
}
|
|
284
294
|
|
|
285
295
|
// Create a Razorpay subscription checkout for an app billing subject
|
|
286
|
-
const { data: subscription } =
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
});
|
|
296
|
+
const { data: subscription } = await insforge.payments.razorpay.createSubscription('test', {
|
|
297
|
+
planId: 'plan_123',
|
|
298
|
+
totalCount: 12,
|
|
299
|
+
subject: { type: 'team', id: 'team_123' },
|
|
300
|
+
notes: { order_id: 'order_123' },
|
|
301
|
+
});
|
|
293
302
|
```
|
|
294
303
|
|
|
295
304
|
### AI Integration
|
|
@@ -299,32 +308,32 @@ AI methods return an OpenAI-like response object directly (not the `{ data, erro
|
|
|
299
308
|
```javascript
|
|
300
309
|
// Chat completion
|
|
301
310
|
const completion = await insforge.ai.chat.completions.create({
|
|
302
|
-
model:
|
|
303
|
-
messages: [{ role:
|
|
311
|
+
model: 'anthropic/claude-3.5-haiku',
|
|
312
|
+
messages: [{ role: 'user', content: 'Write a hello world program' }],
|
|
304
313
|
});
|
|
305
314
|
console.log(completion.choices[0].message.content);
|
|
306
315
|
|
|
307
316
|
// Streaming chat — returns an async iterable of chunks
|
|
308
317
|
const stream = await insforge.ai.chat.completions.create({
|
|
309
|
-
model:
|
|
310
|
-
messages: [{ role:
|
|
318
|
+
model: 'anthropic/claude-3.5-haiku',
|
|
319
|
+
messages: [{ role: 'user', content: 'Tell me a story' }],
|
|
311
320
|
stream: true,
|
|
312
321
|
});
|
|
313
322
|
for await (const chunk of stream) {
|
|
314
|
-
process.stdout.write(chunk.choices[0]?.delta?.content ??
|
|
323
|
+
process.stdout.write(chunk.choices[0]?.delta?.content ?? '');
|
|
315
324
|
}
|
|
316
325
|
|
|
317
326
|
// Generate an image — returns base64-encoded image data
|
|
318
327
|
const image = await insforge.ai.images.generate({
|
|
319
|
-
model:
|
|
320
|
-
prompt:
|
|
328
|
+
model: 'google/gemini-3-pro-image-preview',
|
|
329
|
+
prompt: 'A sunset over mountains',
|
|
321
330
|
});
|
|
322
331
|
const base64Png = image.data[0].b64_json;
|
|
323
332
|
|
|
324
333
|
// Create embeddings
|
|
325
334
|
const embeddings = await insforge.ai.embeddings.create({
|
|
326
|
-
model:
|
|
327
|
-
input:
|
|
335
|
+
model: 'openai/text-embedding-3-small',
|
|
336
|
+
input: 'Hello world',
|
|
328
337
|
});
|
|
329
338
|
console.log(embeddings.data[0].embedding); // number[]
|
|
330
339
|
```
|
|
@@ -342,8 +351,8 @@ The SDK supports the following configuration options:
|
|
|
342
351
|
|
|
343
352
|
```javascript
|
|
344
353
|
const insforge = createClient({
|
|
345
|
-
baseUrl:
|
|
346
|
-
anonKey:
|
|
354
|
+
baseUrl: 'http://localhost:7130', // Your InsForge backend URL
|
|
355
|
+
anonKey: 'your-anon-key', // Optional
|
|
347
356
|
});
|
|
348
357
|
```
|
|
349
358
|
|
|
@@ -361,7 +370,7 @@ By default, the SSR helpers use:
|
|
|
361
370
|
|
|
362
371
|
```typescript
|
|
363
372
|
// app/lib/insforge/client.ts
|
|
364
|
-
import { createBrowserClient } from
|
|
373
|
+
import { createBrowserClient } from '@insforge/sdk/ssr';
|
|
365
374
|
|
|
366
375
|
export const insforge = createBrowserClient();
|
|
367
376
|
```
|
|
@@ -373,8 +382,8 @@ server so the app can write server-owned auth cookies.
|
|
|
373
382
|
|
|
374
383
|
```typescript
|
|
375
384
|
// app/lib/insforge/server.ts
|
|
376
|
-
import { cookies } from
|
|
377
|
-
import { createServerClient } from
|
|
385
|
+
import { cookies } from 'next/headers';
|
|
386
|
+
import { createServerClient } from '@insforge/sdk/ssr';
|
|
378
387
|
|
|
379
388
|
export async function createInsForgeServerClient() {
|
|
380
389
|
return createServerClient({ cookies: await cookies() });
|
|
@@ -383,7 +392,7 @@ export async function createInsForgeServerClient() {
|
|
|
383
392
|
|
|
384
393
|
```typescript
|
|
385
394
|
// app/api/auth/refresh/route.ts
|
|
386
|
-
import { createRefreshAuthRouter } from
|
|
395
|
+
import { createRefreshAuthRouter } from '@insforge/sdk/ssr';
|
|
387
396
|
|
|
388
397
|
export const { POST } = createRefreshAuthRouter();
|
|
389
398
|
```
|
|
@@ -395,17 +404,17 @@ so access and refresh tokens stay server-owned.
|
|
|
395
404
|
|
|
396
405
|
```typescript
|
|
397
406
|
// app/actions.ts
|
|
398
|
-
|
|
407
|
+
'use server';
|
|
399
408
|
|
|
400
|
-
import { cookies } from
|
|
401
|
-
import { createAuthActions } from
|
|
409
|
+
import { cookies } from 'next/headers';
|
|
410
|
+
import { createAuthActions } from '@insforge/sdk/ssr';
|
|
402
411
|
|
|
403
412
|
export async function signIn(formData: FormData) {
|
|
404
413
|
const auth = createAuthActions({ cookies: await cookies() });
|
|
405
414
|
|
|
406
415
|
const { data, error } = await auth.signInWithPassword({
|
|
407
|
-
email: String(formData.get(
|
|
408
|
-
password: String(formData.get(
|
|
416
|
+
email: String(formData.get('email')),
|
|
417
|
+
password: String(formData.get('password')),
|
|
409
418
|
});
|
|
410
419
|
|
|
411
420
|
return { user: data?.user ?? null, error };
|
|
@@ -418,32 +427,29 @@ verifier in an httpOnly app cookie and exchange the callback code with
|
|
|
418
427
|
|
|
419
428
|
```typescript
|
|
420
429
|
// app/actions.ts
|
|
421
|
-
|
|
430
|
+
'use server';
|
|
422
431
|
|
|
423
|
-
import { cookies } from
|
|
424
|
-
import { redirect } from
|
|
425
|
-
import { createAuthActions } from
|
|
432
|
+
import { cookies } from 'next/headers';
|
|
433
|
+
import { redirect } from 'next/navigation';
|
|
434
|
+
import { createAuthActions } from '@insforge/sdk/ssr';
|
|
426
435
|
|
|
427
436
|
export async function signInWithGoogle() {
|
|
428
437
|
const cookieStore = await cookies();
|
|
429
438
|
const auth = createAuthActions({ cookies: cookieStore });
|
|
430
|
-
const { data, error } = await auth.signInWithOAuth(
|
|
431
|
-
redirectTo: new URL(
|
|
432
|
-
"/api/auth/callback",
|
|
433
|
-
process.env.NEXT_PUBLIC_APP_URL
|
|
434
|
-
).toString(),
|
|
439
|
+
const { data, error } = await auth.signInWithOAuth('google', {
|
|
440
|
+
redirectTo: new URL('/api/auth/callback', process.env.NEXT_PUBLIC_APP_URL).toString(),
|
|
435
441
|
skipBrowserRedirect: true,
|
|
436
442
|
});
|
|
437
443
|
|
|
438
444
|
if (error || !data.url || !data.codeVerifier) {
|
|
439
|
-
throw new Error(error?.message ??
|
|
445
|
+
throw new Error(error?.message ?? 'OAuth init failed');
|
|
440
446
|
}
|
|
441
447
|
|
|
442
|
-
cookieStore.set(
|
|
448
|
+
cookieStore.set('insforge_code_verifier', data.codeVerifier, {
|
|
443
449
|
httpOnly: true,
|
|
444
|
-
secure: process.env.NODE_ENV ===
|
|
445
|
-
sameSite:
|
|
446
|
-
path:
|
|
450
|
+
secure: process.env.NODE_ENV === 'production',
|
|
451
|
+
sameSite: 'lax',
|
|
452
|
+
path: '/',
|
|
447
453
|
maxAge: 600,
|
|
448
454
|
});
|
|
449
455
|
|
|
@@ -453,28 +459,28 @@ export async function signInWithGoogle() {
|
|
|
453
459
|
|
|
454
460
|
```typescript
|
|
455
461
|
// app/api/auth/callback/route.ts
|
|
456
|
-
import { cookies } from
|
|
457
|
-
import { NextResponse, type NextRequest } from
|
|
458
|
-
import { createAuthActions } from
|
|
462
|
+
import { cookies } from 'next/headers';
|
|
463
|
+
import { NextResponse, type NextRequest } from 'next/server';
|
|
464
|
+
import { createAuthActions } from '@insforge/sdk/ssr';
|
|
459
465
|
|
|
460
466
|
export async function GET(request: NextRequest) {
|
|
461
|
-
const code = request.nextUrl.searchParams.get(
|
|
462
|
-
const verifier = (await cookies()).get(
|
|
467
|
+
const code = request.nextUrl.searchParams.get('insforge_code');
|
|
468
|
+
const verifier = (await cookies()).get('insforge_code_verifier')?.value;
|
|
463
469
|
if (!code || !verifier) {
|
|
464
|
-
return NextResponse.redirect(new URL(
|
|
470
|
+
return NextResponse.redirect(new URL('/login?error=oauth', request.url));
|
|
465
471
|
}
|
|
466
472
|
|
|
467
|
-
const response = NextResponse.redirect(new URL(
|
|
473
|
+
const response = NextResponse.redirect(new URL('/dashboard', request.url));
|
|
468
474
|
const auth = createAuthActions({
|
|
469
475
|
requestCookies: request.cookies,
|
|
470
476
|
responseCookies: response.cookies,
|
|
471
477
|
});
|
|
472
478
|
const { error } = await auth.exchangeOAuthCode(code, verifier);
|
|
473
479
|
if (error) {
|
|
474
|
-
return NextResponse.redirect(new URL(
|
|
480
|
+
return NextResponse.redirect(new URL('/login?error=oauth', request.url));
|
|
475
481
|
}
|
|
476
482
|
|
|
477
|
-
response.cookies.delete(
|
|
483
|
+
response.cookies.delete('insforge_code_verifier');
|
|
478
484
|
return response;
|
|
479
485
|
}
|
|
480
486
|
```
|
|
@@ -488,8 +494,8 @@ response cookies for writing the next session:
|
|
|
488
494
|
|
|
489
495
|
```typescript
|
|
490
496
|
// app/api/auth/sign-out/route.ts
|
|
491
|
-
import { NextResponse, type NextRequest } from
|
|
492
|
-
import { createAuthActions } from
|
|
497
|
+
import { NextResponse, type NextRequest } from 'next/server';
|
|
498
|
+
import { createAuthActions } from '@insforge/sdk/ssr';
|
|
493
499
|
|
|
494
500
|
export async function POST(request: NextRequest) {
|
|
495
501
|
const response = NextResponse.json({ ok: true });
|
|
@@ -513,7 +519,7 @@ export async function POST(request: NextRequest) {
|
|
|
513
519
|
If your refresh route needs custom side effects:
|
|
514
520
|
|
|
515
521
|
```typescript
|
|
516
|
-
import { refreshAuth } from
|
|
522
|
+
import { refreshAuth } from '@insforge/sdk/ssr';
|
|
517
523
|
|
|
518
524
|
export async function POST(request: Request) {
|
|
519
525
|
const result = await refreshAuth({ request });
|
|
@@ -526,8 +532,8 @@ For Next.js Proxy/Middleware, refresh before Server Components render:
|
|
|
526
532
|
|
|
527
533
|
```typescript
|
|
528
534
|
// proxy.ts on Next.js 16+, middleware.ts on Next.js 15 and earlier
|
|
529
|
-
import { NextResponse, type NextRequest } from
|
|
530
|
-
import { updateSession } from
|
|
535
|
+
import { NextResponse, type NextRequest } from 'next/server';
|
|
536
|
+
import { updateSession } from '@insforge/sdk/ssr/middleware';
|
|
531
537
|
|
|
532
538
|
export async function proxy(request: NextRequest) {
|
|
533
539
|
const response = NextResponse.next({ request });
|
|
@@ -549,10 +555,10 @@ the session refresh helpers and avoids bundling the full SDK client.
|
|
|
549
555
|
The SDK is written in TypeScript and provides full type definitions:
|
|
550
556
|
|
|
551
557
|
```typescript
|
|
552
|
-
import { createClient, InsForgeClient } from
|
|
558
|
+
import { createClient, InsForgeClient } from '@insforge/sdk';
|
|
553
559
|
|
|
554
560
|
const insforge: InsForgeClient = createClient({
|
|
555
|
-
baseUrl:
|
|
561
|
+
baseUrl: 'http://localhost:7130',
|
|
556
562
|
});
|
|
557
563
|
|
|
558
564
|
// Type-safe API calls
|