rystem.authentication.social.client 0.5.0 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -87,87 +87,111 @@ yarn add rystem.authentication.social.client
87
87
  pnpm add rystem.authentication.social.client
88
88
  ```
89
89
 
90
- ### 🚀 React Native Setup
90
+ ---
91
+
92
+ ## 📱 React Native Complete Guide
93
+
94
+ This library is **100% framework-agnostic** and works perfectly in **React Native**, **Expo**, and any mobile framework! However, setup differs from web because:
91
95
 
92
- The library works with **React Native** without any polyfills! You just need to provide custom implementations for:
96
+ 1. **No `window`, `document`, or `localStorage`** in React Native
97
+ 2. ❌ **Web SDK scripts** (Google Sign-In SDK, `@azure/msal-browser`) don't work in React Native
98
+ 3. ✅ **Native SDKs** must be used instead (`@react-native-google-signin/google-signin`, `@react-native-community/msal`)
93
99
 
94
- 1. **Storage Service** (use AsyncStorage or Secure Storage instead of localStorage)
95
- 2. **Routing Service** (use Linking API for deep links)
100
+ ### 🎯 Architecture Overview
96
101
 
97
- #### Example React Native Setup
102
+ | Component | Web | React Native |
103
+ |-----------|-----|--------------|
104
+ | **Core Services** (`SocialLoginManager`, `setupSocialLogin`) | ✅ Works | ✅ Works |
105
+ | **React Hooks** (`useSocialToken`, `useSocialUser`) | ✅ Works | ✅ Works |
106
+ | **UI Components** (`MicrosoftButton`, `GoogleButton`) | ✅ Works | ❌ **Web-only** |
107
+ | **Storage** | `LocalStorageService` | Custom `IStorageService` (AsyncStorage) |
108
+ | **Routing** | `WindowRoutingService` | Custom `IRoutingService` (Linking) |
109
+ | **Platform** | `BrowserPlatformService` | Custom `IPlatformService` (Dimensions) |
110
+
111
+ ### 📋 Setup Checklist
112
+
113
+ - [ ] 1. Install `@react-native-async-storage/async-storage`
114
+ - [ ] 2. Create `ReactNativeStorageService` implementing `IStorageService`
115
+ - [ ] 3. Create `ReactNativeRoutingService` implementing `IRoutingService`
116
+ - [ ] 4. Create `ReactNativePlatformService` implementing `IPlatformService`
117
+ - [ ] 5. Install native OAuth SDKs (`@react-native-community/msal`, `@react-native-google-signin/google-signin`)
118
+ - [ ] 6. Create custom login button components
119
+ - [ ] 7. Use `SocialLoginManager.Instance(null).updateToken()` after successful native login
120
+
121
+ ---
122
+
123
+ ### 🔧 Step 1-4: Implementing Platform Services
124
+
125
+ #### 💾 Storage Service (AsyncStorage)
126
+
127
+ ```bash
128
+ npm install @react-native-async-storage/async-storage
129
+ ```
98
130
 
99
131
  ```typescript
100
132
  // services/ReactNativeStorageService.ts
101
133
  import AsyncStorage from '@react-native-async-storage/async-storage';
102
- import { IStorageService } from 'rystem.authentication.social.react';
134
+ import { IStorageService } from 'rystem.authentication.social.client';
103
135
 
104
136
  export class ReactNativeStorageService implements IStorageService {
105
- async get(key: string): Promise<string | null> {
106
- try {
107
- return await AsyncStorage.getItem(key);
108
- } catch (error) {
109
- console.error('AsyncStorage get error:', error);
110
- return null;
111
- }
137
+ get(key: string): string | null {
138
+ // Note: AsyncStorage is async, but IStorageService expects sync
139
+ // Use synchronous approach or modify your usage to handle promises
140
+ let result: string | null = null;
141
+ AsyncStorage.getItem(key).then(value => result = value);
142
+ return result;
112
143
  }
113
144
 
114
- async set(key: string, value: string): Promise<void> {
115
- try {
116
- await AsyncStorage.setItem(key, value);
117
- } catch (error) {
118
- console.error('AsyncStorage set error:', error);
119
- }
145
+ set(key: string, value: string): void {
146
+ AsyncStorage.setItem(key, value).catch(error =>
147
+ console.error('AsyncStorage set error:', error)
148
+ );
120
149
  }
121
150
 
122
- async remove(key: string): Promise<void> {
123
- try {
124
- await AsyncStorage.removeItem(key);
125
- } catch (error) {
126
- console.error('AsyncStorage remove error:', error);
127
- }
151
+ remove(key: string): void {
152
+ AsyncStorage.removeItem(key).catch(error =>
153
+ console.error('AsyncStorage remove error:', error)
154
+ );
128
155
  }
129
156
 
130
- async has(key: string): Promise<boolean> {
131
- const value = await this.get(key);
132
- return value !== null;
157
+ has(key: string): boolean {
158
+ return this.get(key) !== null;
133
159
  }
134
160
 
135
- async clear(): Promise<void> {
136
- try {
137
- await AsyncStorage.clear();
138
- } catch (error) {
139
- console.error('AsyncStorage clear error:', error);
140
- }
161
+ clear(): void {
162
+ AsyncStorage.clear().catch(error =>
163
+ console.error('AsyncStorage clear error:', error)
164
+ );
141
165
  }
142
166
  }
167
+ ```
168
+
169
+ #### 🧭 Routing Service (Linking API)
143
170
 
171
+ ```typescript
144
172
  // services/ReactNativeRoutingService.ts
145
173
  import { Linking } from 'react-native';
146
- import { IRoutingService } from 'rystem.authentication.social.react';
174
+ import { IRoutingService } from 'rystem.authentication.social.client';
147
175
 
148
176
  export class ReactNativeRoutingService implements IRoutingService {
149
177
  private currentUrl: URL | null = null;
150
178
 
151
179
  constructor() {
152
- // Parse initial URL
153
180
  Linking.getInitialURL().then(url => {
154
181
  if (url) this.currentUrl = new URL(url);
155
182
  });
156
183
 
157
- // Listen for deep link events
158
184
  Linking.addEventListener('url', ({ url }) => {
159
185
  this.currentUrl = new URL(url);
160
186
  });
161
187
  }
162
188
 
163
189
  getSearchParam(key: string): string | null {
164
- if (!this.currentUrl) return null;
165
- return this.currentUrl.searchParams.get(key);
190
+ return this.currentUrl?.searchParams.get(key) ?? null;
166
191
  }
167
192
 
168
193
  getAllSearchParams(): URLSearchParams {
169
- if (!this.currentUrl) return new URLSearchParams();
170
- return this.currentUrl.searchParams;
194
+ return this.currentUrl?.searchParams ?? new URLSearchParams();
171
195
  }
172
196
 
173
197
  getCurrentPath(): string {
@@ -176,43 +200,97 @@ export class ReactNativeRoutingService implements IRoutingService {
176
200
  }
177
201
 
178
202
  navigateTo(url: string): void {
179
- // For OAuth URLs, open in browser
180
203
  Linking.openURL(url);
181
204
  }
182
205
 
183
206
  navigateReplace(path: string): void {
184
- // React Native navigation - implement with your router (React Navigation, Expo Router)
207
+ // Implement with React Navigation or Expo Router
185
208
  console.log('Navigate to:', path);
186
209
  }
187
210
 
188
211
  openPopup(url: string, name: string, features: string): Window | null {
189
- // React Native doesn't support popups - use in-app browser
190
212
  Linking.openURL(url);
191
213
  return null;
192
214
  }
193
215
  }
216
+ ```
194
217
 
195
- // App setup
196
- import { setupSocialLogin, PlatformType, LoginMode } from 'rystem.authentication.social.react';
218
+ #### 📐 Platform Service (Dimensions & Events)
219
+
220
+ ```typescript
221
+ // services/ReactNativePlatformService.ts
222
+ import { Dimensions, EventEmitter } from 'react-native';
223
+ import { IPlatformService } from 'rystem.authentication.social.client';
224
+
225
+ export class ReactNativePlatformService implements IPlatformService {
226
+ private eventEmitter = new EventEmitter();
227
+
228
+ addStorageListener(callback: () => void): void {
229
+ this.eventEmitter.addListener('storage', callback);
230
+ }
231
+
232
+ removeStorageListener(callback: () => void): void {
233
+ this.eventEmitter.removeListener('storage', callback);
234
+ }
235
+
236
+ getScreenWidth(): number {
237
+ return Dimensions.get('window').width;
238
+ }
239
+
240
+ getScreenHeight(): number {
241
+ return Dimensions.get('window').height;
242
+ }
243
+
244
+ loadScript(id: string, src: string, onLoad: () => void): HTMLScriptElement | null {
245
+ console.warn('Script loading not supported in React Native');
246
+ onLoad();
247
+ return null;
248
+ }
249
+
250
+ scriptExists(id: string): boolean {
251
+ return false;
252
+ }
253
+
254
+ removeScript(scriptElement: HTMLScriptElement): void {
255
+ // No-op
256
+ }
257
+
258
+ isPopup(): boolean {
259
+ return false;
260
+ }
261
+
262
+ closeWindow(): void {
263
+ // No-op
264
+ }
265
+ }
266
+ ```
267
+
268
+ #### ⚙️ Complete Setup
269
+
270
+ ```typescript
271
+ // App.tsx
272
+ import { setupSocialLogin, PlatformType, LoginMode } from 'rystem.authentication.social.client';
197
273
  import { ReactNativeStorageService } from './services/ReactNativeStorageService';
198
274
  import { ReactNativeRoutingService } from './services/ReactNativeRoutingService';
275
+ import { ReactNativePlatformService } from './services/ReactNativePlatformService';
276
+ import { Platform, Alert } from 'react-native';
199
277
 
200
278
  setupSocialLogin(x => {
201
279
  x.apiUri = "https://api.yourdomain.com";
202
280
 
203
- // ✅ Provide React Native implementations
281
+ // ✅ Inject React Native implementations
204
282
  x.storageService = new ReactNativeStorageService();
205
283
  x.routingService = new ReactNativeRoutingService();
284
+ x.platformService = new ReactNativePlatformService();
206
285
 
207
- // Platform configuration
208
286
  x.platform = {
209
- type: PlatformType.Auto, // Auto-detects iOS/Android
287
+ type: PlatformType.Auto,
210
288
  redirectPath: Platform.select({
211
289
  ios: 'myapp://oauth/callback',
212
290
  android: 'myapp://oauth/callback',
213
291
  default: '/account/login'
214
292
  }),
215
- loginMode: LoginMode.Redirect // Always redirect for mobile
293
+ loginMode: LoginMode.Redirect
216
294
  };
217
295
 
218
296
  x.microsoft.clientId = "your-client-id";
@@ -224,10 +302,190 @@ setupSocialLogin(x => {
224
302
  });
225
303
  ```
226
304
 
227
- **Why No Polyfills?**
228
- - ✅ The library now checks `typeof window !== 'undefined'` before accessing browser APIs
229
- - You inject platform-specific implementations via `IStorageService` and `IRoutingService`
230
- - ✅ No need for hacky polyfills or modifying `global` object
305
+ ---
306
+
307
+ ### 🎨 Step 5-6: Native Login Buttons
308
+
309
+ #### 📘 Microsoft Login (Native MSAL)
310
+
311
+ ```bash
312
+ npm install @react-native-community/msal
313
+ ```
314
+
315
+ ```typescript
316
+ // components/MicrosoftLoginButton.tsx
317
+ import React from 'react';
318
+ import { Pressable, Text, StyleSheet, Alert } from 'react-native';
319
+ import { MSALConfiguration, MSALResult, PublicClientApplication } from '@react-native-community/msal';
320
+ import { SocialLoginManager, ProviderType } from 'rystem.authentication.social.client';
321
+
322
+ const MSAL_CONFIG: MSALConfiguration = {
323
+ auth: {
324
+ clientId: 'your-microsoft-client-id',
325
+ authority: 'https://login.microsoftonline.com/consumers',
326
+ },
327
+ };
328
+
329
+ export const MicrosoftLoginButton = () => {
330
+ const handleLogin = async () => {
331
+ try {
332
+ const pca = new PublicClientApplication(MSAL_CONFIG);
333
+ await pca.init();
334
+
335
+ const result: MSALResult = await pca.acquireToken({
336
+ scopes: ['openid', 'profile', 'email', 'User.Read'],
337
+ });
338
+
339
+ // ✅ Send token to backend
340
+ await SocialLoginManager.Instance(null).updateToken(
341
+ ProviderType.Microsoft,
342
+ result.idToken
343
+ );
344
+
345
+ console.log('✅ Microsoft login successful');
346
+ } catch (error: any) {
347
+ console.error('❌ Microsoft login failed:', error);
348
+ Alert.alert('Login Failed', error.message || 'Unknown error');
349
+ }
350
+ };
351
+
352
+ return (
353
+ <Pressable style={[styles.button, { backgroundColor: '#2f2f2f' }]} onPress={handleLogin}>
354
+ <Text style={styles.text}>🪟 Sign in with Microsoft</Text>
355
+ </Pressable>
356
+ );
357
+ };
358
+
359
+ const styles = StyleSheet.create({
360
+ button: { padding: 12, borderRadius: 8, alignItems: 'center', marginVertical: 8 },
361
+ text: { color: '#fff', fontSize: 16, fontWeight: '600' },
362
+ });
363
+ ```
364
+
365
+ #### 🔴 Google Login (Native SDK)
366
+
367
+ ```bash
368
+ npm install @react-native-google-signin/google-signin
369
+ ```
370
+
371
+ ```typescript
372
+ // components/GoogleLoginButton.tsx
373
+ import React from 'react';
374
+ import { Pressable, Text, StyleSheet, Alert } from 'react-native';
375
+ import { GoogleSignin, statusCodes } from '@react-native-google-signin/google-signin';
376
+ import { SocialLoginManager, ProviderType } from 'rystem.authentication.social.client';
377
+
378
+ // Configure once at app startup
379
+ GoogleSignin.configure({
380
+ webClientId: 'your-google-web-client-id.apps.googleusercontent.com',
381
+ offlineAccess: true,
382
+ forceCodeForRefreshToken: true,
383
+ iosClientId: 'your-ios-client-id.apps.googleusercontent.com',
384
+ });
385
+
386
+ export const GoogleLoginButton = () => {
387
+ const handleLogin = async () => {
388
+ try {
389
+ await GoogleSignin.hasPlayServices();
390
+ const userInfo = await GoogleSignin.signIn();
391
+
392
+ if (!userInfo.idToken) throw new Error('No ID token received');
393
+
394
+ // ✅ Send token to backend
395
+ await SocialLoginManager.Instance(null).updateToken(
396
+ ProviderType.Google,
397
+ userInfo.idToken
398
+ );
399
+
400
+ console.log('✅ Google login successful');
401
+ } catch (error: any) {
402
+ if (error.code === statusCodes.SIGN_IN_CANCELLED) {
403
+ console.log('User cancelled login');
404
+ } else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
405
+ Alert.alert('Error', 'Play Services not available');
406
+ } else {
407
+ console.error('❌ Google login failed:', error);
408
+ Alert.alert('Login Failed', error.message);
409
+ }
410
+ }
411
+ };
412
+
413
+ return (
414
+ <Pressable style={[styles.button, { backgroundColor: '#4285F4' }]} onPress={handleLogin}>
415
+ <Text style={styles.text}>🔵 Sign in with Google</Text>
416
+ </Pressable>
417
+ );
418
+ };
419
+
420
+ const styles = StyleSheet.create({
421
+ button: { padding: 12, borderRadius: 8, alignItems: 'center', marginVertical: 8 },
422
+ text: { color: '#fff', fontSize: 16, fontWeight: '600' },
423
+ });
424
+ ```
425
+
426
+ ---
427
+
428
+ ### 📱 Step 7: Usage in Your App
429
+
430
+ ```typescript
431
+ // screens/LoginScreen.tsx
432
+ import React from 'react';
433
+ import { View, Text, StyleSheet } from 'react-native';
434
+ import { MicrosoftLoginButton } from '../components/MicrosoftLoginButton';
435
+ import { GoogleLoginButton } from '../components/GoogleLoginButton';
436
+ import { useSocialToken, useSocialUser } from 'rystem.authentication.social.client';
437
+
438
+ export const LoginScreen = () => {
439
+ const token = useSocialToken();
440
+ const user = useSocialUser();
441
+
442
+ if (!token.isExpired) {
443
+ return (
444
+ <View style={styles.container}>
445
+ <Text style={styles.welcome}>Welcome, {user.username}!</Text>
446
+ <Text>Provider: {user.provider}</Text>
447
+ <Text>Email: {user.email}</Text>
448
+ </View>
449
+ );
450
+ }
451
+
452
+ return (
453
+ <View style={styles.container}>
454
+ <Text style={styles.title}>Choose Login Method</Text>
455
+ <MicrosoftLoginButton />
456
+ <GoogleLoginButton />
457
+ </View>
458
+ );
459
+ };
460
+
461
+ const styles = StyleSheet.create({
462
+ container: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 },
463
+ title: { fontSize: 20, fontWeight: 'bold', marginBottom: 20 },
464
+ welcome: { fontSize: 24, fontWeight: 'bold', marginBottom: 10 },
465
+ });
466
+ ```
467
+
468
+ ---
469
+
470
+ ### ✅ React Native Summary
471
+
472
+ **What You Need:**
473
+ 1. Implement 3 services: `IStorageService`, `IRoutingService`, `IPlatformService`
474
+ 2. Install native OAuth SDKs (not web SDKs!)
475
+ 3. Create custom UI components
476
+ 4. Call `SocialLoginManager.Instance(null).updateToken()` after native login success
477
+
478
+ **What Works Automatically:**
479
+ - ✅ `useSocialToken()` / `useSocialUser()` hooks
480
+ - ✅ Token storage and refresh logic
481
+ - ✅ Backend communication
482
+
483
+ **Why No `window` Access Issues:**
484
+ - ✅ Library uses service abstractions (`IStorageService`, `IRoutingService`, `IPlatformService`)
485
+ - ✅ You inject platform-specific implementations
486
+ - ✅ No polyfills needed!
487
+
488
+ ---
231
489
 
232
490
  ### ⚠️ Important for React Router / Next.js Users
233
491
 
@@ -240,36 +498,34 @@ If you're using **React Router** or **Next.js App Router**, OAuth callbacks and
240
498
  - Next.js App Router (v13+)
241
499
  - Unit Testing
242
500
 
243
- ## 🚀 Quick Start
501
+ ## 🚀 Quick Start (Web Applications)
244
502
 
245
503
  ### 1. Setup Configuration (main.tsx)
246
504
 
247
505
  ```typescript
248
- import { SocialLoginWrapper, setupSocialLogin } from 'rystem.authentication.social.react';
506
+ import { SocialLoginWrapper, setupSocialLogin } from 'rystem.authentication.social.client';
249
507
  import App from './App';
250
508
 
251
509
  setupSocialLogin(x => {
510
+ // 🚀 ONE-LINE SETUP for web applications
511
+ // This configures localStorage, window routing, and browser platform APIs
512
+ x.useBrowserDefaults();
513
+
252
514
  // API server URL
253
515
  x.apiUri = "https://localhost:7017";
254
-
255
- // Optional: Custom redirect path (default: "/account/login")
256
- x.platform = {
257
- redirectPath: "/account/login" // Auto-detects domain
258
- };
259
-
516
+
260
517
  // Configure OAuth providers (only clientId needed for client-side)
261
518
  x.microsoft.clientId = "0b90db07-be9f-4b29-b673-9e8ee9265927";
262
519
  x.google.clientId = "23769141170-lfs24avv5qrj00m4cbmrm202c0fc6gcg.apps.googleusercontent.com";
263
520
  x.facebook.clientId = "345885718092912";
264
521
  x.github.clientId = "97154d062f2bb5d28620";
265
- x.amazon.clientId = "amzn1.application-oa2-client.dffbc466d62c44e49d71ad32f4aecb62";
266
-
522
+
267
523
  // Error handling callback
268
524
  x.onLoginFailure = (error) => {
269
525
  console.error(`Login failed: ${error.message} (Code: ${error.code})`);
270
526
  alert(`Authentication error: ${error.message}`);
271
527
  };
272
-
528
+
273
529
  // Automatic token refresh when expired
274
530
  x.automaticRefresh = true;
275
531
  });
@@ -285,10 +541,49 @@ function Root() {
285
541
  export default Root;
286
542
  ```
287
543
 
544
+ #### 🤔 What does `useBrowserDefaults()` do?
545
+
546
+ This helper method **automatically configures three essential services** for web browsers:
547
+
548
+ 1. **`storageService`**: `LocalStorageService` → Uses browser `localStorage` to persist tokens, PKCE verifiers, and user data
549
+ 2. **`routingService`**: `WindowRoutingService` → Uses `window.location` to read OAuth callback parameters and `window.history` for navigation
550
+ 3. **`platformService`**: `BrowserPlatformService` → Uses `window`, `document`, and DOM APIs for popup windows, screen dimensions, and external SDK loading
551
+
552
+ **Why is this needed?**
553
+ The library is **framework-agnostic** and works in **React**, **React Native**, **Next.js**, and **Expo**. Each environment has different APIs:
554
+ - **Web**: Uses `window`, `localStorage`, `document`
555
+ - **React Native**: Uses `AsyncStorage`, `Linking`, `Dimensions`
556
+ - **Next.js SSR**: May not have `window` available
557
+
558
+ By requiring explicit service configuration, the library:
559
+ - ✅ Forces you to think about your environment
560
+ - ✅ Avoids unexpected crashes in non-browser environments
561
+ - ✅ Makes it clear which dependencies are being used
562
+ - ✅ Allows easy customization (e.g., using React Router instead of `window.history`)
563
+
564
+ **Alternative: Manual configuration**
565
+
566
+ If you want more control, you can configure services manually:
567
+
568
+ ```typescript
569
+ import { LocalStorageService, WindowRoutingService, BrowserPlatformService } from 'rystem.authentication.social.client';
570
+
571
+ setupSocialLogin(x => {
572
+ x.storageService = new LocalStorageService();
573
+ x.routingService = new WindowRoutingService();
574
+ x.platformService = new BrowserPlatformService();
575
+ // ... rest of config
576
+ });
577
+ ```
578
+
579
+ **For React Native setup**, see the [📱 React Native Complete Guide](#-react-native-complete-guide) section below.
580
+
581
+ ---
582
+
288
583
  ### 2. Use in Components
289
584
 
290
585
  ```typescript
291
- import { useSocialToken, useSocialUser, SocialLoginButtons, SocialLogoutButton } from 'rystem.authentication.social.react';
586
+ import { useSocialToken, useSocialUser, SocialLoginButtons, SocialLogoutButton } from 'rystem.authentication.social.client';
292
587
 
293
588
  export const App = () => {
294
589
  const token = useSocialToken();
@@ -15,8 +15,10 @@ const CreateSocialButton = ({ provider, className = '', redirect_uri, children,
15
15
  // Clean up any leftover popup result from previous attempts
16
16
  // Note: Popup mode uses native localStorage for cross-window communication (storage event)
17
17
  // The popup processes token exchange and saves the result (success or error)
18
- const social_result = `social_result_${provider}`;
19
- localStorage.removeItem(social_result);
18
+ if (typeof window !== 'undefined' && typeof localStorage !== 'undefined') {
19
+ const social_result = `social_result_${provider}`;
20
+ localStorage.removeItem(social_result);
21
+ }
20
22
  }, [provider]);
21
23
  (0, react_1.useEffect)(() => {
22
24
  // Note: Popup window callback (detect code + write to localStorage)
@@ -32,37 +34,27 @@ const CreateSocialButton = ({ provider, className = '', redirect_uri, children,
32
34
  !isSdkLoaded && load();
33
35
  }, [isSdkLoaded]);
34
36
  (0, react_1.useEffect)(() => () => {
35
- if (scriptNodeRef.current)
36
- scriptNodeRef.current.remove();
37
+ if (scriptNodeRef.current) {
38
+ settings.platformService.removeScript(scriptNodeRef.current);
39
+ }
37
40
  }, []);
38
41
  const checkIsExistsSDKScript = (0, react_1.useCallback)(() => {
39
- return !!document.getElementById(scriptId);
40
- }, []);
41
- const insertScript = (0, react_1.useCallback)((d, s = 'script', id, jsSrc, cb) => {
42
- const sdkScriptTag = d.createElement(s);
43
- sdkScriptTag.id = id;
44
- sdkScriptTag.src = jsSrc;
45
- sdkScriptTag.async = true;
46
- sdkScriptTag.defer = true;
47
- sdkScriptTag.crossorigin = "anonymous";
48
- const scriptNode = document.getElementsByTagName('script')[0];
49
- scriptNodeRef.current = sdkScriptTag;
50
- scriptNode &&
51
- scriptNode.parentNode &&
52
- scriptNode.parentNode.insertBefore(sdkScriptTag, scriptNode);
53
- sdkScriptTag.onload = cb;
54
- }, []);
42
+ return settings.platformService.scriptExists(scriptId);
43
+ }, [scriptId]);
55
44
  const load = (0, react_1.useCallback)(() => {
56
45
  if (checkIsExistsSDKScript()) {
57
46
  setIsSdkLoaded(true);
58
47
  }
59
48
  else {
60
- insertScript(document, 'script', scriptId, scriptUri ?? "", () => {
49
+ const scriptElement = settings.platformService.loadScript(scriptId, scriptUri ?? "", () => {
61
50
  onScriptLoad && onScriptLoad();
62
51
  setIsSdkLoaded(true);
63
52
  });
53
+ if (scriptElement) {
54
+ scriptNodeRef.current = scriptElement;
55
+ }
64
56
  }
65
- }, [checkIsExistsSDKScript, insertScript]);
57
+ }, [checkIsExistsSDKScript, scriptId, scriptUri, onScriptLoad]);
66
58
  const handlePostMessage = (0, react_1.useCallback)(async (code) => {
67
59
  if (code)
68
60
  __1.SocialLoginManager.Instance(null).updateToken(provider, code);
@@ -75,6 +67,10 @@ const CreateSocialButton = ({ provider, className = '', redirect_uri, children,
75
67
  settings.onLoginFailure({ code: 7, message: x, provider: provider });
76
68
  };
77
69
  const onChangeLocalStorage = (0, react_1.useCallback)(() => {
70
+ if (typeof window === 'undefined') {
71
+ console.warn('CreateSocialButton: window not available (React Native or SSR?)');
72
+ return;
73
+ }
78
74
  window.removeEventListener('storage', onChangeLocalStorage, false);
79
75
  // Popup mode: The popup processes the token exchange and saves the result
80
76
  // We just need to read the result and update the UI (or show error)
@@ -131,11 +127,12 @@ const CreateSocialButton = ({ provider, className = '', redirect_uri, children,
131
127
  }
132
128
  else {
133
129
  // Popup mode: open in popup window using routing service
134
- window.addEventListener('storage', onChangeLocalStorage, false);
130
+ settings.platformService.addStorageListener(onChangeLocalStorage);
131
+ // Calculate popup position
135
132
  const width = 450;
136
133
  const height = 730;
137
- const left = window.screen.width / 2 - width / 2;
138
- const top = window.screen.height / 2 - height / 2;
134
+ const left = settings.platformService.getScreenWidth() / 2 - width / 2;
135
+ const top = settings.platformService.getScreenHeight() / 2 - height / 2;
139
136
  const features = 'menubar=no,location=no,resizable=no,scrollbars=no,status=no, width=' +
140
137
  width +
141
138
  ', height=' +
@@ -152,6 +149,10 @@ const CreateSocialButton = ({ provider, className = '', redirect_uri, children,
152
149
  isSdkLoaded,
153
150
  onChangeLocalStorage,
154
151
  loginMode,
152
+ load,
153
+ onClick,
154
+ handlePostMessage,
155
+ handleError,
155
156
  ]);
156
157
  return ((0, jsx_runtime_1.jsx)("div", { className: className, onClick: onLogin, children: children }, provider.toString()));
157
158
  };
@@ -23,13 +23,15 @@ const SocialLoginWrapper = (c) => {
23
23
  const loginMode = settings.platform?.loginMode || LoginMode_1.LoginMode.Popup;
24
24
  // Use routingService to read URL parameters (supports React Router, Next.js, etc.)
25
25
  const routingService = settings.routingService;
26
+ const storageService = settings.storageService;
27
+ const platformService = settings.platformService;
26
28
  const code = routingService.getSearchParam(queryCode);
27
29
  const stateParam = routingService.getSearchParam(queryState);
28
30
  console.log('🔍 SocialLoginWrapper: OAuth callback detection:', {
29
31
  code: code ? 'present' : 'missing',
30
32
  state: stateParam,
31
33
  loginMode,
32
- isPopup: window.opener !== null,
34
+ isPopup: platformService.isPopup(),
33
35
  alreadyProcessed: callbackProcessedRef.current,
34
36
  routingService: routingService.constructor.name
35
37
  });
@@ -44,43 +46,41 @@ const SocialLoginWrapper = (c) => {
44
46
  const providerName = ProviderType_1.ProviderType[providerType];
45
47
  // Mark as processed BEFORE making API call
46
48
  callbackProcessedRef.current = true;
47
- // Check if we're in a popup window (simpler condition)
48
- // If window.opener exists, we're definitely in a popup, regardless of loginMode config
49
- if (window.opener) {
49
+ // Check if we're in a popup window using platformService
50
+ if (platformService.isPopup()) {
50
51
  // POPUP MODE: Process token exchange IN THE POPUP, then save result and close
51
52
  console.log('✅ [Popup] Detected popup window, processing token exchange in popup');
52
- const social_code = `social_code_${providerType}`;
53
53
  const social_result = `social_result_${providerType}`;
54
54
  __1.SocialLoginManager.Instance(null).updateToken(providerType, code)
55
55
  .then(() => {
56
56
  console.log('✅ [Popup] Token exchange successful, saving success result');
57
- // Save success result for main window
58
- localStorage.setItem(social_result, JSON.stringify({
57
+ // Save success result for main window using storage service
58
+ storageService.set(social_result, JSON.stringify({
59
59
  success: true,
60
60
  provider: providerName
61
61
  }));
62
- // Close popup after ensuring localStorage is written
62
+ // Close popup after ensuring storage is written
63
63
  setTimeout(() => {
64
64
  console.log('🔒 [Popup] Closing popup after successful login');
65
- window.close();
65
+ platformService.closeWindow();
66
66
  }, 100);
67
67
  })
68
68
  .catch((error) => {
69
69
  console.error('❌ [Popup] Token exchange failed:', error);
70
- // Save error result for main window
71
- localStorage.setItem(social_result, JSON.stringify({
70
+ // Save error result for main window using storage service
71
+ storageService.set(social_result, JSON.stringify({
72
72
  success: false,
73
73
  provider: providerName,
74
74
  error: error.message || 'Token exchange failed'
75
75
  }));
76
- // Close popup after ensuring localStorage is written
76
+ // Close popup after ensuring storage is written
77
77
  setTimeout(() => {
78
78
  console.log('🔒 [Popup] Closing popup after error');
79
- window.close();
79
+ platformService.closeWindow();
80
80
  }, 100);
81
81
  });
82
82
  // Note: The main window's CreateSocialButton will receive the storage event
83
- // and read the result (success or error) from localStorage
83
+ // and read the result (success or error) from storage
84
84
  }
85
85
  else {
86
86
  // REDIRECT MODE: Process token exchange directly in main window
@@ -89,7 +89,6 @@ const SocialLoginWrapper = (c) => {
89
89
  .then(() => {
90
90
  console.log('✅ Token exchange successful');
91
91
  // Get return URL saved before OAuth redirect using storage service
92
- const storageService = settings.storageService;
93
92
  const returnUrl = storageService.get('social_login_return_url');
94
93
  storageService.remove('social_login_return_url');
95
94
  if (returnUrl) {
@@ -116,7 +115,7 @@ const SocialLoginWrapper = (c) => {
116
115
  });
117
116
  }
118
117
  // Clean return URL on error using storage service
119
- settings.storageService.remove('social_login_return_url');
118
+ storageService.remove('social_login_return_url');
120
119
  // Clean URL even on error using routing service
121
120
  const cleanPath = routingService.getCurrentPath().split('?')[0];
122
121
  routingService.navigateReplace(cleanPath);
package/lib/index.d.ts CHANGED
@@ -40,6 +40,8 @@ export { TokenStorageService } from './services/TokenStorageService';
40
40
  export { UserStorageService } from './services/UserStorageService';
41
41
  export type { IRoutingService } from './services/IRoutingService';
42
42
  export { WindowRoutingService } from './services/WindowRoutingService';
43
+ export type { IPlatformService } from './services/IPlatformService';
44
+ export { BrowserPlatformService } from './services/BrowserPlatformService';
43
45
  export { PlatformType } from './models/setup/PlatformType';
44
46
  export { LoginMode } from './models/setup/LoginMode';
45
47
  export type { PlatformConfig, PlatformSelector } from './models/setup/PlatformConfig';
package/lib/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.selectByPlatform = exports.buildRedirectUri = exports.isReactNative = exports.isMobilePlatform = exports.getDefaultRedirectUri = exports.detectPlatform = exports.LoginMode = exports.PlatformType = exports.WindowRoutingService = exports.UserStorageService = exports.TokenStorageService = exports.PkceStorageService = exports.LocalStorageService = exports.PinterestIcon = exports.TikTokIcon = exports.InstagramIcon = exports.AmazonIcon = exports.LinkedInIcon = exports.XIcon = exports.GitHubIcon = exports.FacebookIcon = exports.GoogleIcon = exports.MicrosoftIcon = exports.ModernSocialButton = exports.generateCodeChallenge = exports.generateCodeVerifier = exports.SocialLoginButtons = exports.CreateSocialButton = exports.ProviderType = exports.SocialLoginManager = exports.setupSocialLogin = exports.getSocialLoginSettings = exports.removeSocialLogin = exports.useSocialUser = exports.useSocialToken = exports.SocialLoginContextLogout = exports.SocialLoginContextRefresh = exports.SocialLoginContextUpdate = exports.SocialLoginWrapper = exports.SocialLogoutButton = exports.PinterestButton = exports.InstagramButton = exports.TikTokButton = exports.XButton = exports.LinkedinButton = exports.GitHubButton = exports.FacebookButton = exports.MicrosoftButton = exports.GoogleButton = exports.AmazonButton = void 0;
3
+ exports.buildRedirectUri = exports.isReactNative = exports.isMobilePlatform = exports.getDefaultRedirectUri = exports.detectPlatform = exports.LoginMode = exports.PlatformType = exports.BrowserPlatformService = exports.WindowRoutingService = exports.UserStorageService = exports.TokenStorageService = exports.PkceStorageService = exports.LocalStorageService = exports.PinterestIcon = exports.TikTokIcon = exports.InstagramIcon = exports.AmazonIcon = exports.LinkedInIcon = exports.XIcon = exports.GitHubIcon = exports.FacebookIcon = exports.GoogleIcon = exports.MicrosoftIcon = exports.ModernSocialButton = exports.generateCodeChallenge = exports.generateCodeVerifier = exports.SocialLoginButtons = exports.CreateSocialButton = exports.ProviderType = exports.SocialLoginManager = exports.setupSocialLogin = exports.getSocialLoginSettings = exports.removeSocialLogin = exports.useSocialUser = exports.useSocialToken = exports.SocialLoginContextLogout = exports.SocialLoginContextRefresh = exports.SocialLoginContextUpdate = exports.SocialLoginWrapper = exports.SocialLogoutButton = exports.PinterestButton = exports.InstagramButton = exports.TikTokButton = exports.XButton = exports.LinkedinButton = exports.GitHubButton = exports.FacebookButton = exports.MicrosoftButton = exports.GoogleButton = exports.AmazonButton = void 0;
4
+ exports.selectByPlatform = void 0;
4
5
  var AmazonButton_1 = require("./buttons/singles/AmazonButton");
5
6
  Object.defineProperty(exports, "AmazonButton", { enumerable: true, get: function () { return AmazonButton_1.AmazonButton; } });
6
7
  var GoogleButton_1 = require("./buttons/singles/GoogleButton");
@@ -75,6 +76,8 @@ var UserStorageService_1 = require("./services/UserStorageService");
75
76
  Object.defineProperty(exports, "UserStorageService", { enumerable: true, get: function () { return UserStorageService_1.UserStorageService; } });
76
77
  var WindowRoutingService_1 = require("./services/WindowRoutingService");
77
78
  Object.defineProperty(exports, "WindowRoutingService", { enumerable: true, get: function () { return WindowRoutingService_1.WindowRoutingService; } });
79
+ var BrowserPlatformService_1 = require("./services/BrowserPlatformService");
80
+ Object.defineProperty(exports, "BrowserPlatformService", { enumerable: true, get: function () { return BrowserPlatformService_1.BrowserPlatformService; } });
78
81
  // Platform and login mode support
79
82
  var PlatformType_1 = require("./models/setup/PlatformType");
80
83
  Object.defineProperty(exports, "PlatformType", { enumerable: true, get: function () { return PlatformType_1.PlatformType; } });
@@ -2,6 +2,7 @@ import { SocialLoginErrorResponse, SocialParameter } from "../..";
2
2
  import { PlatformConfig } from "./PlatformConfig";
3
3
  import { IStorageService } from "../../services/IStorageService";
4
4
  import { IRoutingService } from "../../services/IRoutingService";
5
+ import { IPlatformService } from "../../services/IPlatformService";
5
6
  export interface SocialLoginSettings {
6
7
  apiUri: string;
7
8
  automaticRefresh: boolean;
@@ -10,26 +11,73 @@ export interface SocialLoginSettings {
10
11
  title: string | null;
11
12
  /**
12
13
  * Storage service for persisting tokens, PKCE verifiers, etc.
13
- * Default: LocalStorageService (browser localStorage)
14
14
  *
15
- * @example Custom secure storage for mobile
16
- * storageService: new SecureStorageService()
15
+ * ⚠️ **Required**: Must be configured explicitly or via `useBrowserDefaults()`.
16
+ *
17
+ * Web: Use `x.useBrowserDefaults()` or manually set `new LocalStorageService()`
18
+ * React Native: Implement `IStorageService` with AsyncStorage
19
+ *
20
+ * @example Web (automatic)
21
+ * x.useBrowserDefaults();
22
+ *
23
+ * @example Web (manual)
24
+ * x.storageService = new LocalStorageService();
25
+ *
26
+ * @example React Native
27
+ * x.storageService = new ReactNativeStorageService();
17
28
  */
18
29
  storageService: IStorageService;
19
30
  /**
20
31
  * Routing service for URL parameter reading and navigation
21
- * Default: WindowRoutingService (uses window.location and window.history)
32
+ *
33
+ * ⚠️ **Required**: Must be configured explicitly or via `useBrowserDefaults()`.
34
+ *
35
+ * Web: Use `x.useBrowserDefaults()` or manually set `new WindowRoutingService()`
36
+ * React Native: Implement `IRoutingService` with Linking API
22
37
  *
23
38
  * IMPORTANT: Required for React Router, Next.js App Router, and other client-side routing frameworks
24
39
  * to properly handle OAuth callbacks, redirects, and return URLs.
25
40
  *
26
- * @example React Router with useSearchParams, useNavigate, and useLocation hooks
27
- * routingService: new ReactRouterRoutingService()
41
+ * @example Web (automatic)
42
+ * x.useBrowserDefaults();
28
43
  *
29
- * @example Next.js App Router with useRouter, usePathname, and useSearchParams
30
- * routingService: new NextAppRouterRoutingService()
44
+ * @example Web (manual)
45
+ * x.routingService = new WindowRoutingService();
46
+ *
47
+ * @example React Router
48
+ * x.routingService = new ReactRouterRoutingService();
49
+ *
50
+ * @example Next.js App Router
51
+ * x.routingService = new NextAppRouterRoutingService();
52
+ *
53
+ * @example React Native
54
+ * x.routingService = new ReactNativeRoutingService();
31
55
  */
32
56
  routingService: IRoutingService;
57
+ /**
58
+ * Platform service for environment-specific operations (events, dimensions, scripts)
59
+ *
60
+ * ⚠️ **Required**: Must be configured explicitly or via `useBrowserDefaults()`.
61
+ *
62
+ * Web: Use `x.useBrowserDefaults()` or manually set `new BrowserPlatformService()`
63
+ * React Native: Implement `IPlatformService` with Dimensions, EventEmitter
64
+ *
65
+ * Used by UI components for:
66
+ * - Storage event listeners (popup ↔ main window communication)
67
+ * - Screen dimensions (popup positioning)
68
+ * - External script loading (Google SDK, Facebook SDK)
69
+ * - Window operations (popup detection, closing)
70
+ *
71
+ * @example Web (automatic)
72
+ * x.useBrowserDefaults();
73
+ *
74
+ * @example Web (manual)
75
+ * x.platformService = new BrowserPlatformService();
76
+ *
77
+ * @example React Native
78
+ * x.platformService = new ReactNativePlatformService();
79
+ */
80
+ platformService: IPlatformService;
33
81
  /**
34
82
  * Platform configuration (Web, iOS, Android)
35
83
  *
@@ -58,6 +106,31 @@ export interface SocialLoginSettings {
58
106
  instagram: SocialParameter;
59
107
  pinterest: SocialParameter;
60
108
  tiktok: SocialParameter;
109
+ /**
110
+ * 🚀 Helper method for WEB applications
111
+ *
112
+ * Automatically configures browser-based services:
113
+ * - `storageService`: LocalStorageService (uses browser localStorage)
114
+ * - `routingService`: WindowRoutingService (uses window.location and window.history)
115
+ * - `platformService`: BrowserPlatformService (uses window, document, DOM APIs)
116
+ *
117
+ * ⚠️ **Web only**: Do NOT call this in React Native applications!
118
+ *
119
+ * @example Basic web setup
120
+ * setupSocialLogin(x => {
121
+ * x.useBrowserDefaults(); // ✅ One line to configure all browser services
122
+ * x.apiUri = "https://api.example.com";
123
+ * x.microsoft.clientId = "your-client-id";
124
+ * });
125
+ *
126
+ * @example With custom routing (React Router, Next.js)
127
+ * setupSocialLogin(x => {
128
+ * x.useBrowserDefaults();
129
+ * x.routingService = new ReactRouterRoutingService(); // Override routing only
130
+ * // ... rest of config
131
+ * });
132
+ */
133
+ useBrowserDefaults(): void;
61
134
  }
62
135
  export interface IIdentityTransformer<TIdentity> {
63
136
  toPlain: (input: TIdentity | any) => any;
@@ -0,0 +1,38 @@
1
+ import type { IPlatformService } from './IPlatformService';
2
+ /**
3
+ * Browser implementation of IPlatformService
4
+ * Uses native window, document, and DOM APIs
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * const platformService = new BrowserPlatformService();
9
+ *
10
+ * // Listen for storage events (popup communication)
11
+ * platformService.addStorageListener(() => {
12
+ * console.log('Storage changed!');
13
+ * });
14
+ *
15
+ * // Get screen dimensions
16
+ * const width = platformService.getScreenWidth();
17
+ *
18
+ * // Load external script (e.g., Google SDK)
19
+ * platformService.loadScript('google-sdk', 'https://apis.google.com/js/api.js', () => {
20
+ * console.log('Google SDK loaded!');
21
+ * });
22
+ * ```
23
+ */
24
+ export declare class BrowserPlatformService implements IPlatformService {
25
+ /**
26
+ * Check if browser APIs are available
27
+ */
28
+ private isAvailable;
29
+ addStorageListener(callback: () => void): void;
30
+ removeStorageListener(callback: () => void): void;
31
+ getScreenWidth(): number;
32
+ getScreenHeight(): number;
33
+ loadScript(id: string, src: string, onLoad: () => void): HTMLScriptElement | null;
34
+ scriptExists(id: string): boolean;
35
+ removeScript(scriptElement: HTMLScriptElement): void;
36
+ isPopup(): boolean;
37
+ closeWindow(): void;
38
+ }
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BrowserPlatformService = void 0;
4
+ /**
5
+ * Browser implementation of IPlatformService
6
+ * Uses native window, document, and DOM APIs
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const platformService = new BrowserPlatformService();
11
+ *
12
+ * // Listen for storage events (popup communication)
13
+ * platformService.addStorageListener(() => {
14
+ * console.log('Storage changed!');
15
+ * });
16
+ *
17
+ * // Get screen dimensions
18
+ * const width = platformService.getScreenWidth();
19
+ *
20
+ * // Load external script (e.g., Google SDK)
21
+ * platformService.loadScript('google-sdk', 'https://apis.google.com/js/api.js', () => {
22
+ * console.log('Google SDK loaded!');
23
+ * });
24
+ * ```
25
+ */
26
+ class BrowserPlatformService {
27
+ /**
28
+ * Check if browser APIs are available
29
+ */
30
+ isAvailable() {
31
+ return typeof window !== 'undefined';
32
+ }
33
+ // ===== Storage Event Handling =====
34
+ addStorageListener(callback) {
35
+ if (!this.isAvailable()) {
36
+ console.warn('BrowserPlatformService: window not available (SSR?)');
37
+ return;
38
+ }
39
+ window.addEventListener('storage', callback, false);
40
+ }
41
+ removeStorageListener(callback) {
42
+ if (!this.isAvailable()) {
43
+ return;
44
+ }
45
+ window.removeEventListener('storage', callback, false);
46
+ }
47
+ // ===== Screen Dimensions =====
48
+ getScreenWidth() {
49
+ if (!this.isAvailable() || !window.screen) {
50
+ return 1024; // Default fallback
51
+ }
52
+ return window.screen.width;
53
+ }
54
+ getScreenHeight() {
55
+ if (!this.isAvailable() || !window.screen) {
56
+ return 768; // Default fallback
57
+ }
58
+ return window.screen.height;
59
+ }
60
+ // ===== Script Loading =====
61
+ loadScript(id, src, onLoad) {
62
+ if (!this.isAvailable() || typeof document === 'undefined') {
63
+ console.warn('BrowserPlatformService: document not available (SSR?)');
64
+ onLoad(); // Call callback anyway to avoid hanging
65
+ return null;
66
+ }
67
+ // Check if script already exists
68
+ if (this.scriptExists(id)) {
69
+ onLoad();
70
+ return document.getElementById(id);
71
+ }
72
+ const script = document.createElement('script');
73
+ script.id = id;
74
+ script.src = src;
75
+ script.async = true;
76
+ script.defer = true;
77
+ script.crossOrigin = 'anonymous';
78
+ script.onload = onLoad;
79
+ const firstScript = document.getElementsByTagName('script')[0];
80
+ if (firstScript && firstScript.parentNode) {
81
+ firstScript.parentNode.insertBefore(script, firstScript);
82
+ }
83
+ else {
84
+ document.head.appendChild(script);
85
+ }
86
+ return script;
87
+ }
88
+ scriptExists(id) {
89
+ if (!this.isAvailable() || typeof document === 'undefined') {
90
+ return false;
91
+ }
92
+ return !!document.getElementById(id);
93
+ }
94
+ removeScript(scriptElement) {
95
+ if (!this.isAvailable() || typeof document === 'undefined') {
96
+ return;
97
+ }
98
+ if (scriptElement && scriptElement.parentNode) {
99
+ scriptElement.parentNode.removeChild(scriptElement);
100
+ }
101
+ }
102
+ // ===== Window Operations =====
103
+ isPopup() {
104
+ if (!this.isAvailable()) {
105
+ return false;
106
+ }
107
+ return window.opener !== null;
108
+ }
109
+ closeWindow() {
110
+ if (!this.isAvailable()) {
111
+ console.warn('BrowserPlatformService: window not available (SSR?)');
112
+ return;
113
+ }
114
+ window.close();
115
+ }
116
+ }
117
+ exports.BrowserPlatformService = BrowserPlatformService;
File without changes
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Platform-agnostic service for environment-specific operations
3
+ * Abstracts browser/native APIs for cross-platform support
4
+ *
5
+ * Implementations:
6
+ * - BrowserPlatformService (web): Uses window, document, storage events
7
+ * - ReactNativePlatformService (mobile): Uses EventEmitter, Dimensions, Linking
8
+ *
9
+ * Use cases:
10
+ * - Web: DOM manipulation, popup windows, storage events
11
+ * - React Native: Custom events, screen dimensions, deep links
12
+ * - SSR: No-op implementations to avoid crashes
13
+ */
14
+ export interface IPlatformService {
15
+ /**
16
+ * Add listener for storage changes (used for popup → main window communication)
17
+ * Web: Uses window.addEventListener('storage', ...)
18
+ * React Native: Uses custom event emitter or no-op
19
+ *
20
+ * @param callback Function to call when storage changes
21
+ */
22
+ addStorageListener(callback: () => void): void;
23
+ /**
24
+ * Remove storage event listener
25
+ * Web: Uses window.removeEventListener('storage', ...)
26
+ * React Native: Removes custom event listener
27
+ *
28
+ * @param callback Function to remove
29
+ */
30
+ removeStorageListener(callback: () => void): void;
31
+ /**
32
+ * Get screen width for popup positioning
33
+ * Web: Returns window.screen.width
34
+ * React Native: Returns Dimensions.get('window').width
35
+ *
36
+ * @returns Screen width in pixels
37
+ */
38
+ getScreenWidth(): number;
39
+ /**
40
+ * Get screen height for popup positioning
41
+ * Web: Returns window.screen.height
42
+ * React Native: Returns Dimensions.get('window').height
43
+ *
44
+ * @returns Screen height in pixels
45
+ */
46
+ getScreenHeight(): number;
47
+ /**
48
+ * Load external JavaScript SDK
49
+ * Web: Injects <script> tag into document
50
+ * React Native: No-op (SDKs are bundled or not used)
51
+ *
52
+ * @param id Unique script ID
53
+ * @param src Script URL
54
+ * @param onLoad Callback when script loads
55
+ * @returns Script element if loaded, null otherwise
56
+ */
57
+ loadScript(id: string, src: string, onLoad: () => void): HTMLScriptElement | null;
58
+ /**
59
+ * Check if script is already loaded
60
+ * Web: Checks document.getElementById(id)
61
+ * React Native: Returns false (no script loading)
62
+ *
63
+ * @param id Script ID
64
+ * @returns True if script exists
65
+ */
66
+ scriptExists(id: string): boolean;
67
+ /**
68
+ * Remove loaded script
69
+ * Web: Removes script tag from DOM
70
+ * React Native: No-op
71
+ *
72
+ * @param scriptElement Script element to remove
73
+ */
74
+ removeScript(scriptElement: HTMLScriptElement): void;
75
+ /**
76
+ * Check if current window is a popup window
77
+ * Web: Checks window.opener !== null
78
+ * React Native: Returns false (no popup concept)
79
+ *
80
+ * @returns True if running in a popup window
81
+ */
82
+ isPopup(): boolean;
83
+ /**
84
+ * Close current window (used after popup OAuth callback)
85
+ * Web: Calls window.close()
86
+ * React Native: No-op or custom navigation
87
+ */
88
+ closeWindow(): void;
89
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,2 +1,29 @@
1
1
  import { SocialLoginSettings, SocialLoginManager } from "..";
2
+ /**
3
+ * Setup social login authentication
4
+ *
5
+ * ⚠️ **Important**: You MUST configure the following services:
6
+ * - `storageService` (IStorageService)
7
+ * - `routingService` (IRoutingService)
8
+ * - `platformService` (IPlatformService)
9
+ *
10
+ * For **web applications**, use the helper method:
11
+ * ```typescript
12
+ * setupSocialLogin(x => {
13
+ * x.useBrowserDefaults(); // ✅ Sets up localStorage, window routing, and browser platform
14
+ * x.apiUri = "https://api.example.com";
15
+ * x.microsoft.clientId = "your-client-id";
16
+ * });
17
+ * ```
18
+ *
19
+ * For **React Native**, provide custom implementations:
20
+ * ```typescript
21
+ * setupSocialLogin(x => {
22
+ * x.storageService = new ReactNativeStorageService();
23
+ * x.routingService = new ReactNativeRoutingService();
24
+ * x.platformService = new ReactNativePlatformService();
25
+ * // ... rest of config
26
+ * });
27
+ * ```
28
+ */
2
29
  export declare const setupSocialLogin: (settings: (settings: SocialLoginSettings) => void) => SocialLoginManager;
@@ -7,6 +7,34 @@ const PlatformType_1 = require("../models/setup/PlatformType");
7
7
  const platform_1 = require("../utils/platform");
8
8
  const LocalStorageService_1 = require("../services/LocalStorageService");
9
9
  const WindowRoutingService_1 = require("../services/WindowRoutingService");
10
+ const BrowserPlatformService_1 = require("../services/BrowserPlatformService");
11
+ /**
12
+ * Setup social login authentication
13
+ *
14
+ * ⚠️ **Important**: You MUST configure the following services:
15
+ * - `storageService` (IStorageService)
16
+ * - `routingService` (IRoutingService)
17
+ * - `platformService` (IPlatformService)
18
+ *
19
+ * For **web applications**, use the helper method:
20
+ * ```typescript
21
+ * setupSocialLogin(x => {
22
+ * x.useBrowserDefaults(); // ✅ Sets up localStorage, window routing, and browser platform
23
+ * x.apiUri = "https://api.example.com";
24
+ * x.microsoft.clientId = "your-client-id";
25
+ * });
26
+ * ```
27
+ *
28
+ * For **React Native**, provide custom implementations:
29
+ * ```typescript
30
+ * setupSocialLogin(x => {
31
+ * x.storageService = new ReactNativeStorageService();
32
+ * x.routingService = new ReactNativeRoutingService();
33
+ * x.platformService = new ReactNativePlatformService();
34
+ * // ... rest of config
35
+ * });
36
+ * ```
37
+ */
10
38
  const setupSocialLogin = function (settings) {
11
39
  // ✅ Lazy evaluation: only access window when actually needed
12
40
  const getDefaultApiUri = () => {
@@ -17,15 +45,17 @@ const setupSocialLogin = function (settings) {
17
45
  return '';
18
46
  };
19
47
  const parameters = {
20
- apiUri: getDefaultApiUri(), // ✅ Safe: checks window existence
48
+ apiUri: getDefaultApiUri(),
21
49
  title: null,
22
50
  onLoginFailure: (data) => { console.log(data.code); },
23
51
  automaticRefresh: false,
24
- storageService: new LocalStorageService_1.LocalStorageService(), // Default: localStorage
25
- routingService: new WindowRoutingService_1.WindowRoutingService(), // Default: window.location + window.history
52
+ // Temporarily undefined - MUST be configured before validation
53
+ storageService: undefined,
54
+ routingService: undefined,
55
+ platformService: undefined,
26
56
  platform: {
27
57
  type: PlatformType_1.PlatformType.Auto,
28
- redirectPath: undefined, // Smart default: will use window.location.origin + "/account/login"
58
+ redirectPath: undefined,
29
59
  loginMode: LoginMode_1.LoginMode.Popup
30
60
  },
31
61
  google: {},
@@ -38,8 +68,52 @@ const setupSocialLogin = function (settings) {
38
68
  instagram: {},
39
69
  pinterest: {},
40
70
  tiktok: {},
71
+ // ✅ Helper method for web applications
72
+ useBrowserDefaults: function () {
73
+ this.storageService = new LocalStorageService_1.LocalStorageService();
74
+ this.routingService = new WindowRoutingService_1.WindowRoutingService();
75
+ this.platformService = new BrowserPlatformService_1.BrowserPlatformService();
76
+ }
41
77
  };
42
78
  settings(parameters);
79
+ // ✅ VALIDATION: Ensure required services are configured
80
+ const missingServices = [];
81
+ if (!parameters.storageService) {
82
+ missingServices.push('storageService (IStorageService)');
83
+ }
84
+ if (!parameters.routingService) {
85
+ missingServices.push('routingService (IRoutingService)');
86
+ }
87
+ if (!parameters.platformService) {
88
+ missingServices.push('platformService (IPlatformService)');
89
+ }
90
+ if (missingServices.length > 0) {
91
+ const errorMessage = `
92
+ 🚨 Missing required services in setupSocialLogin():
93
+
94
+ ${missingServices.map(s => ` ❌ ${s}`).join('\n')}
95
+
96
+ 📖 Solutions:
97
+
98
+ 1️⃣ For WEB applications, use the helper method:
99
+ setupSocialLogin(x => {
100
+ x.useBrowserDefaults(); // ✅ Auto-configures browser services
101
+ x.apiUri = "https://api.example.com";
102
+ x.microsoft.clientId = "your-client-id";
103
+ });
104
+
105
+ 2️⃣ For REACT NATIVE, provide custom implementations:
106
+ setupSocialLogin(x => {
107
+ x.storageService = new ReactNativeStorageService();
108
+ x.routingService = new ReactNativeRoutingService();
109
+ x.platformService = new ReactNativePlatformService();
110
+ // ... rest of config
111
+ });
112
+
113
+ 📚 Documentation: https://github.com/KeyserDSoze/Rystem/blob/master/src/Authentication/rystem.authentication.social.client/README.md
114
+ `.trim();
115
+ throw new Error(errorMessage);
116
+ }
43
117
  // Auto-detect platform if set to 'auto'
44
118
  if (!parameters.platform) {
45
119
  parameters.platform = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rystem.authentication.social.client",
3
- "version": "0.5.0",
3
+ "version": "0.5.4",
4
4
  "author": "Alessandro Rapiti <alessandro.rapiti44@gmail.com>",
5
5
  "license": "MIT",
6
6
  "description": "Framework-agnostic social authentication library for web and mobile (React, React Native, Next.js, Expo, etc.)",