authtara-sdk 1.0.0 → 1.1.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.
@@ -0,0 +1,52 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+
4
+ interface User {
5
+ id: string;
6
+ email: string;
7
+ name?: string | null;
8
+ avatar?: string | null;
9
+ }
10
+ interface AuthTaraConfig {
11
+ appId: string;
12
+ apiUrl: string;
13
+ }
14
+ interface AuthTaraContextValue {
15
+ isAuthenticated: boolean;
16
+ user: User | null;
17
+ isLoading: boolean;
18
+ signIn: (provider: 'google' | 'github' | 'credentials', options?: SignInOptions) => Promise<void>;
19
+ signOut: () => Promise<void>;
20
+ refreshSession: () => Promise<void>;
21
+ }
22
+ interface SignInOptions {
23
+ email?: string;
24
+ password?: string;
25
+ redirectPath?: string;
26
+ }
27
+ declare function AuthTaraProvider({ children, appId, apiUrl, }: {
28
+ children: React.ReactNode;
29
+ } & AuthTaraConfig): react_jsx_runtime.JSX.Element;
30
+ declare function useAuth(): AuthTaraContextValue;
31
+
32
+ /**
33
+ * AuthTaraAuth Component
34
+ *
35
+ * Embeddable login/register component
36
+ * Renders social login buttons and handles OAuth popup flow
37
+ */
38
+ interface AuthTaraAuthProps {
39
+ mode?: 'modal' | 'redirect';
40
+ appearance?: {
41
+ theme?: 'light' | 'dark';
42
+ variables?: Record<string, string>;
43
+ };
44
+ redirectUrl?: string;
45
+ onSuccess?: (session: {
46
+ user: any;
47
+ }) => void;
48
+ onError?: (error: Error) => void;
49
+ }
50
+ declare function AuthTaraAuth({ appearance, redirectUrl, onSuccess, onError, }: AuthTaraAuthProps): react_jsx_runtime.JSX.Element;
51
+
52
+ export { AuthTaraAuth, type AuthTaraAuthProps, type AuthTaraConfig, type AuthTaraContextValue, AuthTaraProvider, type SignInOptions, type User, useAuth };
@@ -0,0 +1,52 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+
4
+ interface User {
5
+ id: string;
6
+ email: string;
7
+ name?: string | null;
8
+ avatar?: string | null;
9
+ }
10
+ interface AuthTaraConfig {
11
+ appId: string;
12
+ apiUrl: string;
13
+ }
14
+ interface AuthTaraContextValue {
15
+ isAuthenticated: boolean;
16
+ user: User | null;
17
+ isLoading: boolean;
18
+ signIn: (provider: 'google' | 'github' | 'credentials', options?: SignInOptions) => Promise<void>;
19
+ signOut: () => Promise<void>;
20
+ refreshSession: () => Promise<void>;
21
+ }
22
+ interface SignInOptions {
23
+ email?: string;
24
+ password?: string;
25
+ redirectPath?: string;
26
+ }
27
+ declare function AuthTaraProvider({ children, appId, apiUrl, }: {
28
+ children: React.ReactNode;
29
+ } & AuthTaraConfig): react_jsx_runtime.JSX.Element;
30
+ declare function useAuth(): AuthTaraContextValue;
31
+
32
+ /**
33
+ * AuthTaraAuth Component
34
+ *
35
+ * Embeddable login/register component
36
+ * Renders social login buttons and handles OAuth popup flow
37
+ */
38
+ interface AuthTaraAuthProps {
39
+ mode?: 'modal' | 'redirect';
40
+ appearance?: {
41
+ theme?: 'light' | 'dark';
42
+ variables?: Record<string, string>;
43
+ };
44
+ redirectUrl?: string;
45
+ onSuccess?: (session: {
46
+ user: any;
47
+ }) => void;
48
+ onError?: (error: Error) => void;
49
+ }
50
+ declare function AuthTaraAuth({ appearance, redirectUrl, onSuccess, onError, }: AuthTaraAuthProps): react_jsx_runtime.JSX.Element;
51
+
52
+ export { AuthTaraAuth, type AuthTaraAuthProps, type AuthTaraConfig, type AuthTaraContextValue, AuthTaraProvider, type SignInOptions, type User, useAuth };
package/dist/react.js ADDED
@@ -0,0 +1,460 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/react.ts
31
+ var react_exports = {};
32
+ __export(react_exports, {
33
+ AuthTaraAuth: () => AuthTaraAuth,
34
+ AuthTaraProvider: () => AuthTaraProvider,
35
+ useAuth: () => useAuth
36
+ });
37
+ module.exports = __toCommonJS(react_exports);
38
+
39
+ // src/components/providers/AuthTaraProvider.tsx
40
+ var React = __toESM(require("react"));
41
+
42
+ // src/oauth/popup.ts
43
+ var OAuthPopup = class {
44
+ constructor(apiUrl) {
45
+ this.popup = null;
46
+ try {
47
+ const url = new URL(apiUrl);
48
+ this.expectedOrigin = url.origin;
49
+ } catch {
50
+ this.expectedOrigin = apiUrl;
51
+ }
52
+ }
53
+ /**
54
+ * Open popup window for OAuth flow
55
+ */
56
+ async open(url) {
57
+ const width = 500;
58
+ const height = 700;
59
+ const left = window.screen.width / 2 - width / 2;
60
+ const top = window.screen.height / 2 - height / 2;
61
+ this.popup = window.open(
62
+ url,
63
+ "authtara-oauth",
64
+ `width=${width},height=${height},left=${left},top=${top},popup=1,scrollbars=1`
65
+ );
66
+ if (!this.popup) {
67
+ throw new Error("Popup blocked. Please allow popups for this site.");
68
+ }
69
+ this.popup.focus();
70
+ return new Promise((resolve, reject) => {
71
+ this.messageListener = (event) => {
72
+ if (event.origin !== this.expectedOrigin) {
73
+ console.warn("Received message from unexpected origin:", event.origin);
74
+ return;
75
+ }
76
+ const { type, payload, error } = event.data;
77
+ if (type === "OAUTH_SUCCESS" && payload) {
78
+ this.cleanup();
79
+ resolve(payload);
80
+ } else if (type === "OAUTH_ERROR") {
81
+ this.cleanup();
82
+ reject(new Error(error || "OAuth authentication failed"));
83
+ } else if (type === "OAUTH_CANCEL") {
84
+ this.cleanup();
85
+ reject(new Error("Authentication cancelled by user"));
86
+ }
87
+ };
88
+ window.addEventListener("message", this.messageListener);
89
+ this.pollTimer = setInterval(() => {
90
+ if (this.popup?.closed) {
91
+ this.cleanup();
92
+ reject(new Error("Authentication window was closed"));
93
+ }
94
+ }, 500);
95
+ setTimeout(() => {
96
+ if (this.popup && !this.popup.closed) {
97
+ this.cleanup();
98
+ reject(new Error("Authentication timed out"));
99
+ }
100
+ }, 5 * 60 * 1e3);
101
+ });
102
+ }
103
+ /**
104
+ * Cleanup resources
105
+ */
106
+ cleanup() {
107
+ if (this.messageListener) {
108
+ window.removeEventListener("message", this.messageListener);
109
+ this.messageListener = void 0;
110
+ }
111
+ if (this.pollTimer) {
112
+ clearInterval(this.pollTimer);
113
+ this.pollTimer = void 0;
114
+ }
115
+ if (this.popup && !this.popup.closed) {
116
+ this.popup.close();
117
+ }
118
+ this.popup = null;
119
+ }
120
+ /**
121
+ * Manually close popup
122
+ */
123
+ close() {
124
+ this.cleanup();
125
+ }
126
+ };
127
+
128
+ // src/components/providers/AuthTaraProvider.tsx
129
+ var import_jsx_runtime = require("react/jsx-runtime");
130
+ var AuthTaraContext = React.createContext(null);
131
+ function AuthTaraProvider({
132
+ children,
133
+ appId,
134
+ apiUrl
135
+ }) {
136
+ const [user, setUser] = React.useState(null);
137
+ const [isLoading, setIsLoading] = React.useState(true);
138
+ React.useEffect(() => {
139
+ loadSession();
140
+ }, []);
141
+ const loadSession = React.useCallback(async () => {
142
+ try {
143
+ const response = await fetch("/api/auth/me", {
144
+ credentials: "include"
145
+ });
146
+ if (response.ok) {
147
+ const data = await response.json();
148
+ setUser(data.user);
149
+ } else {
150
+ setUser(null);
151
+ }
152
+ } catch (error) {
153
+ console.error("[AuthTara] Failed to load session:", error);
154
+ setUser(null);
155
+ } finally {
156
+ setIsLoading(false);
157
+ }
158
+ }, []);
159
+ const signIn = React.useCallback(
160
+ async (provider, options) => {
161
+ try {
162
+ setIsLoading(true);
163
+ if (provider === "credentials") {
164
+ throw new Error("Credentials flow not yet implemented in popup mode");
165
+ }
166
+ const callbackUrl = `${window.location.origin}/api/sso/callback`;
167
+ const state = btoa(JSON.stringify({
168
+ redirectPath: options?.redirectPath || "/dashboard",
169
+ timestamp: Date.now()
170
+ }));
171
+ const params = new URLSearchParams({
172
+ response_type: "code",
173
+ provider,
174
+ // ← NEW: Pass provider for Clerk-style direct redirect
175
+ client_id: appId,
176
+ redirect_uri: callbackUrl,
177
+ state,
178
+ scope: "read write"
179
+ });
180
+ const authorizeUrl = `${apiUrl}/api/oauth/authorize?${params.toString()}`;
181
+ const popup = new OAuthPopup(apiUrl);
182
+ const { code: authCode, state: returnedState } = await popup.open(authorizeUrl);
183
+ const exchangeResponse = await fetch("/api/sso/callback", {
184
+ method: "POST",
185
+ headers: { "Content-Type": "application/json" },
186
+ credentials: "include",
187
+ body: JSON.stringify({
188
+ code: authCode,
189
+ state: returnedState
190
+ })
191
+ });
192
+ if (!exchangeResponse.ok) {
193
+ const error = await exchangeResponse.json();
194
+ throw new Error(error.message || "Token exchange failed");
195
+ }
196
+ await loadSession();
197
+ } catch (error) {
198
+ console.error("[AuthTara] Sign in failed:", error);
199
+ throw error;
200
+ } finally {
201
+ setIsLoading(false);
202
+ }
203
+ },
204
+ [appId, apiUrl, loadSession]
205
+ );
206
+ const signOut = React.useCallback(async () => {
207
+ try {
208
+ await fetch("/api/auth/logout", {
209
+ method: "POST",
210
+ credentials: "include"
211
+ });
212
+ setUser(null);
213
+ } catch (error) {
214
+ console.error("[AuthTara] Sign out failed:", error);
215
+ throw error;
216
+ }
217
+ }, []);
218
+ const refreshSession = React.useCallback(async () => {
219
+ await loadSession();
220
+ }, [loadSession]);
221
+ const value = {
222
+ isAuthenticated: !!user,
223
+ user,
224
+ isLoading,
225
+ signIn,
226
+ signOut,
227
+ refreshSession
228
+ };
229
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthTaraContext.Provider, { value, children });
230
+ }
231
+ function useAuth() {
232
+ const context = React.useContext(AuthTaraContext);
233
+ if (!context) {
234
+ throw new Error("useAuth must be used within AuthTaraProvider");
235
+ }
236
+ return context;
237
+ }
238
+
239
+ // src/components/AuthTaraAuth.tsx
240
+ var React2 = __toESM(require("react"));
241
+ var import_jsx_runtime2 = require("react/jsx-runtime");
242
+ function AuthTaraAuth({
243
+ // mode = 'modal', // Reserved for future use
244
+ appearance,
245
+ redirectUrl = "/dashboard",
246
+ onSuccess,
247
+ onError
248
+ }) {
249
+ const { signIn, isLoading, user } = useAuth();
250
+ const [error, setError] = React2.useState(null);
251
+ const [provider, setProvider] = React2.useState(null);
252
+ async function handleSignIn(selectedProvider) {
253
+ setError(null);
254
+ setProvider(selectedProvider);
255
+ try {
256
+ await signIn(selectedProvider, { redirectPath: redirectUrl });
257
+ onSuccess?.({ user });
258
+ if (redirectUrl) {
259
+ window.location.href = redirectUrl;
260
+ }
261
+ } catch (err) {
262
+ const message = err instanceof Error ? err.message : "Authentication failed";
263
+ setError(message);
264
+ onError?.(err);
265
+ } finally {
266
+ setProvider(null);
267
+ }
268
+ }
269
+ const isProviderLoading = (p) => isLoading && provider === p;
270
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "authtara-auth-container", style: { maxWidth: "400px", margin: "0 auto" }, children: [
271
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
272
+ "div",
273
+ {
274
+ className: "authtara-auth-card",
275
+ style: {
276
+ padding: "2rem",
277
+ border: "1px solid #e5e7eb",
278
+ borderRadius: "0.5rem",
279
+ backgroundColor: appearance?.theme === "dark" ? "#1f2937" : "#ffffff"
280
+ },
281
+ children: [
282
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
283
+ "h2",
284
+ {
285
+ style: {
286
+ fontSize: "1.5rem",
287
+ fontWeight: "bold",
288
+ marginBottom: "1.5rem",
289
+ color: appearance?.theme === "dark" ? "#ffffff" : "#111827"
290
+ },
291
+ children: "Sign in to continue"
292
+ }
293
+ ),
294
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
295
+ "div",
296
+ {
297
+ style: {
298
+ padding: "0.75rem",
299
+ marginBottom: "1rem",
300
+ backgroundColor: "#fee2e2",
301
+ color: "#991b1b",
302
+ borderRadius: "0.375rem",
303
+ fontSize: "0.875rem"
304
+ },
305
+ children: error
306
+ }
307
+ ),
308
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem" }, children: [
309
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
310
+ "button",
311
+ {
312
+ onClick: () => handleSignIn("google"),
313
+ disabled: isLoading,
314
+ style: {
315
+ display: "flex",
316
+ alignItems: "center",
317
+ justifyContent: "center",
318
+ gap: "0.5rem",
319
+ padding: "0.75rem 1rem",
320
+ border: "1px solid #d1d5db",
321
+ borderRadius: "0.375rem",
322
+ backgroundColor: "#ffffff",
323
+ color: "#374151",
324
+ fontSize: "0.875rem",
325
+ fontWeight: "500",
326
+ cursor: isLoading ? "not-allowed" : "pointer",
327
+ opacity: isLoading ? 0.6 : 1,
328
+ transition: "all 0.2s"
329
+ },
330
+ onMouseOver: (e) => {
331
+ if (!isLoading) {
332
+ e.currentTarget.style.backgroundColor = "#f9fafb";
333
+ }
334
+ },
335
+ onMouseOut: (e) => {
336
+ e.currentTarget.style.backgroundColor = "#ffffff";
337
+ },
338
+ children: [
339
+ isProviderLoading("google") ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
340
+ "div",
341
+ {
342
+ style: {
343
+ width: "1rem",
344
+ height: "1rem",
345
+ border: "2px solid #d1d5db",
346
+ borderTopColor: "#3b82f6",
347
+ borderRadius: "50%",
348
+ animation: "spin 1s linear infinite"
349
+ }
350
+ }
351
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 18 18", children: [
352
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
353
+ "path",
354
+ {
355
+ fill: "#4285F4",
356
+ d: "M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.875 2.684-6.615z"
357
+ }
358
+ ),
359
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
360
+ "path",
361
+ {
362
+ fill: "#34A853",
363
+ d: "M9.003 18c2.43 0 4.467-.806 5.956-2.18L12.05 13.56c-.806.54-1.837.86-3.047.86-2.344 0-4.328-1.584-5.036-3.711H.96v2.332C2.44 15.983 5.485 18 9.003 18z"
364
+ }
365
+ ),
366
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
367
+ "path",
368
+ {
369
+ fill: "#FBBC05",
370
+ d: "M3.964 10.712c-.18-.54-.282-1.117-.282-1.71 0-.593.102-1.17.282-1.71V4.96H.957C.347 6.175 0 7.55 0 9.002c0 1.452.348 2.827.957 4.042l3.007-2.332z"
371
+ }
372
+ ),
373
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
374
+ "path",
375
+ {
376
+ fill: "#EA4335",
377
+ d: "M9.003 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.464.891 11.428 0 9.003 0 5.485 0 2.44 2.017.96 4.958L3.967 7.29c.708-2.127 2.692-3.71 5.036-3.71z"
378
+ }
379
+ )
380
+ ] }),
381
+ "Continue with Google"
382
+ ]
383
+ }
384
+ ),
385
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
386
+ "button",
387
+ {
388
+ onClick: () => handleSignIn("github"),
389
+ disabled: isLoading,
390
+ style: {
391
+ display: "flex",
392
+ alignItems: "center",
393
+ justifyContent: "center",
394
+ gap: "0.5rem",
395
+ padding: "0.75rem 1rem",
396
+ border: "1px solid #d1d5db",
397
+ borderRadius: "0.375rem",
398
+ backgroundColor: "#24292e",
399
+ color: "#ffffff",
400
+ fontSize: "0.875rem",
401
+ fontWeight: "500",
402
+ cursor: isLoading ? "not-allowed" : "pointer",
403
+ opacity: isLoading ? 0.6 : 1,
404
+ transition: "all 0.2s"
405
+ },
406
+ onMouseOver: (e) => {
407
+ if (!isLoading) {
408
+ e.currentTarget.style.backgroundColor = "#1b1f23";
409
+ }
410
+ },
411
+ onMouseOut: (e) => {
412
+ e.currentTarget.style.backgroundColor = "#24292e";
413
+ },
414
+ children: [
415
+ isProviderLoading("github") ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
416
+ "div",
417
+ {
418
+ style: {
419
+ width: "1rem",
420
+ height: "1rem",
421
+ border: "2px solid #ffffff40",
422
+ borderTopColor: "#ffffff",
423
+ borderRadius: "50%",
424
+ animation: "spin 1s linear infinite"
425
+ }
426
+ }
427
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "18", height: "18", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" }) }),
428
+ "Continue with GitHub"
429
+ ]
430
+ }
431
+ )
432
+ ] }),
433
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
434
+ "p",
435
+ {
436
+ style: {
437
+ marginTop: "1.5rem",
438
+ fontSize: "0.75rem",
439
+ color: appearance?.theme === "dark" ? "#9ca3af" : "#6b7280",
440
+ textAlign: "center"
441
+ },
442
+ children: "By continuing, you agree to our Terms of Service and Privacy Policy"
443
+ }
444
+ )
445
+ ]
446
+ }
447
+ ),
448
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `
449
+ @keyframes spin {
450
+ to { transform: rotate(360deg); }
451
+ }
452
+ ` })
453
+ ] });
454
+ }
455
+ // Annotate the CommonJS export names for ESM import in node:
456
+ 0 && (module.exports = {
457
+ AuthTaraAuth,
458
+ AuthTaraProvider,
459
+ useAuth
460
+ });
package/dist/react.mjs ADDED
@@ -0,0 +1,421 @@
1
+ // src/components/providers/AuthTaraProvider.tsx
2
+ import * as React from "react";
3
+
4
+ // src/oauth/popup.ts
5
+ var OAuthPopup = class {
6
+ constructor(apiUrl) {
7
+ this.popup = null;
8
+ try {
9
+ const url = new URL(apiUrl);
10
+ this.expectedOrigin = url.origin;
11
+ } catch {
12
+ this.expectedOrigin = apiUrl;
13
+ }
14
+ }
15
+ /**
16
+ * Open popup window for OAuth flow
17
+ */
18
+ async open(url) {
19
+ const width = 500;
20
+ const height = 700;
21
+ const left = window.screen.width / 2 - width / 2;
22
+ const top = window.screen.height / 2 - height / 2;
23
+ this.popup = window.open(
24
+ url,
25
+ "authtara-oauth",
26
+ `width=${width},height=${height},left=${left},top=${top},popup=1,scrollbars=1`
27
+ );
28
+ if (!this.popup) {
29
+ throw new Error("Popup blocked. Please allow popups for this site.");
30
+ }
31
+ this.popup.focus();
32
+ return new Promise((resolve, reject) => {
33
+ this.messageListener = (event) => {
34
+ if (event.origin !== this.expectedOrigin) {
35
+ console.warn("Received message from unexpected origin:", event.origin);
36
+ return;
37
+ }
38
+ const { type, payload, error } = event.data;
39
+ if (type === "OAUTH_SUCCESS" && payload) {
40
+ this.cleanup();
41
+ resolve(payload);
42
+ } else if (type === "OAUTH_ERROR") {
43
+ this.cleanup();
44
+ reject(new Error(error || "OAuth authentication failed"));
45
+ } else if (type === "OAUTH_CANCEL") {
46
+ this.cleanup();
47
+ reject(new Error("Authentication cancelled by user"));
48
+ }
49
+ };
50
+ window.addEventListener("message", this.messageListener);
51
+ this.pollTimer = setInterval(() => {
52
+ if (this.popup?.closed) {
53
+ this.cleanup();
54
+ reject(new Error("Authentication window was closed"));
55
+ }
56
+ }, 500);
57
+ setTimeout(() => {
58
+ if (this.popup && !this.popup.closed) {
59
+ this.cleanup();
60
+ reject(new Error("Authentication timed out"));
61
+ }
62
+ }, 5 * 60 * 1e3);
63
+ });
64
+ }
65
+ /**
66
+ * Cleanup resources
67
+ */
68
+ cleanup() {
69
+ if (this.messageListener) {
70
+ window.removeEventListener("message", this.messageListener);
71
+ this.messageListener = void 0;
72
+ }
73
+ if (this.pollTimer) {
74
+ clearInterval(this.pollTimer);
75
+ this.pollTimer = void 0;
76
+ }
77
+ if (this.popup && !this.popup.closed) {
78
+ this.popup.close();
79
+ }
80
+ this.popup = null;
81
+ }
82
+ /**
83
+ * Manually close popup
84
+ */
85
+ close() {
86
+ this.cleanup();
87
+ }
88
+ };
89
+
90
+ // src/components/providers/AuthTaraProvider.tsx
91
+ import { jsx } from "react/jsx-runtime";
92
+ var AuthTaraContext = React.createContext(null);
93
+ function AuthTaraProvider({
94
+ children,
95
+ appId,
96
+ apiUrl
97
+ }) {
98
+ const [user, setUser] = React.useState(null);
99
+ const [isLoading, setIsLoading] = React.useState(true);
100
+ React.useEffect(() => {
101
+ loadSession();
102
+ }, []);
103
+ const loadSession = React.useCallback(async () => {
104
+ try {
105
+ const response = await fetch("/api/auth/me", {
106
+ credentials: "include"
107
+ });
108
+ if (response.ok) {
109
+ const data = await response.json();
110
+ setUser(data.user);
111
+ } else {
112
+ setUser(null);
113
+ }
114
+ } catch (error) {
115
+ console.error("[AuthTara] Failed to load session:", error);
116
+ setUser(null);
117
+ } finally {
118
+ setIsLoading(false);
119
+ }
120
+ }, []);
121
+ const signIn = React.useCallback(
122
+ async (provider, options) => {
123
+ try {
124
+ setIsLoading(true);
125
+ if (provider === "credentials") {
126
+ throw new Error("Credentials flow not yet implemented in popup mode");
127
+ }
128
+ const callbackUrl = `${window.location.origin}/api/sso/callback`;
129
+ const state = btoa(JSON.stringify({
130
+ redirectPath: options?.redirectPath || "/dashboard",
131
+ timestamp: Date.now()
132
+ }));
133
+ const params = new URLSearchParams({
134
+ response_type: "code",
135
+ provider,
136
+ // ← NEW: Pass provider for Clerk-style direct redirect
137
+ client_id: appId,
138
+ redirect_uri: callbackUrl,
139
+ state,
140
+ scope: "read write"
141
+ });
142
+ const authorizeUrl = `${apiUrl}/api/oauth/authorize?${params.toString()}`;
143
+ const popup = new OAuthPopup(apiUrl);
144
+ const { code: authCode, state: returnedState } = await popup.open(authorizeUrl);
145
+ const exchangeResponse = await fetch("/api/sso/callback", {
146
+ method: "POST",
147
+ headers: { "Content-Type": "application/json" },
148
+ credentials: "include",
149
+ body: JSON.stringify({
150
+ code: authCode,
151
+ state: returnedState
152
+ })
153
+ });
154
+ if (!exchangeResponse.ok) {
155
+ const error = await exchangeResponse.json();
156
+ throw new Error(error.message || "Token exchange failed");
157
+ }
158
+ await loadSession();
159
+ } catch (error) {
160
+ console.error("[AuthTara] Sign in failed:", error);
161
+ throw error;
162
+ } finally {
163
+ setIsLoading(false);
164
+ }
165
+ },
166
+ [appId, apiUrl, loadSession]
167
+ );
168
+ const signOut = React.useCallback(async () => {
169
+ try {
170
+ await fetch("/api/auth/logout", {
171
+ method: "POST",
172
+ credentials: "include"
173
+ });
174
+ setUser(null);
175
+ } catch (error) {
176
+ console.error("[AuthTara] Sign out failed:", error);
177
+ throw error;
178
+ }
179
+ }, []);
180
+ const refreshSession = React.useCallback(async () => {
181
+ await loadSession();
182
+ }, [loadSession]);
183
+ const value = {
184
+ isAuthenticated: !!user,
185
+ user,
186
+ isLoading,
187
+ signIn,
188
+ signOut,
189
+ refreshSession
190
+ };
191
+ return /* @__PURE__ */ jsx(AuthTaraContext.Provider, { value, children });
192
+ }
193
+ function useAuth() {
194
+ const context = React.useContext(AuthTaraContext);
195
+ if (!context) {
196
+ throw new Error("useAuth must be used within AuthTaraProvider");
197
+ }
198
+ return context;
199
+ }
200
+
201
+ // src/components/AuthTaraAuth.tsx
202
+ import * as React2 from "react";
203
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
204
+ function AuthTaraAuth({
205
+ // mode = 'modal', // Reserved for future use
206
+ appearance,
207
+ redirectUrl = "/dashboard",
208
+ onSuccess,
209
+ onError
210
+ }) {
211
+ const { signIn, isLoading, user } = useAuth();
212
+ const [error, setError] = React2.useState(null);
213
+ const [provider, setProvider] = React2.useState(null);
214
+ async function handleSignIn(selectedProvider) {
215
+ setError(null);
216
+ setProvider(selectedProvider);
217
+ try {
218
+ await signIn(selectedProvider, { redirectPath: redirectUrl });
219
+ onSuccess?.({ user });
220
+ if (redirectUrl) {
221
+ window.location.href = redirectUrl;
222
+ }
223
+ } catch (err) {
224
+ const message = err instanceof Error ? err.message : "Authentication failed";
225
+ setError(message);
226
+ onError?.(err);
227
+ } finally {
228
+ setProvider(null);
229
+ }
230
+ }
231
+ const isProviderLoading = (p) => isLoading && provider === p;
232
+ return /* @__PURE__ */ jsxs("div", { className: "authtara-auth-container", style: { maxWidth: "400px", margin: "0 auto" }, children: [
233
+ /* @__PURE__ */ jsxs(
234
+ "div",
235
+ {
236
+ className: "authtara-auth-card",
237
+ style: {
238
+ padding: "2rem",
239
+ border: "1px solid #e5e7eb",
240
+ borderRadius: "0.5rem",
241
+ backgroundColor: appearance?.theme === "dark" ? "#1f2937" : "#ffffff"
242
+ },
243
+ children: [
244
+ /* @__PURE__ */ jsx2(
245
+ "h2",
246
+ {
247
+ style: {
248
+ fontSize: "1.5rem",
249
+ fontWeight: "bold",
250
+ marginBottom: "1.5rem",
251
+ color: appearance?.theme === "dark" ? "#ffffff" : "#111827"
252
+ },
253
+ children: "Sign in to continue"
254
+ }
255
+ ),
256
+ error && /* @__PURE__ */ jsx2(
257
+ "div",
258
+ {
259
+ style: {
260
+ padding: "0.75rem",
261
+ marginBottom: "1rem",
262
+ backgroundColor: "#fee2e2",
263
+ color: "#991b1b",
264
+ borderRadius: "0.375rem",
265
+ fontSize: "0.875rem"
266
+ },
267
+ children: error
268
+ }
269
+ ),
270
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem" }, children: [
271
+ /* @__PURE__ */ jsxs(
272
+ "button",
273
+ {
274
+ onClick: () => handleSignIn("google"),
275
+ disabled: isLoading,
276
+ style: {
277
+ display: "flex",
278
+ alignItems: "center",
279
+ justifyContent: "center",
280
+ gap: "0.5rem",
281
+ padding: "0.75rem 1rem",
282
+ border: "1px solid #d1d5db",
283
+ borderRadius: "0.375rem",
284
+ backgroundColor: "#ffffff",
285
+ color: "#374151",
286
+ fontSize: "0.875rem",
287
+ fontWeight: "500",
288
+ cursor: isLoading ? "not-allowed" : "pointer",
289
+ opacity: isLoading ? 0.6 : 1,
290
+ transition: "all 0.2s"
291
+ },
292
+ onMouseOver: (e) => {
293
+ if (!isLoading) {
294
+ e.currentTarget.style.backgroundColor = "#f9fafb";
295
+ }
296
+ },
297
+ onMouseOut: (e) => {
298
+ e.currentTarget.style.backgroundColor = "#ffffff";
299
+ },
300
+ children: [
301
+ isProviderLoading("google") ? /* @__PURE__ */ jsx2(
302
+ "div",
303
+ {
304
+ style: {
305
+ width: "1rem",
306
+ height: "1rem",
307
+ border: "2px solid #d1d5db",
308
+ borderTopColor: "#3b82f6",
309
+ borderRadius: "50%",
310
+ animation: "spin 1s linear infinite"
311
+ }
312
+ }
313
+ ) : /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 18 18", children: [
314
+ /* @__PURE__ */ jsx2(
315
+ "path",
316
+ {
317
+ fill: "#4285F4",
318
+ d: "M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.875 2.684-6.615z"
319
+ }
320
+ ),
321
+ /* @__PURE__ */ jsx2(
322
+ "path",
323
+ {
324
+ fill: "#34A853",
325
+ d: "M9.003 18c2.43 0 4.467-.806 5.956-2.18L12.05 13.56c-.806.54-1.837.86-3.047.86-2.344 0-4.328-1.584-5.036-3.711H.96v2.332C2.44 15.983 5.485 18 9.003 18z"
326
+ }
327
+ ),
328
+ /* @__PURE__ */ jsx2(
329
+ "path",
330
+ {
331
+ fill: "#FBBC05",
332
+ d: "M3.964 10.712c-.18-.54-.282-1.117-.282-1.71 0-.593.102-1.17.282-1.71V4.96H.957C.347 6.175 0 7.55 0 9.002c0 1.452.348 2.827.957 4.042l3.007-2.332z"
333
+ }
334
+ ),
335
+ /* @__PURE__ */ jsx2(
336
+ "path",
337
+ {
338
+ fill: "#EA4335",
339
+ d: "M9.003 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.464.891 11.428 0 9.003 0 5.485 0 2.44 2.017.96 4.958L3.967 7.29c.708-2.127 2.692-3.71 5.036-3.71z"
340
+ }
341
+ )
342
+ ] }),
343
+ "Continue with Google"
344
+ ]
345
+ }
346
+ ),
347
+ /* @__PURE__ */ jsxs(
348
+ "button",
349
+ {
350
+ onClick: () => handleSignIn("github"),
351
+ disabled: isLoading,
352
+ style: {
353
+ display: "flex",
354
+ alignItems: "center",
355
+ justifyContent: "center",
356
+ gap: "0.5rem",
357
+ padding: "0.75rem 1rem",
358
+ border: "1px solid #d1d5db",
359
+ borderRadius: "0.375rem",
360
+ backgroundColor: "#24292e",
361
+ color: "#ffffff",
362
+ fontSize: "0.875rem",
363
+ fontWeight: "500",
364
+ cursor: isLoading ? "not-allowed" : "pointer",
365
+ opacity: isLoading ? 0.6 : 1,
366
+ transition: "all 0.2s"
367
+ },
368
+ onMouseOver: (e) => {
369
+ if (!isLoading) {
370
+ e.currentTarget.style.backgroundColor = "#1b1f23";
371
+ }
372
+ },
373
+ onMouseOut: (e) => {
374
+ e.currentTarget.style.backgroundColor = "#24292e";
375
+ },
376
+ children: [
377
+ isProviderLoading("github") ? /* @__PURE__ */ jsx2(
378
+ "div",
379
+ {
380
+ style: {
381
+ width: "1rem",
382
+ height: "1rem",
383
+ border: "2px solid #ffffff40",
384
+ borderTopColor: "#ffffff",
385
+ borderRadius: "50%",
386
+ animation: "spin 1s linear infinite"
387
+ }
388
+ }
389
+ ) : /* @__PURE__ */ jsx2("svg", { width: "18", height: "18", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ jsx2("path", { d: "M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" }) }),
390
+ "Continue with GitHub"
391
+ ]
392
+ }
393
+ )
394
+ ] }),
395
+ /* @__PURE__ */ jsx2(
396
+ "p",
397
+ {
398
+ style: {
399
+ marginTop: "1.5rem",
400
+ fontSize: "0.75rem",
401
+ color: appearance?.theme === "dark" ? "#9ca3af" : "#6b7280",
402
+ textAlign: "center"
403
+ },
404
+ children: "By continuing, you agree to our Terms of Service and Privacy Policy"
405
+ }
406
+ )
407
+ ]
408
+ }
409
+ ),
410
+ /* @__PURE__ */ jsx2("style", { children: `
411
+ @keyframes spin {
412
+ to { transform: rotate(360deg); }
413
+ }
414
+ ` })
415
+ ] });
416
+ }
417
+ export {
418
+ AuthTaraAuth,
419
+ AuthTaraProvider,
420
+ useAuth
421
+ };
package/package.json CHANGED
@@ -1,72 +1,91 @@
1
- {
2
- "name": "authtara-sdk",
3
- "version": "1.0.0",
4
- "description": "SDK Client untuk integrasi dengan DigitalSolution Platform - SSO, Billing, dan Metering",
5
- "main": "./dist/index.js",
6
- "module": "./dist/index.mjs",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.mjs",
12
- "require": "./dist/index.js"
13
- }
14
- },
15
- "files": [
16
- "dist",
17
- "README.md",
18
- "LICENSE"
19
- ],
20
- "scripts": {
21
- "build": "tsup src/index.ts --format cjs,esm --dts --clean",
22
- "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
23
- "test": "vitest",
24
- "test:run": "vitest run",
25
- "test:coverage": "vitest run --coverage",
26
- "typecheck": "tsc --noEmit",
27
- "prepublishOnly": "bun run build && bun test:run",
28
- "release": "bun run build && bun test:run && npm version patch",
29
- "release:minor": "bun run build && bun test:run && npm version minor",
30
- "release:major": "bun run build && bun test:run && npm version major"
31
- },
32
- "keywords": [
33
- "digitalsolution",
34
- "authtara",
35
- "sdk",
36
- "sso",
37
- "authentication",
38
- "billing",
39
- "metering",
40
- "oauth",
41
- "jwt",
42
- "saas"
43
- ],
44
- "author": "DigitalSolution Team",
45
- "license": "MIT",
46
- "repository": {
47
- "type": "git",
48
- "url": "https://github.com/digitalsolution/authtara-sdk.git"
49
- },
50
- "bugs": {
51
- "url": "https://github.com/digitalsolution/authtara-sdk/issues",
52
- "email": "support@digitalsolution.com"
53
- },
54
- "homepage": "https://docs.digitalsolution.com/sdk",
55
- "dependencies": {
56
- "jose": "^6.0.11"
57
- },
58
- "devDependencies": {
59
- "@types/node": "^24.0.0",
60
- "@vitest/coverage-v8": "^4.0.15",
61
- "tsup": "^8.5.0",
62
- "typescript": "^5.9.3",
63
- "vitest": "^4.0.15"
64
- },
65
- "engines": {
66
- "node": ">=18.0.0"
67
- },
68
- "publishConfig": {
69
- "access": "public",
70
- "registry": "https://registry.npmjs.org/"
71
- }
1
+ {
2
+ "name": "authtara-sdk",
3
+ "version": "1.1.1",
4
+ "description": "SDK Client untuk integrasi dengan DigitalSolution Platform - SSO, Billing, dan Metering",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ },
14
+ "./react": {
15
+ "types": "./dist/react.d.ts",
16
+ "import": "./dist/react.mjs",
17
+ "require": "./dist/react.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsup src/index.ts src/react.ts --format cjs,esm --dts --clean",
27
+ "dev": "tsup src/index.ts src/react.ts --format cjs,esm --dts --watch",
28
+ "test": "vitest",
29
+ "test:run": "vitest run",
30
+ "test:coverage": "vitest run --coverage",
31
+ "typecheck": "tsc --noEmit",
32
+ "prepublishOnly": "bun run build && bun test:run",
33
+ "release": "bun run build && bun test:run && npm version patch",
34
+ "release:minor": "bun run build && bun test:run && npm version minor",
35
+ "release:major": "bun run build && bun test:run && npm version major"
36
+ },
37
+ "keywords": [
38
+ "digitalsolution",
39
+ "authtara",
40
+ "sdk",
41
+ "sso",
42
+ "authentication",
43
+ "billing",
44
+ "metering",
45
+ "oauth",
46
+ "jwt",
47
+ "saas"
48
+ ],
49
+ "author": "DigitalSolution Team",
50
+ "license": "MIT",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/digitalsolution/authtara-sdk.git"
54
+ },
55
+ "bugs": {
56
+ "url": "https://github.com/digitalsolution/authtara-sdk/issues",
57
+ "email": "support@digitalsolution.com"
58
+ },
59
+ "homepage": "https://docs.digitalsolution.com/sdk",
60
+ "dependencies": {
61
+ "jose": "^6.0.11"
62
+ },
63
+ "peerDependencies": {
64
+ "react": "^18.0.0 || ^19.0.0",
65
+ "react-dom": "^18.0.0 || ^19.0.0"
66
+ },
67
+ "peerDependenciesMeta": {
68
+ "react": {
69
+ "optional": true
70
+ },
71
+ "react-dom": {
72
+ "optional": true
73
+ }
74
+ },
75
+ "devDependencies": {
76
+ "@types/node": "^24.0.0",
77
+ "@types/react": "^19.2.8",
78
+ "@types/react-dom": "^19.2.3",
79
+ "@vitest/coverage-v8": "^4.0.15",
80
+ "tsup": "^8.5.0",
81
+ "typescript": "^5.9.3",
82
+ "vitest": "^4.0.15"
83
+ },
84
+ "engines": {
85
+ "node": ">=18.0.0"
86
+ },
87
+ "publishConfig": {
88
+ "access": "public",
89
+ "registry": "https://registry.npmjs.org/"
90
+ }
72
91
  }