authtara-sdk 1.0.0 → 1.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.
@@ -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,458 @@
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
+ client_id: appId,
174
+ redirect_uri: callbackUrl,
175
+ state,
176
+ scope: "read write"
177
+ });
178
+ const authorizeUrl = `${apiUrl}/api/oauth/authorize?${params.toString()}`;
179
+ const popup = new OAuthPopup(apiUrl);
180
+ const { code: authCode, state: returnedState } = await popup.open(authorizeUrl);
181
+ const exchangeResponse = await fetch("/api/sso/callback", {
182
+ method: "POST",
183
+ headers: { "Content-Type": "application/json" },
184
+ credentials: "include",
185
+ body: JSON.stringify({
186
+ code: authCode,
187
+ state: returnedState
188
+ })
189
+ });
190
+ if (!exchangeResponse.ok) {
191
+ const error = await exchangeResponse.json();
192
+ throw new Error(error.message || "Token exchange failed");
193
+ }
194
+ await loadSession();
195
+ } catch (error) {
196
+ console.error("[AuthTara] Sign in failed:", error);
197
+ throw error;
198
+ } finally {
199
+ setIsLoading(false);
200
+ }
201
+ },
202
+ [appId, apiUrl, loadSession]
203
+ );
204
+ const signOut = React.useCallback(async () => {
205
+ try {
206
+ await fetch("/api/auth/logout", {
207
+ method: "POST",
208
+ credentials: "include"
209
+ });
210
+ setUser(null);
211
+ } catch (error) {
212
+ console.error("[AuthTara] Sign out failed:", error);
213
+ throw error;
214
+ }
215
+ }, []);
216
+ const refreshSession = React.useCallback(async () => {
217
+ await loadSession();
218
+ }, [loadSession]);
219
+ const value = {
220
+ isAuthenticated: !!user,
221
+ user,
222
+ isLoading,
223
+ signIn,
224
+ signOut,
225
+ refreshSession
226
+ };
227
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthTaraContext.Provider, { value, children });
228
+ }
229
+ function useAuth() {
230
+ const context = React.useContext(AuthTaraContext);
231
+ if (!context) {
232
+ throw new Error("useAuth must be used within AuthTaraProvider");
233
+ }
234
+ return context;
235
+ }
236
+
237
+ // src/components/AuthTaraAuth.tsx
238
+ var React2 = __toESM(require("react"));
239
+ var import_jsx_runtime2 = require("react/jsx-runtime");
240
+ function AuthTaraAuth({
241
+ // mode = 'modal', // Reserved for future use
242
+ appearance,
243
+ redirectUrl = "/dashboard",
244
+ onSuccess,
245
+ onError
246
+ }) {
247
+ const { signIn, isLoading, user } = useAuth();
248
+ const [error, setError] = React2.useState(null);
249
+ const [provider, setProvider] = React2.useState(null);
250
+ async function handleSignIn(selectedProvider) {
251
+ setError(null);
252
+ setProvider(selectedProvider);
253
+ try {
254
+ await signIn(selectedProvider, { redirectPath: redirectUrl });
255
+ onSuccess?.({ user });
256
+ if (redirectUrl) {
257
+ window.location.href = redirectUrl;
258
+ }
259
+ } catch (err) {
260
+ const message = err instanceof Error ? err.message : "Authentication failed";
261
+ setError(message);
262
+ onError?.(err);
263
+ } finally {
264
+ setProvider(null);
265
+ }
266
+ }
267
+ const isProviderLoading = (p) => isLoading && provider === p;
268
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "authtara-auth-container", style: { maxWidth: "400px", margin: "0 auto" }, children: [
269
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
270
+ "div",
271
+ {
272
+ className: "authtara-auth-card",
273
+ style: {
274
+ padding: "2rem",
275
+ border: "1px solid #e5e7eb",
276
+ borderRadius: "0.5rem",
277
+ backgroundColor: appearance?.theme === "dark" ? "#1f2937" : "#ffffff"
278
+ },
279
+ children: [
280
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
281
+ "h2",
282
+ {
283
+ style: {
284
+ fontSize: "1.5rem",
285
+ fontWeight: "bold",
286
+ marginBottom: "1.5rem",
287
+ color: appearance?.theme === "dark" ? "#ffffff" : "#111827"
288
+ },
289
+ children: "Sign in to continue"
290
+ }
291
+ ),
292
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
293
+ "div",
294
+ {
295
+ style: {
296
+ padding: "0.75rem",
297
+ marginBottom: "1rem",
298
+ backgroundColor: "#fee2e2",
299
+ color: "#991b1b",
300
+ borderRadius: "0.375rem",
301
+ fontSize: "0.875rem"
302
+ },
303
+ children: error
304
+ }
305
+ ),
306
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem" }, children: [
307
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
308
+ "button",
309
+ {
310
+ onClick: () => handleSignIn("google"),
311
+ disabled: isLoading,
312
+ style: {
313
+ display: "flex",
314
+ alignItems: "center",
315
+ justifyContent: "center",
316
+ gap: "0.5rem",
317
+ padding: "0.75rem 1rem",
318
+ border: "1px solid #d1d5db",
319
+ borderRadius: "0.375rem",
320
+ backgroundColor: "#ffffff",
321
+ color: "#374151",
322
+ fontSize: "0.875rem",
323
+ fontWeight: "500",
324
+ cursor: isLoading ? "not-allowed" : "pointer",
325
+ opacity: isLoading ? 0.6 : 1,
326
+ transition: "all 0.2s"
327
+ },
328
+ onMouseOver: (e) => {
329
+ if (!isLoading) {
330
+ e.currentTarget.style.backgroundColor = "#f9fafb";
331
+ }
332
+ },
333
+ onMouseOut: (e) => {
334
+ e.currentTarget.style.backgroundColor = "#ffffff";
335
+ },
336
+ children: [
337
+ isProviderLoading("google") ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
338
+ "div",
339
+ {
340
+ style: {
341
+ width: "1rem",
342
+ height: "1rem",
343
+ border: "2px solid #d1d5db",
344
+ borderTopColor: "#3b82f6",
345
+ borderRadius: "50%",
346
+ animation: "spin 1s linear infinite"
347
+ }
348
+ }
349
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 18 18", children: [
350
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
351
+ "path",
352
+ {
353
+ fill: "#4285F4",
354
+ 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"
355
+ }
356
+ ),
357
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
358
+ "path",
359
+ {
360
+ fill: "#34A853",
361
+ 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"
362
+ }
363
+ ),
364
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
365
+ "path",
366
+ {
367
+ fill: "#FBBC05",
368
+ 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"
369
+ }
370
+ ),
371
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
372
+ "path",
373
+ {
374
+ fill: "#EA4335",
375
+ 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"
376
+ }
377
+ )
378
+ ] }),
379
+ "Continue with Google"
380
+ ]
381
+ }
382
+ ),
383
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
384
+ "button",
385
+ {
386
+ onClick: () => handleSignIn("github"),
387
+ disabled: isLoading,
388
+ style: {
389
+ display: "flex",
390
+ alignItems: "center",
391
+ justifyContent: "center",
392
+ gap: "0.5rem",
393
+ padding: "0.75rem 1rem",
394
+ border: "1px solid #d1d5db",
395
+ borderRadius: "0.375rem",
396
+ backgroundColor: "#24292e",
397
+ color: "#ffffff",
398
+ fontSize: "0.875rem",
399
+ fontWeight: "500",
400
+ cursor: isLoading ? "not-allowed" : "pointer",
401
+ opacity: isLoading ? 0.6 : 1,
402
+ transition: "all 0.2s"
403
+ },
404
+ onMouseOver: (e) => {
405
+ if (!isLoading) {
406
+ e.currentTarget.style.backgroundColor = "#1b1f23";
407
+ }
408
+ },
409
+ onMouseOut: (e) => {
410
+ e.currentTarget.style.backgroundColor = "#24292e";
411
+ },
412
+ children: [
413
+ isProviderLoading("github") ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
414
+ "div",
415
+ {
416
+ style: {
417
+ width: "1rem",
418
+ height: "1rem",
419
+ border: "2px solid #ffffff40",
420
+ borderTopColor: "#ffffff",
421
+ borderRadius: "50%",
422
+ animation: "spin 1s linear infinite"
423
+ }
424
+ }
425
+ ) : /* @__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" }) }),
426
+ "Continue with GitHub"
427
+ ]
428
+ }
429
+ )
430
+ ] }),
431
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
432
+ "p",
433
+ {
434
+ style: {
435
+ marginTop: "1.5rem",
436
+ fontSize: "0.75rem",
437
+ color: appearance?.theme === "dark" ? "#9ca3af" : "#6b7280",
438
+ textAlign: "center"
439
+ },
440
+ children: "By continuing, you agree to our Terms of Service and Privacy Policy"
441
+ }
442
+ )
443
+ ]
444
+ }
445
+ ),
446
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `
447
+ @keyframes spin {
448
+ to { transform: rotate(360deg); }
449
+ }
450
+ ` })
451
+ ] });
452
+ }
453
+ // Annotate the CommonJS export names for ESM import in node:
454
+ 0 && (module.exports = {
455
+ AuthTaraAuth,
456
+ AuthTaraProvider,
457
+ useAuth
458
+ });
package/dist/react.mjs ADDED
@@ -0,0 +1,419 @@
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
+ client_id: appId,
136
+ redirect_uri: callbackUrl,
137
+ state,
138
+ scope: "read write"
139
+ });
140
+ const authorizeUrl = `${apiUrl}/api/oauth/authorize?${params.toString()}`;
141
+ const popup = new OAuthPopup(apiUrl);
142
+ const { code: authCode, state: returnedState } = await popup.open(authorizeUrl);
143
+ const exchangeResponse = await fetch("/api/sso/callback", {
144
+ method: "POST",
145
+ headers: { "Content-Type": "application/json" },
146
+ credentials: "include",
147
+ body: JSON.stringify({
148
+ code: authCode,
149
+ state: returnedState
150
+ })
151
+ });
152
+ if (!exchangeResponse.ok) {
153
+ const error = await exchangeResponse.json();
154
+ throw new Error(error.message || "Token exchange failed");
155
+ }
156
+ await loadSession();
157
+ } catch (error) {
158
+ console.error("[AuthTara] Sign in failed:", error);
159
+ throw error;
160
+ } finally {
161
+ setIsLoading(false);
162
+ }
163
+ },
164
+ [appId, apiUrl, loadSession]
165
+ );
166
+ const signOut = React.useCallback(async () => {
167
+ try {
168
+ await fetch("/api/auth/logout", {
169
+ method: "POST",
170
+ credentials: "include"
171
+ });
172
+ setUser(null);
173
+ } catch (error) {
174
+ console.error("[AuthTara] Sign out failed:", error);
175
+ throw error;
176
+ }
177
+ }, []);
178
+ const refreshSession = React.useCallback(async () => {
179
+ await loadSession();
180
+ }, [loadSession]);
181
+ const value = {
182
+ isAuthenticated: !!user,
183
+ user,
184
+ isLoading,
185
+ signIn,
186
+ signOut,
187
+ refreshSession
188
+ };
189
+ return /* @__PURE__ */ jsx(AuthTaraContext.Provider, { value, children });
190
+ }
191
+ function useAuth() {
192
+ const context = React.useContext(AuthTaraContext);
193
+ if (!context) {
194
+ throw new Error("useAuth must be used within AuthTaraProvider");
195
+ }
196
+ return context;
197
+ }
198
+
199
+ // src/components/AuthTaraAuth.tsx
200
+ import * as React2 from "react";
201
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
202
+ function AuthTaraAuth({
203
+ // mode = 'modal', // Reserved for future use
204
+ appearance,
205
+ redirectUrl = "/dashboard",
206
+ onSuccess,
207
+ onError
208
+ }) {
209
+ const { signIn, isLoading, user } = useAuth();
210
+ const [error, setError] = React2.useState(null);
211
+ const [provider, setProvider] = React2.useState(null);
212
+ async function handleSignIn(selectedProvider) {
213
+ setError(null);
214
+ setProvider(selectedProvider);
215
+ try {
216
+ await signIn(selectedProvider, { redirectPath: redirectUrl });
217
+ onSuccess?.({ user });
218
+ if (redirectUrl) {
219
+ window.location.href = redirectUrl;
220
+ }
221
+ } catch (err) {
222
+ const message = err instanceof Error ? err.message : "Authentication failed";
223
+ setError(message);
224
+ onError?.(err);
225
+ } finally {
226
+ setProvider(null);
227
+ }
228
+ }
229
+ const isProviderLoading = (p) => isLoading && provider === p;
230
+ return /* @__PURE__ */ jsxs("div", { className: "authtara-auth-container", style: { maxWidth: "400px", margin: "0 auto" }, children: [
231
+ /* @__PURE__ */ jsxs(
232
+ "div",
233
+ {
234
+ className: "authtara-auth-card",
235
+ style: {
236
+ padding: "2rem",
237
+ border: "1px solid #e5e7eb",
238
+ borderRadius: "0.5rem",
239
+ backgroundColor: appearance?.theme === "dark" ? "#1f2937" : "#ffffff"
240
+ },
241
+ children: [
242
+ /* @__PURE__ */ jsx2(
243
+ "h2",
244
+ {
245
+ style: {
246
+ fontSize: "1.5rem",
247
+ fontWeight: "bold",
248
+ marginBottom: "1.5rem",
249
+ color: appearance?.theme === "dark" ? "#ffffff" : "#111827"
250
+ },
251
+ children: "Sign in to continue"
252
+ }
253
+ ),
254
+ error && /* @__PURE__ */ jsx2(
255
+ "div",
256
+ {
257
+ style: {
258
+ padding: "0.75rem",
259
+ marginBottom: "1rem",
260
+ backgroundColor: "#fee2e2",
261
+ color: "#991b1b",
262
+ borderRadius: "0.375rem",
263
+ fontSize: "0.875rem"
264
+ },
265
+ children: error
266
+ }
267
+ ),
268
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem" }, children: [
269
+ /* @__PURE__ */ jsxs(
270
+ "button",
271
+ {
272
+ onClick: () => handleSignIn("google"),
273
+ disabled: isLoading,
274
+ style: {
275
+ display: "flex",
276
+ alignItems: "center",
277
+ justifyContent: "center",
278
+ gap: "0.5rem",
279
+ padding: "0.75rem 1rem",
280
+ border: "1px solid #d1d5db",
281
+ borderRadius: "0.375rem",
282
+ backgroundColor: "#ffffff",
283
+ color: "#374151",
284
+ fontSize: "0.875rem",
285
+ fontWeight: "500",
286
+ cursor: isLoading ? "not-allowed" : "pointer",
287
+ opacity: isLoading ? 0.6 : 1,
288
+ transition: "all 0.2s"
289
+ },
290
+ onMouseOver: (e) => {
291
+ if (!isLoading) {
292
+ e.currentTarget.style.backgroundColor = "#f9fafb";
293
+ }
294
+ },
295
+ onMouseOut: (e) => {
296
+ e.currentTarget.style.backgroundColor = "#ffffff";
297
+ },
298
+ children: [
299
+ isProviderLoading("google") ? /* @__PURE__ */ jsx2(
300
+ "div",
301
+ {
302
+ style: {
303
+ width: "1rem",
304
+ height: "1rem",
305
+ border: "2px solid #d1d5db",
306
+ borderTopColor: "#3b82f6",
307
+ borderRadius: "50%",
308
+ animation: "spin 1s linear infinite"
309
+ }
310
+ }
311
+ ) : /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 18 18", children: [
312
+ /* @__PURE__ */ jsx2(
313
+ "path",
314
+ {
315
+ fill: "#4285F4",
316
+ 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"
317
+ }
318
+ ),
319
+ /* @__PURE__ */ jsx2(
320
+ "path",
321
+ {
322
+ fill: "#34A853",
323
+ 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"
324
+ }
325
+ ),
326
+ /* @__PURE__ */ jsx2(
327
+ "path",
328
+ {
329
+ fill: "#FBBC05",
330
+ 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"
331
+ }
332
+ ),
333
+ /* @__PURE__ */ jsx2(
334
+ "path",
335
+ {
336
+ fill: "#EA4335",
337
+ 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"
338
+ }
339
+ )
340
+ ] }),
341
+ "Continue with Google"
342
+ ]
343
+ }
344
+ ),
345
+ /* @__PURE__ */ jsxs(
346
+ "button",
347
+ {
348
+ onClick: () => handleSignIn("github"),
349
+ disabled: isLoading,
350
+ style: {
351
+ display: "flex",
352
+ alignItems: "center",
353
+ justifyContent: "center",
354
+ gap: "0.5rem",
355
+ padding: "0.75rem 1rem",
356
+ border: "1px solid #d1d5db",
357
+ borderRadius: "0.375rem",
358
+ backgroundColor: "#24292e",
359
+ color: "#ffffff",
360
+ fontSize: "0.875rem",
361
+ fontWeight: "500",
362
+ cursor: isLoading ? "not-allowed" : "pointer",
363
+ opacity: isLoading ? 0.6 : 1,
364
+ transition: "all 0.2s"
365
+ },
366
+ onMouseOver: (e) => {
367
+ if (!isLoading) {
368
+ e.currentTarget.style.backgroundColor = "#1b1f23";
369
+ }
370
+ },
371
+ onMouseOut: (e) => {
372
+ e.currentTarget.style.backgroundColor = "#24292e";
373
+ },
374
+ children: [
375
+ isProviderLoading("github") ? /* @__PURE__ */ jsx2(
376
+ "div",
377
+ {
378
+ style: {
379
+ width: "1rem",
380
+ height: "1rem",
381
+ border: "2px solid #ffffff40",
382
+ borderTopColor: "#ffffff",
383
+ borderRadius: "50%",
384
+ animation: "spin 1s linear infinite"
385
+ }
386
+ }
387
+ ) : /* @__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" }) }),
388
+ "Continue with GitHub"
389
+ ]
390
+ }
391
+ )
392
+ ] }),
393
+ /* @__PURE__ */ jsx2(
394
+ "p",
395
+ {
396
+ style: {
397
+ marginTop: "1.5rem",
398
+ fontSize: "0.75rem",
399
+ color: appearance?.theme === "dark" ? "#9ca3af" : "#6b7280",
400
+ textAlign: "center"
401
+ },
402
+ children: "By continuing, you agree to our Terms of Service and Privacy Policy"
403
+ }
404
+ )
405
+ ]
406
+ }
407
+ ),
408
+ /* @__PURE__ */ jsx2("style", { children: `
409
+ @keyframes spin {
410
+ to { transform: rotate(360deg); }
411
+ }
412
+ ` })
413
+ ] });
414
+ }
415
+ export {
416
+ AuthTaraAuth,
417
+ AuthTaraProvider,
418
+ useAuth
419
+ };
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.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
+ "./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
  }