@sanvika/auth 2.8.0 → 2.9.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.
Files changed (2) hide show
  1. package/dist/index.js +188 -24
  2. package/package.json +7 -6
package/dist/index.js CHANGED
@@ -17,10 +17,9 @@ var STORAGE_KEYS = {
17
17
  };
18
18
  var DEFAULT_AVATAR_SVG = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40'%3E%3Ccircle cx='20' cy='20' r='20' fill='%23e5e7eb'/%3E%3Ccircle cx='20' cy='15' r='7' fill='%23adb5bd'/%3E%3Cellipse cx='20' cy='35' rx='12' ry='8' fill='%23adb5bd'/%3E%3C/svg%3E`;
19
19
 
20
- // SanvikaAuthProvider.jsx
21
- import { jsx } from "react/jsx-runtime";
22
- var S_AUTH_URL = "https://auth.sanvikaproduction.com";
23
- var SanvikaAuthContext = createContext(null);
20
+ // authFlow.js
21
+ var DEFAULT_AUTH_URL = "https://auth.sanvikaproduction.com";
22
+ var DEVICE_ID_STORAGE_KEY = "sanvika_deviceId";
24
23
  function randomDeviceId() {
25
24
  try {
26
25
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
@@ -30,6 +29,121 @@ function randomDeviceId() {
30
29
  }
31
30
  return `device-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
32
31
  }
32
+ function getOrCreateWebDeviceId() {
33
+ var _a, _b;
34
+ if (typeof window === "undefined") return randomDeviceId();
35
+ let deviceId = localStorage.getItem(DEVICE_ID_STORAGE_KEY);
36
+ if (!deviceId) {
37
+ const raw = `${navigator.userAgent}|${((_a = window.screen) == null ? void 0 : _a.width) ?? 0}x${((_b = window.screen) == null ? void 0 : _b.height) ?? 0}|${navigator.language}`;
38
+ let hash = 0;
39
+ for (let i = 0; i < raw.length; i++) {
40
+ hash = Math.imul(31, hash) + raw.charCodeAt(i) | 0;
41
+ }
42
+ deviceId = `d_${Math.abs(hash).toString(36)}_${Date.now().toString(36)}`;
43
+ localStorage.setItem(DEVICE_ID_STORAGE_KEY, deviceId);
44
+ }
45
+ return deviceId;
46
+ }
47
+ async function checkMobile({
48
+ authBaseUrl = DEFAULT_AUTH_URL,
49
+ mobile,
50
+ deviceId,
51
+ clientId,
52
+ callbackUrl
53
+ }) {
54
+ const res = await fetch(`${authBaseUrl}/api/auth/check-mobile`, {
55
+ method: "POST",
56
+ headers: { "Content-Type": "application/json" },
57
+ body: JSON.stringify({
58
+ mobile: String(mobile).trim(),
59
+ deviceId,
60
+ clientId,
61
+ ...callbackUrl ? { callbackUrl } : {}
62
+ })
63
+ });
64
+ const data = await res.json();
65
+ if (!data.success) {
66
+ const err = new Error(data.message || data.error || "Check mobile failed");
67
+ err.code = data.error;
68
+ throw err;
69
+ }
70
+ return data;
71
+ }
72
+ async function postLogin({
73
+ authBaseUrl = DEFAULT_AUTH_URL,
74
+ mobile,
75
+ password,
76
+ deviceId,
77
+ userAgent,
78
+ clientId,
79
+ deviceName
80
+ }) {
81
+ const body = {
82
+ mobile: String(mobile).trim(),
83
+ deviceId,
84
+ clientId,
85
+ userAgent: userAgent || deviceName || "Sanvika App"
86
+ };
87
+ if (password) {
88
+ body.password = password;
89
+ }
90
+ const res = await fetch(`${authBaseUrl}/api/auth/login`, {
91
+ method: "POST",
92
+ headers: { "Content-Type": "application/json" },
93
+ body: JSON.stringify(body)
94
+ });
95
+ const data = await res.json();
96
+ if (!data.success) {
97
+ const err = new Error(data.error || data.message || "Login failed");
98
+ err.code = data.error;
99
+ throw err;
100
+ }
101
+ return data;
102
+ }
103
+ async function deviceAwareLogin({
104
+ authBaseUrl = DEFAULT_AUTH_URL,
105
+ mobile,
106
+ password,
107
+ deviceId,
108
+ userAgent,
109
+ deviceName,
110
+ clientId,
111
+ callbackUrl,
112
+ resolveDeviceId
113
+ }) {
114
+ const resolvedDeviceId = deviceId || (typeof resolveDeviceId === "function" ? await resolveDeviceId() : getOrCreateWebDeviceId());
115
+ const check = await checkMobile({
116
+ authBaseUrl,
117
+ mobile,
118
+ deviceId: resolvedDeviceId,
119
+ clientId,
120
+ callbackUrl
121
+ });
122
+ if (!check.exists) {
123
+ const err = new Error("Mobile number not registered.");
124
+ err.code = "USER_NOT_REGISTERED";
125
+ throw err;
126
+ }
127
+ if (!check.isKnownDevice && !password) {
128
+ const err = new Error("Password is required for this device.");
129
+ err.code = "MISSING_PASSWORD";
130
+ err.requiresPassword = true;
131
+ throw err;
132
+ }
133
+ return postLogin({
134
+ authBaseUrl,
135
+ mobile,
136
+ password: check.isKnownDevice ? void 0 : password,
137
+ deviceId: resolvedDeviceId,
138
+ userAgent,
139
+ deviceName,
140
+ clientId
141
+ });
142
+ }
143
+
144
+ // SanvikaAuthProvider.jsx
145
+ import { jsx } from "react/jsx-runtime";
146
+ var SanvikaAuthContext = createContext(null);
33
147
  function createDefaultWebPersistence() {
34
148
  return {
35
149
  getItem: async (key) => typeof localStorage !== "undefined" ? localStorage.getItem(key) : null,
@@ -49,9 +163,13 @@ function SanvikaAuthProvider({
49
163
  children,
50
164
  clientId,
51
165
  redirectUri,
166
+ /** RN embedded LoginScreen: skip callbackUrl on check-mobile/login APIs (OAuth URI still used for /authorize links). */
167
+ embeddedLogin = false,
52
168
  dashboardPath,
169
+ authBaseUrl = DEFAULT_AUTH_URL,
53
170
  persistence: persistenceProp
54
171
  }) {
172
+ const apiCallbackUrl = embeddedLogin ? void 0 : redirectUri;
55
173
  const persistence = useMemo(() => {
56
174
  return persistenceProp || createDefaultWebPersistence();
57
175
  }, [persistenceProp]);
@@ -83,25 +201,62 @@ function SanvikaAuthProvider({
83
201
  cancelled = true;
84
202
  };
85
203
  }, [persistence]);
86
- const login = async ({ mobile, password, deviceId, deviceName }) => {
87
- const response = await fetch(`${S_AUTH_URL}/api/auth/login`, {
88
- method: "POST",
89
- headers: { "Content-Type": "application/json" },
90
- body: JSON.stringify({
204
+ const persistSession = useCallback(
205
+ async (token, userData) => {
206
+ await persistence.setItem(STORAGE_KEYS.ACCESS_TOKEN, token);
207
+ await persistence.setItem(STORAGE_KEYS.USER, JSON.stringify(userData));
208
+ setToken(token);
209
+ setUser(userData);
210
+ },
211
+ [persistence]
212
+ );
213
+ const checkMobile2 = useCallback(
214
+ async ({ mobile, deviceId }) => {
215
+ const resolvedDeviceId = deviceId || getOrCreateWebDeviceId();
216
+ return checkMobile({
217
+ authBaseUrl,
218
+ mobile,
219
+ deviceId: resolvedDeviceId,
220
+ clientId,
221
+ callbackUrl: apiCallbackUrl
222
+ });
223
+ },
224
+ [authBaseUrl, clientId, apiCallbackUrl]
225
+ );
226
+ const login = async ({
227
+ mobile,
228
+ password,
229
+ deviceId,
230
+ deviceName,
231
+ userAgent,
232
+ skipDeviceCheck = false
233
+ }) => {
234
+ const resolveDeviceId = async () => deviceId || getOrCreateWebDeviceId();
235
+ let data;
236
+ if (skipDeviceCheck) {
237
+ data = await postLogin({
238
+ authBaseUrl,
91
239
  mobile,
92
240
  password,
93
241
  deviceId: deviceId || randomDeviceId(),
94
- deviceName: deviceName || "Browser"
95
- })
96
- });
97
- const data = await response.json();
98
- if (!data.success) {
99
- throw new Error(data.error || data.message || "Login failed");
242
+ userAgent: userAgent || deviceName,
243
+ clientId,
244
+ deviceName
245
+ });
246
+ } else {
247
+ data = await deviceAwareLogin({
248
+ authBaseUrl,
249
+ mobile,
250
+ password,
251
+ deviceId,
252
+ userAgent: userAgent || (typeof navigator !== "undefined" ? navigator.userAgent : void 0),
253
+ deviceName,
254
+ clientId,
255
+ callbackUrl: apiCallbackUrl,
256
+ resolveDeviceId
257
+ });
100
258
  }
101
- await persistence.setItem(STORAGE_KEYS.ACCESS_TOKEN, data.accessToken);
102
- await persistence.setItem(STORAGE_KEYS.USER, JSON.stringify(data.user));
103
- setToken(data.accessToken);
104
- setUser(data.user);
259
+ await persistSession(data.accessToken, data.user);
105
260
  return data;
106
261
  };
107
262
  const setAuth = useCallback(
@@ -115,7 +270,7 @@ function SanvikaAuthProvider({
115
270
  );
116
271
  const logout = async () => {
117
272
  try {
118
- await fetch(`${S_AUTH_URL}/api/auth/logout`, {
273
+ await fetch(`${authBaseUrl}/api/auth/logout`, {
119
274
  method: "POST",
120
275
  headers: {
121
276
  "Content-Type": "application/json",
@@ -153,12 +308,14 @@ function SanvikaAuthProvider({
153
308
  isAuthenticated: !!user,
154
309
  isLoggedIn: !!user,
155
310
  login,
311
+ checkMobile: checkMobile2,
156
312
  logout,
157
313
  setAuth,
158
314
  authFetch,
159
315
  clientId,
160
316
  redirectUri,
161
- dashboardPath
317
+ dashboardPath,
318
+ authBaseUrl
162
319
  };
163
320
  return /* @__PURE__ */ jsx(SanvikaAuthContext.Provider, { value, children });
164
321
  }
@@ -196,7 +353,7 @@ styleInject("@keyframes snvk-shimmer {\n 0% {\n background-position: 200% 0;
196
353
 
197
354
  // SanvikaAccountButton.jsx
198
355
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
199
- var S_AUTH_URL2 = "https://auth.sanvikaproduction.com";
356
+ var S_AUTH_URL = "https://auth.sanvikaproduction.com";
200
357
  var SanvikaAccountButtonErrorBoundary = class extends Component {
201
358
  constructor(props) {
202
359
  super(props);
@@ -329,7 +486,7 @@ function SanvikaAccountButtonContent({
329
486
  if (!isAuthenticated || loading) {
330
487
  const { clientId } = auth;
331
488
  const redirectUri = auth.redirectUri || (typeof window !== "undefined" && window.location ? window.location.origin + "/auth/callback" : "");
332
- const authorizeUrl = clientId && redirectUri ? `${S_AUTH_URL2}/authorize?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUri)}` : `${S_AUTH_URL2}/authorize`;
489
+ const authorizeUrl = clientId && redirectUri ? `${S_AUTH_URL}/authorize?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUri)}` : `${S_AUTH_URL}/authorize`;
333
490
  return /* @__PURE__ */ jsxs(
334
491
  "button",
335
492
  {
@@ -497,7 +654,7 @@ function friendlyError(code) {
497
654
  case "INVALID_PASSWORD":
498
655
  return "Incorrect password.";
499
656
  case "RATE_LIMIT_EXCEEDED":
500
- return "Too many attempts. Please try again in 15 minutes.";
657
+ return "Too many attempts. Please wait and try again.";
501
658
  case "SERVER_ERROR":
502
659
  return "Auth service error. Please try again.";
503
660
  default:
@@ -687,11 +844,18 @@ function SanvikaAdminLogin({
687
844
  ] }) });
688
845
  }
689
846
  export {
847
+ DEFAULT_AUTH_URL,
690
848
  DEFAULT_AVATAR_SVG,
849
+ DEVICE_ID_STORAGE_KEY,
691
850
  STORAGE_KEYS,
692
851
  SanvikaAccountButton,
693
852
  SanvikaAdminLogin,
694
853
  SanvikaAuthContext,
695
854
  SanvikaAuthProvider,
855
+ checkMobile,
856
+ deviceAwareLogin,
857
+ getOrCreateWebDeviceId,
858
+ postLogin,
859
+ randomDeviceId,
696
860
  useSanvikaAuth
697
861
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanvika/auth",
3
- "version": "2.8.0",
3
+ "version": "2.9.1",
4
4
  "description": "Sanvika Auth SDK — React components/hooks + server-side token verification and user proxy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,6 +12,10 @@
12
12
  "files": [
13
13
  "dist"
14
14
  ],
15
+ "scripts": {
16
+ "build": "tsup",
17
+ "prepublishOnly": "npm run build"
18
+ },
15
19
  "publishConfig": {
16
20
  "access": "public"
17
21
  },
@@ -35,8 +39,5 @@
35
39
  "nextjs"
36
40
  ],
37
41
  "author": "sanvika",
38
- "license": "MIT",
39
- "scripts": {
40
- "build": "tsup"
41
- }
42
- }
42
+ "license": "MIT"
43
+ }