@proma-dev/sdk 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Proma
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,155 @@
1
+ # @proma/sdk
2
+
3
+ Connect your app to the [Proma](https://proma.dev) marketplace. Authenticate users, charge credits, and use AI — all with a few lines of code.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @proma/sdk
9
+ ```
10
+
11
+ ## Setup
12
+
13
+ 1. Create an app at [proma.dev/home/developer](https://proma.dev/home/developer)
14
+ 2. Copy your **Client ID** and register a **Redirect URI**
15
+ 3. Initialize the client:
16
+
17
+ ```ts
18
+ import { PromaClient } from '@proma/sdk';
19
+
20
+ const proma = new PromaClient({
21
+ clientId: 'your_client_id',
22
+ redirectUri: 'https://yourapp.com/callback',
23
+ scopes: ['profile', 'credits', 'ai:chat'],
24
+ });
25
+ ```
26
+
27
+ ## Authentication
28
+
29
+ ```ts
30
+ // Redirect user to Proma login
31
+ await proma.login();
32
+
33
+ // On your callback page, exchange the code for a session
34
+ const session = await proma.handleCallback();
35
+
36
+ // Get the logged-in user
37
+ const user = await proma.getUser();
38
+ // => { sub, email, name, picture }
39
+
40
+ // Check auth status
41
+ await proma.isAuthenticated(); // true
42
+
43
+ // Log out
44
+ proma.logout();
45
+ ```
46
+
47
+ ## Credits
48
+
49
+ Charge users for features in your app. 1,000,000 micro-credits = $1.00.
50
+
51
+ ```ts
52
+ // Check balance
53
+ const { balance, formatted } = await proma.credits.getBalance();
54
+ console.log(`Balance: ${formatted}`); // "Balance: $1.23"
55
+
56
+ // Spend credits
57
+ const result = await proma.credits.spend(500_000, 'Generated a report');
58
+ // => { success: true, amount_spent: 500000, new_balance: 730000 }
59
+ ```
60
+
61
+ ## AI Chat
62
+
63
+ Stream AI responses through Proma's gateway. Credits are deducted automatically per token.
64
+
65
+ ```ts
66
+ // Get the full response as text
67
+ const answer = await proma.ai.chatText([
68
+ { role: 'user', content: 'Explain quantum entanglement simply.' },
69
+ ]);
70
+
71
+ // Or get a streaming Response for real-time UI
72
+ const stream = await proma.ai.chat([
73
+ { role: 'user', content: 'Write a haiku about coding.' },
74
+ ]);
75
+ ```
76
+
77
+ ## React
78
+
79
+ ```bash
80
+ npm install @proma/sdk react
81
+ ```
82
+
83
+ ### Provider
84
+
85
+ Wrap your app with `PromaProvider` to enable auth context:
86
+
87
+ ```tsx
88
+ import { PromaProvider } from '@proma/sdk/react';
89
+
90
+ function App() {
91
+ return (
92
+ <PromaProvider
93
+ clientId="your_client_id"
94
+ redirectUri="https://yourapp.com/callback"
95
+ scopes={['profile', 'credits']}
96
+ >
97
+ <YourApp />
98
+ </PromaProvider>
99
+ );
100
+ }
101
+ ```
102
+
103
+ ### Hook
104
+
105
+ ```tsx
106
+ import { usePromaAuth } from '@proma/sdk/react';
107
+
108
+ function Dashboard() {
109
+ const { user, isLoading, isAuthenticated, login, logout, client } =
110
+ usePromaAuth();
111
+
112
+ if (isLoading) return <p>Loading...</p>;
113
+ if (!isAuthenticated) return <button onClick={() => login()}>Log in</button>;
114
+
115
+ return (
116
+ <div>
117
+ <p>Welcome, {user.name ?? user.email}</p>
118
+ <button onClick={logout}>Log out</button>
119
+ </div>
120
+ );
121
+ }
122
+ ```
123
+
124
+ ### Login Button
125
+
126
+ Drop-in button component:
127
+
128
+ ```tsx
129
+ import { LoginWithProma } from '@proma/sdk/react';
130
+
131
+ <LoginWithProma
132
+ clientId="your_client_id"
133
+ redirectUri="https://yourapp.com/callback"
134
+ scopes={['profile', 'credits']}
135
+ />;
136
+ ```
137
+
138
+ ## API Reference
139
+
140
+ | Method | Description |
141
+ | --- | --- |
142
+ | `proma.login(scopes?)` | Redirect to Proma login |
143
+ | `proma.handleCallback(url?)` | Exchange OAuth code for session |
144
+ | `proma.getSession()` | Get current session (auto-refreshes) |
145
+ | `proma.isAuthenticated()` | Check if user has valid session |
146
+ | `proma.getUser()` | Fetch user profile |
147
+ | `proma.logout()` | Clear local session |
148
+ | `proma.credits.getBalance()` | Get credit balance |
149
+ | `proma.credits.spend(amount, description?)` | Deduct credits |
150
+ | `proma.ai.chat(messages)` | Streaming AI chat |
151
+ | `proma.ai.chatText(messages)` | AI chat (full text) |
152
+
153
+ ## License
154
+
155
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,409 @@
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/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ MemoryStorage: () => MemoryStorage,
24
+ PromaClient: () => PromaClient
25
+ });
26
+ module.exports = __toCommonJS(src_exports);
27
+
28
+ // src/pkce.ts
29
+ var PKCE_STORAGE_KEY = "proma_code_verifier";
30
+ function generateCodeVerifier() {
31
+ const bytes = new Uint8Array(32);
32
+ crypto.getRandomValues(bytes);
33
+ return base64url(bytes);
34
+ }
35
+ async function generateCodeChallenge(verifier) {
36
+ const encoder = new TextEncoder();
37
+ const data = encoder.encode(verifier);
38
+ const hash = await crypto.subtle.digest("SHA-256", data);
39
+ return base64url(new Uint8Array(hash));
40
+ }
41
+ function saveCodeVerifier(verifier) {
42
+ if (typeof sessionStorage !== "undefined") {
43
+ sessionStorage.setItem(PKCE_STORAGE_KEY, verifier);
44
+ }
45
+ }
46
+ function consumeCodeVerifier() {
47
+ if (typeof sessionStorage === "undefined") return null;
48
+ const verifier = sessionStorage.getItem(PKCE_STORAGE_KEY);
49
+ sessionStorage.removeItem(PKCE_STORAGE_KEY);
50
+ return verifier;
51
+ }
52
+ function base64url(bytes) {
53
+ const base64 = btoa(String.fromCharCode(...bytes));
54
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
55
+ }
56
+
57
+ // src/storage.ts
58
+ var SESSION_KEY = "proma_session";
59
+ var TokenStore = class {
60
+ constructor(storage) {
61
+ this.storage = storage;
62
+ }
63
+ get() {
64
+ try {
65
+ const raw = this.storage.getItem(SESSION_KEY);
66
+ if (!raw) return null;
67
+ return JSON.parse(raw);
68
+ } catch (e) {
69
+ return null;
70
+ }
71
+ }
72
+ set(session) {
73
+ this.storage.setItem(SESSION_KEY, JSON.stringify(session));
74
+ }
75
+ clear() {
76
+ this.storage.removeItem(SESSION_KEY);
77
+ this.storage.removeItem("proma_code_verifier");
78
+ }
79
+ isExpired(session) {
80
+ return Date.now() >= session.expiresAt - 3e4;
81
+ }
82
+ };
83
+ var MemoryStorage = class {
84
+ constructor() {
85
+ this.map = /* @__PURE__ */ new Map();
86
+ }
87
+ getItem(key) {
88
+ var _a;
89
+ return (_a = this.map.get(key)) != null ? _a : null;
90
+ }
91
+ setItem(key, value) {
92
+ this.map.set(key, value);
93
+ }
94
+ removeItem(key) {
95
+ this.map.delete(key);
96
+ }
97
+ };
98
+ function getDefaultStorage() {
99
+ if (typeof localStorage !== "undefined") return localStorage;
100
+ return new MemoryStorage();
101
+ }
102
+
103
+ // src/client.ts
104
+ var DEFAULT_BASE_URL = "https://proma.dev";
105
+ var PromaClient = class {
106
+ constructor(config) {
107
+ this.config = config;
108
+ var _a, _b, _c;
109
+ this.baseUrl = (_a = config.baseUrl) != null ? _a : DEFAULT_BASE_URL;
110
+ this.store = new TokenStore((_b = config.storage) != null ? _b : getDefaultStorage());
111
+ this.defaultScopes = (_c = config.scopes) != null ? _c : ["profile"];
112
+ this.credits = new CreditsApi(this);
113
+ this.ai = new AiApi(this);
114
+ }
115
+ // ---------------------------------------------------------------------------
116
+ // Auth
117
+ // ---------------------------------------------------------------------------
118
+ /**
119
+ * Redirects the user to Proma's login page.
120
+ * Call this on a button click — it will navigate away from the current page.
121
+ *
122
+ * @example
123
+ * button.onclick = () => proma.login()
124
+ */
125
+ async login(scopes) {
126
+ const url = await this.buildAuthorizeUrl(scopes != null ? scopes : this.defaultScopes);
127
+ window.location.href = url;
128
+ }
129
+ /**
130
+ * Builds the authorization URL without navigating.
131
+ * Useful if you want to control the redirect yourself.
132
+ */
133
+ async buildAuthorizeUrl(scopes = this.defaultScopes) {
134
+ const verifier = generateCodeVerifier();
135
+ const challenge = await generateCodeChallenge(verifier);
136
+ saveCodeVerifier(verifier);
137
+ const state = crypto.randomUUID();
138
+ if (typeof sessionStorage !== "undefined") {
139
+ sessionStorage.setItem("proma_oauth_state", state);
140
+ }
141
+ const url = new URL("/api/oauth/authorize", this.baseUrl);
142
+ url.searchParams.set("client_id", this.config.clientId);
143
+ url.searchParams.set("redirect_uri", this.config.redirectUri);
144
+ url.searchParams.set("response_type", "code");
145
+ url.searchParams.set("scope", scopes.join(" "));
146
+ url.searchParams.set("state", state);
147
+ url.searchParams.set("code_challenge", challenge);
148
+ url.searchParams.set("code_challenge_method", "S256");
149
+ return url.toString();
150
+ }
151
+ /**
152
+ * Handles the OAuth callback. Call this on your redirect page.
153
+ * Reads the `code` from the URL, exchanges it for tokens, and stores the session.
154
+ *
155
+ * @param url - Defaults to `window.location.href`
156
+ * @returns The new session
157
+ *
158
+ * @example
159
+ * // pages/callback.tsx
160
+ * useEffect(() => {
161
+ * proma.handleCallback().then(session => {
162
+ * router.push('/dashboard')
163
+ * })
164
+ * }, [])
165
+ */
166
+ async handleCallback(url) {
167
+ var _a;
168
+ const href = url != null ? url : typeof window !== "undefined" ? window.location.href : "";
169
+ const params = new URL(href).searchParams;
170
+ const code = params.get("code");
171
+ const error = params.get("error");
172
+ if (error) {
173
+ throw new Error((_a = params.get("error_description")) != null ? _a : error);
174
+ }
175
+ if (!code) {
176
+ throw new Error("No authorization code found in URL");
177
+ }
178
+ const returnedState = params.get("state");
179
+ if (typeof sessionStorage !== "undefined") {
180
+ const expectedState = sessionStorage.getItem("proma_oauth_state");
181
+ sessionStorage.removeItem("proma_oauth_state");
182
+ if (!returnedState || returnedState !== expectedState) {
183
+ throw new Error("Invalid state parameter \u2014 possible CSRF attack");
184
+ }
185
+ }
186
+ const verifier = consumeCodeVerifier();
187
+ const body = new URLSearchParams({
188
+ grant_type: "authorization_code",
189
+ code,
190
+ redirect_uri: this.config.redirectUri,
191
+ client_id: this.config.clientId
192
+ });
193
+ if (verifier) body.set("code_verifier", verifier);
194
+ const tokens = await this.fetchTokens(body);
195
+ const session = this.tokensToSession(tokens);
196
+ this.store.set(session);
197
+ return session;
198
+ }
199
+ /**
200
+ * Returns the current session (access token, refresh token, expiry).
201
+ * Automatically refreshes the access token if it is expired.
202
+ * Returns `null` if the user is not logged in.
203
+ */
204
+ async getSession() {
205
+ const session = this.store.get();
206
+ if (!session) return null;
207
+ if (this.store.isExpired(session)) {
208
+ try {
209
+ return await this.refresh(session.refreshToken);
210
+ } catch (e) {
211
+ this.store.clear();
212
+ return null;
213
+ }
214
+ }
215
+ return session;
216
+ }
217
+ /**
218
+ * Returns `true` if the user has a valid (or refreshable) session.
219
+ */
220
+ async isAuthenticated() {
221
+ return await this.getSession() !== null;
222
+ }
223
+ /**
224
+ * Fetches the logged-in user's profile.
225
+ * Requires the `profile` scope.
226
+ */
227
+ async getUser() {
228
+ const token = await this.requireAccessToken();
229
+ const res = await fetch(`${this.baseUrl}/api/oauth/userinfo`, {
230
+ headers: { Authorization: `Bearer ${token}` }
231
+ });
232
+ if (!res.ok) throw new Error("Failed to fetch user info");
233
+ return res.json();
234
+ }
235
+ /**
236
+ * Clears the stored session and logs the user out.
237
+ * Does not revoke the token server-side.
238
+ */
239
+ logout() {
240
+ this.store.clear();
241
+ }
242
+ // ---------------------------------------------------------------------------
243
+ // Internal helpers (used by sub-APIs)
244
+ // ---------------------------------------------------------------------------
245
+ async requireAccessToken() {
246
+ const session = await this.getSession();
247
+ if (!session)
248
+ throw new Error("Not authenticated \u2014 call proma.login() first");
249
+ return session.accessToken;
250
+ }
251
+ async refresh(refreshToken) {
252
+ const body = new URLSearchParams({
253
+ grant_type: "refresh_token",
254
+ refresh_token: refreshToken,
255
+ client_id: this.config.clientId
256
+ });
257
+ const tokens = await this.fetchTokens(body);
258
+ const session = this.tokensToSession(tokens);
259
+ this.store.set(session);
260
+ return session;
261
+ }
262
+ async fetchTokens(body) {
263
+ var _a;
264
+ const res = await fetch(`${this.baseUrl}/api/oauth/token`, {
265
+ method: "POST",
266
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
267
+ body: body.toString()
268
+ });
269
+ if (!res.ok) {
270
+ const err = await res.json().catch(() => ({ error: "unknown_error" }));
271
+ throw new Error((_a = err.error_description) != null ? _a : err.error);
272
+ }
273
+ return res.json();
274
+ }
275
+ tokensToSession(tokens) {
276
+ return {
277
+ accessToken: tokens.access_token,
278
+ refreshToken: tokens.refresh_token,
279
+ expiresAt: Date.now() + tokens.expires_in * 1e3,
280
+ scope: tokens.scope
281
+ };
282
+ }
283
+ };
284
+ var CreditsApi = class {
285
+ constructor(client) {
286
+ this.client = client;
287
+ }
288
+ /**
289
+ * Returns the user's current credit balance.
290
+ * Requires scope: `credits`
291
+ *
292
+ * @example
293
+ * const { balance, formatted } = await proma.credits.getBalance()
294
+ * console.log(`You have ${formatted}`) // "You have $1.23"
295
+ */
296
+ async getBalance() {
297
+ const token = await this.client.requireAccessToken();
298
+ const res = await fetch(`${this.client.baseUrl}/api/sdk/credits/balance`, {
299
+ headers: { Authorization: `Bearer ${token}` }
300
+ });
301
+ if (!res.ok) throw new Error("Failed to fetch credit balance");
302
+ return res.json();
303
+ }
304
+ /**
305
+ * Deducts credits from the user's account.
306
+ * Requires scope: `credits`
307
+ *
308
+ * @param amount - Micro-credits to spend. 1,000,000 = $1.00
309
+ * @param description - Optional description for the transaction ledger.
310
+ *
311
+ * @example
312
+ * await proma.credits.spend(500_000, 'Generated a report')
313
+ */
314
+ async spend(amount, description) {
315
+ const token = await this.client.requireAccessToken();
316
+ const res = await fetch(`${this.client.baseUrl}/api/sdk/credits/spend`, {
317
+ method: "POST",
318
+ headers: {
319
+ Authorization: `Bearer ${token}`,
320
+ "Content-Type": "application/json"
321
+ },
322
+ body: JSON.stringify({ amount, description })
323
+ });
324
+ if (!res.ok) {
325
+ const err = await res.json().catch(() => ({ error: "unknown" }));
326
+ throw new Error(err.error);
327
+ }
328
+ return res.json();
329
+ }
330
+ };
331
+ var AiApi = class {
332
+ constructor(client) {
333
+ this.client = client;
334
+ }
335
+ /**
336
+ * Sends a chat request through the Proma AI gateway (Gemini).
337
+ * Credits are deducted automatically per token used.
338
+ * Requires scope: `ai:chat`
339
+ *
340
+ * Returns a streaming `Response` — iterate SSE chunks or use a helper library.
341
+ *
342
+ * @example
343
+ * const stream = await proma.ai.chat({
344
+ * messages: [{ role: 'user', content: 'Explain quantum entanglement simply.' }]
345
+ * })
346
+ * const reader = stream.body.getReader()
347
+ */
348
+ async chat(options) {
349
+ var _a;
350
+ const token = await this.client.requireAccessToken();
351
+ const params = Array.isArray(options) ? { messages: options } : options;
352
+ return fetch(`${this.client.baseUrl}/api/gateway/chat`, {
353
+ method: "POST",
354
+ headers: {
355
+ Authorization: `Bearer ${token}`,
356
+ "Content-Type": "application/json"
357
+ },
358
+ body: JSON.stringify({
359
+ messages: params.messages,
360
+ model: (_a = params.model) != null ? _a : "gemini-2.0-flash"
361
+ })
362
+ });
363
+ }
364
+ /**
365
+ * Convenience wrapper around `chat` that collects the full streamed text.
366
+ * Use this when you don't need streaming and just want the final string.
367
+ *
368
+ * @example
369
+ * const text = await proma.ai.chatText({
370
+ * messages: [{ role: 'user', content: 'Hello!' }]
371
+ * })
372
+ * console.log(text)
373
+ */
374
+ async chatText(options) {
375
+ var _a, _b, _c, _d, _e, _f, _g;
376
+ const res = await this.chat(options);
377
+ if (!res.ok) {
378
+ const err = await res.json().catch(() => ({ error: "upstream_error" }));
379
+ throw new Error(err.error);
380
+ }
381
+ const reader = (_a = res.body) == null ? void 0 : _a.getReader();
382
+ if (!reader) return "";
383
+ const decoder = new TextDecoder();
384
+ let fullText = "";
385
+ while (true) {
386
+ const { done, value } = await reader.read();
387
+ if (done) break;
388
+ const chunk = decoder.decode(value, { stream: true });
389
+ for (const line of chunk.split("\n")) {
390
+ if (!line.startsWith("data: ")) continue;
391
+ const json = line.slice(6).trim();
392
+ if (json === "[DONE]") continue;
393
+ try {
394
+ const parsed = JSON.parse(json);
395
+ const text = (_g = (_f = (_e = (_d = (_c = (_b = parsed.candidates) == null ? void 0 : _b[0]) == null ? void 0 : _c.content) == null ? void 0 : _d.parts) == null ? void 0 : _e[0]) == null ? void 0 : _f.text) != null ? _g : "";
396
+ fullText += text;
397
+ } catch (e) {
398
+ }
399
+ }
400
+ }
401
+ return fullText;
402
+ }
403
+ };
404
+ // Annotate the CommonJS export names for ESM import in node:
405
+ 0 && (module.exports = {
406
+ MemoryStorage,
407
+ PromaClient
408
+ });
409
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/pkce.ts","../src/storage.ts","../src/client.ts"],"sourcesContent":["export { PromaClient } from './client';\nexport { MemoryStorage } from './storage';\nexport type {\n BalanceResponse,\n ChatMessage,\n ChatOptions,\n OAuthScope,\n PromaClientConfig,\n Session,\n SpendCreditsResponse,\n TokenResponse,\n TokenStorage,\n UserInfo,\n} from './types';\n","/**\n * PKCE helpers — browser + Node 18+ compatible via SubtleCrypto.\n */\n\nconst PKCE_STORAGE_KEY = 'proma_code_verifier';\n\n/**\n * Generates a cryptographically random code_verifier (43–128 chars from unreserved character set).\n */\nexport function generateCodeVerifier(): string {\n const bytes = new Uint8Array(32);\n crypto.getRandomValues(bytes);\n return base64url(bytes);\n}\n\n/**\n * Derives the code_challenge from a code_verifier using SHA-256 (S256 method).\n */\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n const hash = await crypto.subtle.digest('SHA-256', data);\n return base64url(new Uint8Array(hash));\n}\n\n/**\n * Saves the code_verifier to sessionStorage for retrieval after the redirect.\n */\nexport function saveCodeVerifier(verifier: string): void {\n if (typeof sessionStorage !== 'undefined') {\n sessionStorage.setItem(PKCE_STORAGE_KEY, verifier);\n }\n}\n\n/**\n * Reads and removes the code_verifier from sessionStorage.\n */\nexport function consumeCodeVerifier(): string | null {\n if (typeof sessionStorage === 'undefined') return null;\n const verifier = sessionStorage.getItem(PKCE_STORAGE_KEY);\n sessionStorage.removeItem(PKCE_STORAGE_KEY);\n return verifier;\n}\n\nfunction base64url(bytes: Uint8Array): string {\n const base64 = btoa(String.fromCharCode(...bytes));\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n","import type { Session, TokenStorage } from './types';\n\nconst SESSION_KEY = 'proma_session';\n\nexport class TokenStore {\n constructor(private readonly storage: TokenStorage) {}\n\n get(): Session | null {\n try {\n const raw = this.storage.getItem(SESSION_KEY);\n if (!raw) return null;\n return JSON.parse(raw) as Session;\n } catch {\n return null;\n }\n }\n\n set(session: Session): void {\n this.storage.setItem(SESSION_KEY, JSON.stringify(session));\n }\n\n clear(): void {\n this.storage.removeItem(SESSION_KEY);\n // Also clear the PKCE verifier if present\n this.storage.removeItem('proma_code_verifier');\n }\n\n isExpired(session: Session): boolean {\n // Consider expired 30 seconds before actual expiry\n return Date.now() >= session.expiresAt - 30_000;\n }\n}\n\n/** Default in-memory storage for environments without localStorage (SSR, Node). */\nexport class MemoryStorage implements TokenStorage {\n private map = new Map<string, string>();\n getItem(key: string) {\n return this.map.get(key) ?? null;\n }\n setItem(key: string, value: string) {\n this.map.set(key, value);\n }\n removeItem(key: string) {\n this.map.delete(key);\n }\n}\n\nexport function getDefaultStorage(): TokenStorage {\n if (typeof localStorage !== 'undefined') return localStorage;\n return new MemoryStorage();\n}\n","import {\n consumeCodeVerifier,\n generateCodeChallenge,\n generateCodeVerifier,\n saveCodeVerifier,\n} from './pkce';\nimport { TokenStore, getDefaultStorage } from './storage';\nimport type {\n BalanceResponse,\n ChatMessage,\n ChatOptions,\n OAuthScope,\n PromaClientConfig,\n Session,\n SpendCreditsResponse,\n TokenResponse,\n UserInfo,\n} from './types';\n\nconst DEFAULT_BASE_URL = 'https://proma.dev';\n\nexport class PromaClient {\n readonly baseUrl: string;\n private readonly store: TokenStore;\n private readonly defaultScopes: OAuthScope[];\n\n /** Credits API — requires the `credits` scope. */\n readonly credits: CreditsApi;\n\n /** AI gateway API — requires the `ai:chat` scope. */\n readonly ai: AiApi;\n\n constructor(private readonly config: PromaClientConfig) {\n this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;\n this.store = new TokenStore(config.storage ?? getDefaultStorage());\n this.defaultScopes = config.scopes ?? ['profile'];\n this.credits = new CreditsApi(this);\n this.ai = new AiApi(this);\n }\n\n // ---------------------------------------------------------------------------\n // Auth\n // ---------------------------------------------------------------------------\n\n /**\n * Redirects the user to Proma's login page.\n * Call this on a button click — it will navigate away from the current page.\n *\n * @example\n * button.onclick = () => proma.login()\n */\n async login(scopes?: OAuthScope[]): Promise<void> {\n const url = await this.buildAuthorizeUrl(scopes ?? this.defaultScopes);\n window.location.href = url;\n }\n\n /**\n * Builds the authorization URL without navigating.\n * Useful if you want to control the redirect yourself.\n */\n async buildAuthorizeUrl(\n scopes: OAuthScope[] = this.defaultScopes,\n ): Promise<string> {\n const verifier = generateCodeVerifier();\n const challenge = await generateCodeChallenge(verifier);\n saveCodeVerifier(verifier);\n\n // Generate and persist state for CSRF protection\n const state = crypto.randomUUID();\n if (typeof sessionStorage !== 'undefined') {\n sessionStorage.setItem('proma_oauth_state', state);\n }\n\n const url = new URL('/api/oauth/authorize', this.baseUrl);\n url.searchParams.set('client_id', this.config.clientId);\n url.searchParams.set('redirect_uri', this.config.redirectUri);\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('scope', scopes.join(' '));\n url.searchParams.set('state', state);\n url.searchParams.set('code_challenge', challenge);\n url.searchParams.set('code_challenge_method', 'S256');\n\n return url.toString();\n }\n\n /**\n * Handles the OAuth callback. Call this on your redirect page.\n * Reads the `code` from the URL, exchanges it for tokens, and stores the session.\n *\n * @param url - Defaults to `window.location.href`\n * @returns The new session\n *\n * @example\n * // pages/callback.tsx\n * useEffect(() => {\n * proma.handleCallback().then(session => {\n * router.push('/dashboard')\n * })\n * }, [])\n */\n async handleCallback(url?: string): Promise<Session> {\n const href =\n url ?? (typeof window !== 'undefined' ? window.location.href : '');\n const params = new URL(href).searchParams;\n const code = params.get('code');\n const error = params.get('error');\n\n if (error) {\n throw new Error(params.get('error_description') ?? error);\n }\n\n if (!code) {\n throw new Error('No authorization code found in URL');\n }\n\n // Validate state parameter to prevent CSRF attacks\n const returnedState = params.get('state');\n if (typeof sessionStorage !== 'undefined') {\n const expectedState = sessionStorage.getItem('proma_oauth_state');\n sessionStorage.removeItem('proma_oauth_state');\n if (!returnedState || returnedState !== expectedState) {\n throw new Error('Invalid state parameter — possible CSRF attack');\n }\n }\n\n const verifier = consumeCodeVerifier();\n\n const body = new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n redirect_uri: this.config.redirectUri,\n client_id: this.config.clientId,\n });\n\n if (verifier) body.set('code_verifier', verifier);\n\n const tokens = await this.fetchTokens(body);\n const session = this.tokensToSession(tokens);\n this.store.set(session);\n return session;\n }\n\n /**\n * Returns the current session (access token, refresh token, expiry).\n * Automatically refreshes the access token if it is expired.\n * Returns `null` if the user is not logged in.\n */\n async getSession(): Promise<Session | null> {\n const session = this.store.get();\n if (!session) return null;\n\n if (this.store.isExpired(session)) {\n try {\n return await this.refresh(session.refreshToken);\n } catch {\n this.store.clear();\n return null;\n }\n }\n\n return session;\n }\n\n /**\n * Returns `true` if the user has a valid (or refreshable) session.\n */\n async isAuthenticated(): Promise<boolean> {\n return (await this.getSession()) !== null;\n }\n\n /**\n * Fetches the logged-in user's profile.\n * Requires the `profile` scope.\n */\n async getUser(): Promise<UserInfo> {\n const token = await this.requireAccessToken();\n const res = await fetch(`${this.baseUrl}/api/oauth/userinfo`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) throw new Error('Failed to fetch user info');\n return res.json() as Promise<UserInfo>;\n }\n\n /**\n * Clears the stored session and logs the user out.\n * Does not revoke the token server-side.\n */\n logout(): void {\n this.store.clear();\n }\n\n // ---------------------------------------------------------------------------\n // Internal helpers (used by sub-APIs)\n // ---------------------------------------------------------------------------\n\n async requireAccessToken(): Promise<string> {\n const session = await this.getSession();\n if (!session)\n throw new Error('Not authenticated — call proma.login() first');\n return session.accessToken;\n }\n\n private async refresh(refreshToken: string): Promise<Session> {\n const body = new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: this.config.clientId,\n });\n const tokens = await this.fetchTokens(body);\n const session = this.tokensToSession(tokens);\n this.store.set(session);\n return session;\n }\n\n private async fetchTokens(body: URLSearchParams): Promise<TokenResponse> {\n const res = await fetch(`${this.baseUrl}/api/oauth/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n if (!res.ok) {\n const err = (await res\n .json()\n .catch(() => ({ error: 'unknown_error' }))) as {\n error: string;\n error_description?: string;\n };\n throw new Error(err.error_description ?? err.error);\n }\n return res.json() as Promise<TokenResponse>;\n }\n\n private tokensToSession(tokens: TokenResponse): Session {\n return {\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token,\n expiresAt: Date.now() + tokens.expires_in * 1000,\n scope: tokens.scope,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Credits API\n// ---------------------------------------------------------------------------\n\nclass CreditsApi {\n constructor(private readonly client: PromaClient) {}\n\n /**\n * Returns the user's current credit balance.\n * Requires scope: `credits`\n *\n * @example\n * const { balance, formatted } = await proma.credits.getBalance()\n * console.log(`You have ${formatted}`) // \"You have $1.23\"\n */\n async getBalance(): Promise<BalanceResponse> {\n const token = await this.client.requireAccessToken();\n const res = await fetch(`${this.client.baseUrl}/api/sdk/credits/balance`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) throw new Error('Failed to fetch credit balance');\n return res.json() as Promise<BalanceResponse>;\n }\n\n /**\n * Deducts credits from the user's account.\n * Requires scope: `credits`\n *\n * @param amount - Micro-credits to spend. 1,000,000 = $1.00\n * @param description - Optional description for the transaction ledger.\n *\n * @example\n * await proma.credits.spend(500_000, 'Generated a report')\n */\n async spend(\n amount: number,\n description?: string,\n ): Promise<SpendCreditsResponse> {\n const token = await this.client.requireAccessToken();\n const res = await fetch(`${this.client.baseUrl}/api/sdk/credits/spend`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ amount, description }),\n });\n if (!res.ok) {\n const err = (await res.json().catch(() => ({ error: 'unknown' }))) as {\n error: string;\n };\n throw new Error(err.error);\n }\n return res.json() as Promise<SpendCreditsResponse>;\n }\n}\n\n// ---------------------------------------------------------------------------\n// AI API\n// ---------------------------------------------------------------------------\n\nclass AiApi {\n constructor(private readonly client: PromaClient) {}\n\n /**\n * Sends a chat request through the Proma AI gateway (Gemini).\n * Credits are deducted automatically per token used.\n * Requires scope: `ai:chat`\n *\n * Returns a streaming `Response` — iterate SSE chunks or use a helper library.\n *\n * @example\n * const stream = await proma.ai.chat({\n * messages: [{ role: 'user', content: 'Explain quantum entanglement simply.' }]\n * })\n * const reader = stream.body.getReader()\n */\n async chat(options: ChatOptions | ChatMessage[]): Promise<Response> {\n const token = await this.client.requireAccessToken();\n const params: ChatOptions = Array.isArray(options)\n ? { messages: options }\n : options;\n\n return fetch(`${this.client.baseUrl}/api/gateway/chat`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n messages: params.messages,\n model: params.model ?? 'gemini-2.0-flash',\n }),\n });\n }\n\n /**\n * Convenience wrapper around `chat` that collects the full streamed text.\n * Use this when you don't need streaming and just want the final string.\n *\n * @example\n * const text = await proma.ai.chatText({\n * messages: [{ role: 'user', content: 'Hello!' }]\n * })\n * console.log(text)\n */\n async chatText(options: ChatOptions | ChatMessage[]): Promise<string> {\n const res = await this.chat(options);\n if (!res.ok) {\n const err = (await res\n .json()\n .catch(() => ({ error: 'upstream_error' }))) as { error: string };\n throw new Error(err.error);\n }\n\n const reader = res.body?.getReader();\n if (!reader) return '';\n\n const decoder = new TextDecoder();\n let fullText = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const chunk = decoder.decode(value, { stream: true });\n // Parse SSE lines: \"data: {...}\"\n for (const line of chunk.split('\\n')) {\n if (!line.startsWith('data: ')) continue;\n const json = line.slice(6).trim();\n if (json === '[DONE]') continue;\n try {\n const parsed = JSON.parse(json) as {\n candidates?: Array<{\n content?: { parts?: Array<{ text?: string }> };\n }>;\n };\n const text = parsed.candidates?.[0]?.content?.parts?.[0]?.text ?? '';\n fullText += text;\n } catch {\n // skip malformed chunks\n }\n }\n }\n\n return fullText;\n }\n}\n"],"mappings":"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","names":[]}