@merit-systems/echo-next-sdk 0.0.1

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 ADDED
@@ -0,0 +1,34 @@
1
+ # Echo TypeScript SDK
2
+
3
+ The official TypeScript SDK for the Echo platform, providing easy access to Echo APIs and a command-line interface for managing your Echo applications.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm install @merit-systems/echo-typescript-sdk
9
+ ```
10
+
11
+ ## Programmatic Usage
12
+
13
+ ```typescript
14
+ import { EchoClient } from '@merit-systems/echo-typescript-sdk';
15
+
16
+ // Initialize with API key
17
+ const client = new EchoClient({
18
+ apiKey: 'echo_your_api_key_here',
19
+ });
20
+
21
+ // Or use stored credentials from CLI
22
+ const client = new EchoClient();
23
+
24
+ // Get account balance
25
+ const balance = await client.getBalance();
26
+ console.log(`Balance: $${balance.balance}`);
27
+
28
+ // Create a payment link
29
+ const paymentResponse = await client.createPaymentLink({
30
+ amount: 10.0, // $10.00
31
+ description: 'Credits for my account',
32
+ });
33
+ console.log('Payment URL:', paymentResponse.paymentLink.url);
34
+ ```
@@ -0,0 +1,10 @@
1
+ interface EchoClientConfig {
2
+ basePath?: string;
3
+ }
4
+ /**
5
+ * Sign in to Echo (client-side only)
6
+ */
7
+ declare function signIn(config?: EchoClientConfig): void;
8
+ declare function refreshToken(config?: EchoClientConfig): void;
9
+
10
+ export { type EchoClientConfig, refreshToken, signIn };
package/dist/client.js ADDED
@@ -0,0 +1,22 @@
1
+ // src/client.ts
2
+ function signIn(config) {
3
+ if (typeof window === "undefined") {
4
+ console.warn("signIn() can only be called in client components");
5
+ return;
6
+ }
7
+ const basePath = config?.basePath || "/api/echo";
8
+ window.location.href = `${window.location.origin}${basePath}/signin`;
9
+ }
10
+ function refreshToken(config) {
11
+ if (typeof window === "undefined") {
12
+ console.warn("refreshToken() can only be called in client components");
13
+ return;
14
+ }
15
+ const basePath = config?.basePath || "/api/echo";
16
+ window.location.href = `${window.location.origin}${basePath}/refresh`;
17
+ }
18
+ export {
19
+ refreshToken,
20
+ signIn
21
+ };
22
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts"],"sourcesContent":["export interface EchoClientConfig {\n basePath?: string;\n}\n\n/**\n * Sign in to Echo (client-side only)\n */\nexport function signIn(config?: EchoClientConfig) {\n if (typeof window === 'undefined') {\n console.warn('signIn() can only be called in client components');\n return;\n }\n\n const basePath = config?.basePath || '/api/echo';\n window.location.href = `${window.location.origin}${basePath}/signin`;\n}\n\nexport function refreshToken(config?: EchoClientConfig) {\n if (typeof window === 'undefined') {\n console.warn('refreshToken() can only be called in client components');\n return;\n }\n\n const basePath = config?.basePath || '/api/echo';\n window.location.href = `${window.location.origin}${basePath}/refresh`;\n}\n"],"mappings":";AAOO,SAAS,OAAO,QAA2B;AAChD,MAAI,OAAO,WAAW,aAAa;AACjC,YAAQ,KAAK,kDAAkD;AAC/D;AAAA,EACF;AAEA,QAAM,WAAW,QAAQ,YAAY;AACrC,SAAO,SAAS,OAAO,GAAG,OAAO,SAAS,MAAM,GAAG,QAAQ;AAC7D;AAEO,SAAS,aAAa,QAA2B;AACtD,MAAI,OAAO,WAAW,aAAa;AACjC,YAAQ,KAAK,wDAAwD;AACrE;AAAA,EACF;AAEA,QAAM,WAAW,QAAQ,YAAY;AACrC,SAAO,SAAS,OAAO,GAAG,OAAO,SAAS,MAAM,GAAG,QAAQ;AAC7D;","names":[]}
@@ -0,0 +1,24 @@
1
+ import { User, EchoOpenAIProvider, EchoAnthropicProvider } from '@merit-systems/echo-typescript-sdk';
2
+ import { NextRequest } from 'next/server';
3
+
4
+ interface EchoConfig {
5
+ appId: string;
6
+ basePath?: string;
7
+ baseRouterUrl?: string;
8
+ }
9
+ type AppRouteHandlers = Record<'GET' | 'POST', (req: NextRequest) => Promise<Response>>;
10
+ type EchoResult = {
11
+ handlers: AppRouteHandlers;
12
+ getUser: () => Promise<User | null>;
13
+ isSignedIn: () => Promise<boolean>;
14
+ openai: EchoOpenAIProvider;
15
+ anthropic: EchoAnthropicProvider;
16
+ };
17
+
18
+ /**
19
+ * Echo SDK for Next.js
20
+ * Provides OAuth authentication and token management for Echo API integration
21
+ */
22
+ declare function Echo(config: EchoConfig): EchoResult;
23
+
24
+ export { Echo as default };
package/dist/index.js ADDED
@@ -0,0 +1,306 @@
1
+ // src/index.ts
2
+ import { NextResponse as NextResponse2 } from "next/server";
3
+
4
+ // src/auth/token-manager.ts
5
+ import {
6
+ EchoClient,
7
+ ECHO_BASE_URL
8
+ } from "@merit-systems/echo-typescript-sdk";
9
+ import { cookies as getCookies } from "next/headers";
10
+
11
+ // src/auth/jwt-utils.ts
12
+ import { jwtDecode } from "jwt-decode";
13
+ function shouldRefreshToken(token) {
14
+ try {
15
+ const decoded = jwtDecode(token);
16
+ if (!decoded.exp) return true;
17
+ const now = Math.floor(Date.now() / 1e3);
18
+ const bufferTime = 30;
19
+ return decoded.exp <= now + bufferTime;
20
+ } catch {
21
+ return true;
22
+ }
23
+ }
24
+
25
+ // src/auth/token-manager.ts
26
+ async function performTokenRefresh(refreshToken, appId) {
27
+ return fetch(`${ECHO_BASE_URL}/api/oauth/token`, {
28
+ method: "POST",
29
+ headers: { "Content-Type": "application/json" },
30
+ body: JSON.stringify({
31
+ grant_type: "refresh_token",
32
+ refresh_token: refreshToken,
33
+ client_id: appId
34
+ })
35
+ }).then(async (r) => {
36
+ if (r.ok) {
37
+ const tokenData = await r.json();
38
+ return tokenData;
39
+ }
40
+ return Promise.reject(new Error(await r.text()));
41
+ });
42
+ }
43
+ async function getEchoToken(appId) {
44
+ const cookies2 = await getCookies();
45
+ const accessToken = cookies2.get("echo_access_token")?.value;
46
+ if (!accessToken || shouldRefreshToken(accessToken)) {
47
+ const refreshToken = cookies2.get("echo_refresh_token")?.value;
48
+ if (!refreshToken) {
49
+ console.log("No refresh token found");
50
+ return null;
51
+ }
52
+ try {
53
+ const refreshResult = await performTokenRefresh(refreshToken, appId);
54
+ cookies2.set("echo_access_token", refreshResult.access_token, {
55
+ httpOnly: true,
56
+ secure: process.env.NODE_ENV === "production",
57
+ sameSite: "lax",
58
+ maxAge: refreshResult.expires_in,
59
+ path: "/"
60
+ });
61
+ cookies2.set("echo_refresh_token", refreshResult.refresh_token, {
62
+ httpOnly: true,
63
+ secure: process.env.NODE_ENV === "production",
64
+ sameSite: "lax",
65
+ maxAge: refreshResult.refresh_token_expires_in,
66
+ path: "/"
67
+ });
68
+ cookies2.set(
69
+ "echo_refresh_token_expires",
70
+ String(
71
+ Math.floor(Date.now() / 1e3) + refreshResult.refresh_token_expires_in
72
+ ),
73
+ {
74
+ httpOnly: true,
75
+ secure: process.env.NODE_ENV === "production",
76
+ sameSite: "lax",
77
+ maxAge: refreshResult.refresh_token_expires_in,
78
+ path: "/"
79
+ }
80
+ );
81
+ return refreshResult.access_token;
82
+ } catch (error) {
83
+ console.error("Token refresh failed:", refreshToken, error);
84
+ return null;
85
+ }
86
+ }
87
+ return accessToken;
88
+ }
89
+
90
+ // src/providers/anthropic.ts
91
+ import {
92
+ createEchoAnthropic as createEchoAnthropicBase,
93
+ ROUTER_BASE_URL
94
+ } from "@merit-systems/echo-typescript-sdk";
95
+ function createEchoAnthropic({
96
+ appId,
97
+ baseRouterUrl = ROUTER_BASE_URL
98
+ }) {
99
+ return createEchoAnthropicBase(
100
+ { appId, baseRouterUrl },
101
+ async () => getEchoToken(appId)
102
+ );
103
+ }
104
+
105
+ // src/auth/oauth-handlers.ts
106
+ import { NextResponse } from "next/server";
107
+ import { ECHO_BASE_URL as ECHO_BASE_URL2 } from "@merit-systems/echo-typescript-sdk";
108
+ async function generateCodeChallenge() {
109
+ const codeVerifier = Array.from(crypto.getRandomValues(new Uint8Array(32))).map((b) => b.toString(16).padStart(2, "0")).join("");
110
+ const encoder = new TextEncoder();
111
+ const codeChallengeBuffer = await crypto.subtle.digest(
112
+ "SHA-256",
113
+ encoder.encode(codeVerifier)
114
+ );
115
+ const codeChallenge = btoa(
116
+ String.fromCharCode(...new Uint8Array(codeChallengeBuffer))
117
+ ).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
118
+ return { codeVerifier, codeChallenge };
119
+ }
120
+ async function handleSignIn(req, config) {
121
+ const { origin } = req.nextUrl;
122
+ const basePath = config.basePath || "/api/echo";
123
+ const redirectUrl = new URL(`${ECHO_BASE_URL2}/api/oauth/authorize`);
124
+ redirectUrl.searchParams.set("client_id", config.appId);
125
+ redirectUrl.searchParams.set("redirect_uri", `${origin}${basePath}/callback`);
126
+ redirectUrl.searchParams.set("response_type", "code");
127
+ const { codeVerifier, codeChallenge } = await generateCodeChallenge();
128
+ redirectUrl.searchParams.set("code_challenge", codeChallenge);
129
+ redirectUrl.searchParams.set("code_challenge_method", "S256");
130
+ const response = NextResponse.redirect(redirectUrl.toString());
131
+ response.cookies.set("echo_code_verifier", codeVerifier, {
132
+ httpOnly: true,
133
+ secure: process.env.NODE_ENV === "production",
134
+ sameSite: "lax",
135
+ maxAge: 300,
136
+ // 5 minutes - same as auth code TTL
137
+ path: "/"
138
+ });
139
+ return response;
140
+ }
141
+ async function handleCallback(req, config) {
142
+ const { origin } = req.nextUrl;
143
+ const basePath = config.basePath || "/api/echo";
144
+ const code = req.nextUrl.searchParams.get("code");
145
+ const state = req.nextUrl.searchParams.get("state");
146
+ if (!code || !state) {
147
+ return NextResponse.json({ error: "Invalid request" }, { status: 400 });
148
+ }
149
+ const codeVerifier = req.cookies.get("echo_code_verifier")?.value;
150
+ if (!codeVerifier) {
151
+ return NextResponse.json(
152
+ { error: "Code verifier not found. Please try signing in again." },
153
+ { status: 400 }
154
+ );
155
+ }
156
+ const tokenEndpoint = `${ECHO_BASE_URL2}/api/oauth/token`;
157
+ const params = new URLSearchParams();
158
+ params.set("grant_type", "authorization_code");
159
+ params.set("code", code);
160
+ params.set("redirect_uri", `${origin}${basePath}/callback`);
161
+ params.set("client_id", config.appId);
162
+ params.set("code_verifier", codeVerifier);
163
+ const tokenResponse = await fetch(tokenEndpoint, {
164
+ method: "POST",
165
+ headers: {
166
+ "Content-Type": "application/x-www-form-urlencoded"
167
+ },
168
+ body: params.toString()
169
+ });
170
+ if (!tokenResponse.ok) {
171
+ return NextResponse.json(
172
+ { error: "Token exchange failed" },
173
+ { status: 500 }
174
+ );
175
+ }
176
+ const tokenData = await tokenResponse.json();
177
+ const response = NextResponse.redirect(`${origin}`);
178
+ response.cookies.set("echo_code_verifier", "", {
179
+ httpOnly: true,
180
+ secure: process.env.NODE_ENV === "production",
181
+ sameSite: "lax",
182
+ maxAge: 0,
183
+ // Expire immediately
184
+ path: "/"
185
+ });
186
+ response.cookies.set("echo_access_token", tokenData.access_token, {
187
+ httpOnly: true,
188
+ secure: process.env.NODE_ENV === "production",
189
+ sameSite: "lax",
190
+ maxAge: tokenData.expires_in,
191
+ // expires_in is typically in seconds
192
+ path: "/"
193
+ });
194
+ tokenData.refresh_token_expires_in;
195
+ response.cookies.set("echo_refresh_token", tokenData.refresh_token, {
196
+ httpOnly: true,
197
+ secure: process.env.NODE_ENV === "production",
198
+ sameSite: "lax",
199
+ maxAge: tokenData.refresh_token_expires_in,
200
+ path: "/"
201
+ });
202
+ response.cookies.set(
203
+ "echo_refresh_token_expires",
204
+ String(Math.floor(Date.now() / 1e3) + tokenData.refresh_token_expires_in),
205
+ {
206
+ httpOnly: true,
207
+ secure: process.env.NODE_ENV === "production",
208
+ sameSite: "lax",
209
+ maxAge: tokenData.refresh_token_expires_in,
210
+ path: "/"
211
+ }
212
+ );
213
+ return response;
214
+ }
215
+ async function handleRefresh(req, config) {
216
+ try {
217
+ const token = await getEchoToken(config.appId);
218
+ if (!token) {
219
+ return NextResponse.json({ error: "No token found" }, { status: 401 });
220
+ }
221
+ return NextResponse.json({ success: true });
222
+ } catch (error) {
223
+ console.error("Error refreshing token:", error);
224
+ return NextResponse.json(
225
+ { error: "Error refreshing token" },
226
+ { status: 500 }
227
+ );
228
+ }
229
+ }
230
+
231
+ // src/providers/openai.ts
232
+ import {
233
+ createEchoOpenAI as createEchoOpenAIBase,
234
+ ROUTER_BASE_URL as ROUTER_BASE_URL2
235
+ } from "@merit-systems/echo-typescript-sdk";
236
+ function createEchoOpenAI({
237
+ appId,
238
+ baseRouterUrl = ROUTER_BASE_URL2
239
+ }) {
240
+ return createEchoOpenAIBase(
241
+ { appId, baseRouterUrl },
242
+ async () => getEchoToken(appId)
243
+ );
244
+ }
245
+
246
+ // src/index.ts
247
+ import { EchoClient as EchoClient2, ECHO_BASE_URL as ECHO_BASE_URL3 } from "@merit-systems/echo-typescript-sdk";
248
+ import { cookies } from "next/headers";
249
+ function Echo(config) {
250
+ const httpHandler = async (req) => {
251
+ const { pathname } = req.nextUrl;
252
+ const basePath = config.basePath || "/api/echo";
253
+ const path = pathname.replace(basePath, "");
254
+ switch (path) {
255
+ case "/signin":
256
+ return handleSignIn(req, config);
257
+ case "/callback":
258
+ return handleCallback(req, config);
259
+ case "/refresh":
260
+ return handleRefresh(req, config);
261
+ default:
262
+ return NextResponse2.error();
263
+ }
264
+ };
265
+ const getUser = async () => {
266
+ const cookieStore = await cookies();
267
+ const accessToken = cookieStore.get("echo_access_token")?.value;
268
+ if (!accessToken) {
269
+ return null;
270
+ }
271
+ const echo = new EchoClient2({
272
+ apiKey: accessToken,
273
+ baseUrl: ECHO_BASE_URL3
274
+ });
275
+ const user = await echo.users.getUserInfo();
276
+ return user;
277
+ };
278
+ const isSignedIn = async () => {
279
+ const cookieStore = await cookies();
280
+ const refreshTokenExpiry = cookieStore.get(
281
+ "echo_refresh_token_expires"
282
+ )?.value;
283
+ if (!refreshTokenExpiry) {
284
+ return false;
285
+ }
286
+ const expiryTime = parseInt(refreshTokenExpiry);
287
+ const now = Math.floor(Date.now() / 1e3);
288
+ return expiryTime > now;
289
+ };
290
+ return {
291
+ handlers: {
292
+ GET: httpHandler,
293
+ POST: httpHandler
294
+ },
295
+ // echo auth
296
+ getUser,
297
+ isSignedIn,
298
+ // providers
299
+ openai: createEchoOpenAI(config),
300
+ anthropic: createEchoAnthropic(config)
301
+ };
302
+ }
303
+ export {
304
+ Echo as default
305
+ };
306
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/auth/token-manager.ts","../src/auth/jwt-utils.ts","../src/providers/anthropic.ts","../src/auth/oauth-handlers.ts","../src/providers/openai.ts"],"sourcesContent":["import { NextRequest, NextResponse } from 'next/server';\nimport { createEchoAnthropic } from 'providers/anthropic';\nimport {\n handleCallback,\n handleRefresh,\n handleSignIn,\n} from './auth/oauth-handlers';\nimport { createEchoOpenAI } from './providers/openai';\nimport { EchoConfig, EchoResult } from './types';\nimport { EchoClient, ECHO_BASE_URL } from '@merit-systems/echo-typescript-sdk';\nimport { cookies } from 'next/headers';\n\n/**\n * Echo SDK for Next.js\n * Provides OAuth authentication and token management for Echo API integration\n */\nexport default function Echo(config: EchoConfig): EchoResult {\n /**\n * HTTP handler for OAuth routes (signin and callback)\n */\n const httpHandler = async (req: NextRequest): Promise<Response> => {\n const { pathname } = req.nextUrl;\n const basePath = config.basePath || '/api/echo';\n const path = pathname.replace(basePath, '');\n\n switch (path) {\n case '/signin':\n return handleSignIn(req, config);\n\n case '/callback':\n return handleCallback(req, config);\n\n case '/refresh':\n return handleRefresh(req, config);\n\n default:\n return NextResponse.error();\n }\n };\n\n /**\n * Get current user info with automatic token refresh\n */\n const getUser = async () => {\n // read only access token, if expired we are fucked\n const cookieStore = await cookies();\n const accessToken = cookieStore.get('echo_access_token')?.value;\n if (!accessToken) {\n return null;\n }\n const echo = new EchoClient({\n apiKey: accessToken,\n baseUrl: ECHO_BASE_URL,\n });\n const user = await echo.users.getUserInfo();\n return user;\n };\n\n const isSignedIn = async () => {\n const cookieStore = await cookies();\n const refreshTokenExpiry = cookieStore.get(\n 'echo_refresh_token_expires'\n )?.value;\n\n if (!refreshTokenExpiry) {\n return false; // No expiry stored\n }\n\n const expiryTime = parseInt(refreshTokenExpiry);\n const now = Math.floor(Date.now() / 1000);\n\n return expiryTime > now; // True if not expired\n };\n\n return {\n handlers: {\n GET: httpHandler,\n POST: httpHandler,\n },\n // echo auth\n getUser,\n isSignedIn,\n // providers\n openai: createEchoOpenAI(config),\n anthropic: createEchoAnthropic(config),\n };\n}\n","import {\n EchoClient,\n ECHO_BASE_URL,\n User,\n} from '@merit-systems/echo-typescript-sdk';\nimport { cookies as getCookies } from 'next/headers';\nimport { shouldRefreshToken } from './jwt-utils';\n\nexport interface RefreshTokenResponse {\n access_token: string;\n token_type: string;\n expires_in: number;\n refresh_token: string;\n refresh_token_expires_in: number;\n scope: string;\n user: {\n id: string;\n email: string;\n name: string;\n };\n echo_app: {\n id: string;\n name: string;\n description: string;\n };\n}\n\n/**\n * Refresh an access token using a refresh token\n * @param refreshToken - The refresh token to use\n * @param appId - The Echo app ID\n * @returns Promise resolving to the new token data\n */\nexport async function performTokenRefresh(\n refreshToken: string,\n appId: string\n): Promise<RefreshTokenResponse> {\n return fetch(`${ECHO_BASE_URL}/api/oauth/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: appId,\n }),\n }).then(async r => {\n if (r.ok) {\n const tokenData = (await r.json()) as RefreshTokenResponse;\n return tokenData;\n }\n return Promise.reject(new Error(await r.text()));\n });\n}\n\n/**\n * Get a valid Echo token, refreshing if necessary\n * @param appId - The Echo app ID for token refresh\n * @returns Promise resolving to a valid access token or null if authentication failed\n */\nexport async function getEchoToken(appId: string): Promise<string | null> {\n const cookies = await getCookies();\n const accessToken = cookies.get('echo_access_token')?.value;\n\n // Check if token needs refresh\n if (!accessToken || shouldRefreshToken(accessToken)) {\n const refreshToken = cookies.get('echo_refresh_token')?.value;\n\n if (!refreshToken) {\n console.log('No refresh token found');\n return null;\n }\n\n try {\n const refreshResult = await performTokenRefresh(refreshToken, appId);\n\n // Set new tokens in cookies\n cookies.set('echo_access_token', refreshResult.access_token, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: refreshResult.expires_in,\n path: '/',\n });\n\n cookies.set('echo_refresh_token', refreshResult.refresh_token, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: refreshResult.refresh_token_expires_in,\n path: '/',\n });\n\n // Store refresh token expiry time for checking\n cookies.set(\n 'echo_refresh_token_expires',\n String(\n Math.floor(Date.now() / 1000) + refreshResult.refresh_token_expires_in\n ),\n {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: refreshResult.refresh_token_expires_in,\n path: '/',\n }\n );\n\n return refreshResult.access_token;\n } catch (error) {\n console.error('Token refresh failed:', refreshToken, error);\n return null;\n }\n }\n\n return accessToken;\n}\n\nexport async function getUser(appId: string): Promise<User> {\n const echo = await getEchoClient(appId);\n if (!echo) {\n throw new Error('User not signed in');\n }\n return echo.users.getUserInfo();\n}\n\nexport async function getEchoClient(appId: string): Promise<EchoClient | null> {\n const token = await getEchoToken(appId);\n if (!token) {\n return null;\n }\n return new EchoClient({\n baseUrl: ECHO_BASE_URL,\n apiKey: token,\n });\n}\n","import { jwtDecode } from 'jwt-decode';\n\ninterface JwtPayload {\n exp?: number;\n}\n\n/**\n * Check if a JWT token needs to be refreshed\n * @param token - The JWT access token to check\n * @returns true if token should be refreshed, false otherwise\n */\nexport function shouldRefreshToken(token: string): boolean {\n try {\n const decoded = jwtDecode<JwtPayload>(token);\n if (!decoded.exp) return true;\n\n const now = Math.floor(Date.now() / 1000);\n // Refresh if token expires within 30 seconds\n const bufferTime = 30;\n return decoded.exp <= now + bufferTime;\n } catch {\n return true; // If we can't decode, assume it needs refresh\n }\n}\n","import { getEchoToken } from '../auth/token-manager';\nimport {\n createEchoAnthropic as createEchoAnthropicBase,\n EchoAnthropicProvider,\n EchoConfig,\n ROUTER_BASE_URL,\n} from '@merit-systems/echo-typescript-sdk';\n\nexport function createEchoAnthropic({\n appId,\n baseRouterUrl = ROUTER_BASE_URL,\n}: EchoConfig): EchoAnthropicProvider {\n return createEchoAnthropicBase({ appId, baseRouterUrl }, async () =>\n getEchoToken(appId)\n );\n}\n","import { NextRequest, NextResponse } from 'next/server';\nimport { getEchoToken, RefreshTokenResponse } from './token-manager';\nimport { EchoConfig, ECHO_BASE_URL } from '@merit-systems/echo-typescript-sdk';\n\n/**\n * Generate PKCE code challenge and verifier\n * @returns Object containing code verifier and challenge\n */\nasync function generateCodeChallenge(): Promise<{\n codeVerifier: string;\n codeChallenge: string;\n}> {\n const codeVerifier = Array.from(crypto.getRandomValues(new Uint8Array(32)))\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n\n const encoder = new TextEncoder();\n const codeChallengeBuffer = await crypto.subtle.digest(\n 'SHA-256',\n encoder.encode(codeVerifier)\n );\n\n const codeChallenge = btoa(\n String.fromCharCode(...new Uint8Array(codeChallengeBuffer))\n )\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n\n return { codeVerifier, codeChallenge };\n}\n\n/**\n * Handle OAuth signin request - redirect to Echo authorization server\n * @param req - The incoming Next.js request\n * @param config - Echo configuration\n * @returns NextResponse with redirect to authorization server\n */\nexport async function handleSignIn(\n req: NextRequest,\n config: EchoConfig\n): Promise<NextResponse> {\n const { origin } = req.nextUrl;\n const basePath = config.basePath || '/api/echo';\n\n const redirectUrl = new URL(`${ECHO_BASE_URL}/api/oauth/authorize`);\n redirectUrl.searchParams.set('client_id', config.appId);\n redirectUrl.searchParams.set('redirect_uri', `${origin}${basePath}/callback`);\n redirectUrl.searchParams.set('response_type', 'code');\n\n const { codeVerifier, codeChallenge } = await generateCodeChallenge();\n\n redirectUrl.searchParams.set('code_challenge', codeChallenge);\n redirectUrl.searchParams.set('code_challenge_method', 'S256');\n\n // Create response with redirect and set cookie for code verifier\n const response = NextResponse.redirect(redirectUrl.toString());\n\n // Set the code verifier in a secure cookie that will be available during callback\n response.cookies.set('echo_code_verifier', codeVerifier, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: 300, // 5 minutes - same as auth code TTL\n path: '/',\n });\n\n return response;\n}\n\n/**\n * Handle OAuth callback - exchange authorization code for tokens\n * @param req - The incoming Next.js request\n * @param config - Echo configuration\n * @returns NextResponse with redirect to home page and tokens set as cookies\n */\nexport async function handleCallback(\n req: NextRequest,\n config: EchoConfig\n): Promise<NextResponse> {\n const { origin } = req.nextUrl;\n const basePath = config.basePath || '/api/echo';\n\n const code = req.nextUrl.searchParams.get('code');\n const state = req.nextUrl.searchParams.get('state');\n\n if (!code || !state) {\n return NextResponse.json({ error: 'Invalid request' }, { status: 400 });\n }\n\n // Retrieve the code verifier from the cookie\n const codeVerifier = req.cookies.get('echo_code_verifier')?.value;\n\n if (!codeVerifier) {\n return NextResponse.json(\n { error: 'Code verifier not found. Please try signing in again.' },\n { status: 400 }\n );\n }\n\n // Exchange the code for a token from the token endpoint\n const tokenEndpoint = `${ECHO_BASE_URL}/api/oauth/token`;\n\n const params = new URLSearchParams();\n params.set('grant_type', 'authorization_code');\n params.set('code', code);\n params.set('redirect_uri', `${origin}${basePath}/callback`);\n params.set('client_id', config.appId);\n params.set('code_verifier', codeVerifier);\n\n const tokenResponse = await fetch(tokenEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: params.toString(),\n });\n\n if (!tokenResponse.ok) {\n return NextResponse.json(\n { error: 'Token exchange failed' },\n { status: 500 }\n );\n }\n\n const tokenData = (await tokenResponse.json()) as RefreshTokenResponse;\n\n const response = NextResponse.redirect(`${origin}`);\n\n // Clear the code verifier cookie after successful token exchange\n response.cookies.set('echo_code_verifier', '', {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: 0, // Expire immediately\n path: '/',\n });\n\n // Set the access token as a cookie\n response.cookies.set('echo_access_token', tokenData.access_token, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: tokenData.expires_in, // expires_in is typically in seconds\n path: '/',\n });\n\n tokenData.refresh_token_expires_in;\n\n response.cookies.set('echo_refresh_token', tokenData.refresh_token, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: tokenData.refresh_token_expires_in,\n path: '/',\n });\n\n // Store refresh token expiry time for checking\n response.cookies.set(\n 'echo_refresh_token_expires',\n String(Math.floor(Date.now() / 1000) + tokenData.refresh_token_expires_in),\n {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'lax',\n maxAge: tokenData.refresh_token_expires_in,\n path: '/',\n }\n );\n\n return response;\n}\n\nexport async function handleRefresh(\n req: NextRequest,\n config: EchoConfig\n): Promise<NextResponse> {\n try {\n const token = await getEchoToken(config.appId);\n if (!token) {\n return NextResponse.json({ error: 'No token found' }, { status: 401 });\n }\n\n return NextResponse.json({ success: true });\n } catch (error) {\n console.error('Error refreshing token:', error);\n return NextResponse.json(\n { error: 'Error refreshing token' },\n { status: 500 }\n );\n }\n}\n","import { getEchoToken } from '../auth/token-manager';\nimport {\n createEchoOpenAI as createEchoOpenAIBase,\n EchoConfig,\n EchoOpenAIProvider,\n ROUTER_BASE_URL,\n} from '@merit-systems/echo-typescript-sdk';\n\nexport function createEchoOpenAI({\n appId,\n baseRouterUrl = ROUTER_BASE_URL,\n}: EchoConfig): EchoOpenAIProvider {\n return createEchoOpenAIBase({ appId, baseRouterUrl }, async () =>\n getEchoToken(appId)\n );\n}\n"],"mappings":"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","names":["NextResponse","cookies","ECHO_BASE_URL","ROUTER_BASE_URL","EchoClient","ECHO_BASE_URL","NextResponse"]}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@merit-systems/echo-next-sdk",
3
+ "version": "0.0.1",
4
+ "description": "Next.js SDK for Echo",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./client": {
14
+ "types": "./dist/client.d.ts",
15
+ "import": "./dist/client.js"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "dev": "tsup --watch",
21
+ "lint": "pnpx eslint src/**/*.ts",
22
+ "lint:fix": "pnpx eslint src/**/*.ts --fix",
23
+ "type-check": "tsc --noEmit",
24
+ "format": "prettier --write src",
25
+ "test": "echo 'no tests'",
26
+ "test:unit": "echo 'no tests'",
27
+ "test:watch": "echo 'no tests'",
28
+ "prepublishOnly": "pnpm run build"
29
+ },
30
+ "keywords": [
31
+ "echo",
32
+ "typescript",
33
+ "sdk",
34
+ "api"
35
+ ],
36
+ "author": "Merit Systems",
37
+ "license": "MIT",
38
+ "dependencies": {
39
+ "@merit-systems/echo-typescript-sdk": "workspace:*",
40
+ "ai": "^5.0.17",
41
+ "jwt-decode": "^4.0.0"
42
+ },
43
+ "peerDependencies": {
44
+ "next": ">=15.0.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^24.0.3",
48
+ "@typescript-eslint/eslint-plugin": "^8.34.1",
49
+ "@typescript-eslint/parser": "^8.34.1",
50
+ "eslint": "^9.29.0",
51
+ "next": "^15.1.4",
52
+ "tsup": "^8.5.0",
53
+ "typescript": "^5.8.3",
54
+ "vitest": "^3.2.3"
55
+ },
56
+ "files": [
57
+ "dist/**/*"
58
+ ]
59
+ }