react-native-linkedin-oauth2 1.0.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +709 -2
- package/dist/index.d.ts +199 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +646 -2
- package/dist/index.js.map +1 -1
- package/dist/setupTests.d.ts +2 -0
- package/dist/setupTests.d.ts.map +1 -0
- package/dist/setupTests.js +40 -0
- package/dist/setupTests.js.map +1 -0
- package/package.json +37 -6
- package/dist/__tests__/index.test.d.ts +0 -2
- package/dist/__tests__/index.test.d.ts.map +0 -1
- package/dist/__tests__/index.test.js +0 -10
- package/dist/__tests__/index.test.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,3 +1,710 @@
|
|
|
1
|
-
|
|
1
|
+
# react-native-linkedin-oauth2
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://badge.fury.io/js/react-native-linkedin-oauth2)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
A simple, lightweight, and fully customizable React Native package for integrating LinkedIn OAuth2 authentication into your mobile apps. Built with TypeScript and designed for ease of use.
|
|
7
|
+
|
|
8
|
+
## 📦 Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install react-native-linkedin-oauth2
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
or
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
yarn add react-native-linkedin-oauth2
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Dependencies
|
|
21
|
+
|
|
22
|
+
This package requires the following peer dependencies:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install react-native-webview react-native-safe-area-context
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
or
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
yarn add react-native-webview react-native-safe-area-context
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
For iOS, run:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd ios && pod install
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 🔧 LinkedIn Developer Console Setup
|
|
41
|
+
|
|
42
|
+
Before using this package, you need to set up an OAuth2 application in the LinkedIn Developer Console:
|
|
43
|
+
|
|
44
|
+
1. **Create a LinkedIn App**
|
|
45
|
+
- Go to [LinkedIn Developers](https://www.linkedin.com/developers/apps)
|
|
46
|
+
- Click "Create app"
|
|
47
|
+
- Fill in the required information
|
|
48
|
+
|
|
49
|
+
2. **Configure OAuth Settings**
|
|
50
|
+
- Navigate to the "Auth" tab in your app settings
|
|
51
|
+
- Add your redirect URI (e.g., `https://yourapp.com/auth/linkedin/callback`)
|
|
52
|
+
- Note: The redirect URI must match exactly what you use in the component
|
|
53
|
+
|
|
54
|
+
3. **Get Your Credentials**
|
|
55
|
+
- Copy your **Client ID** from the app settings
|
|
56
|
+
- Copy your **Client Secret** (keep this secure!)
|
|
57
|
+
|
|
58
|
+
4. **Request Scopes**
|
|
59
|
+
- In the "Auth" tab, request the scopes you need
|
|
60
|
+
- Common scopes: `openid`, `profile`, `email`
|
|
61
|
+
- Additional scopes may require LinkedIn approval
|
|
62
|
+
|
|
63
|
+
## 🚀 Quick Start
|
|
64
|
+
|
|
65
|
+
Here's a simple example to get you started:
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
import React, { useState } from 'react';
|
|
69
|
+
import { View, Button, Text } from 'react-native';
|
|
70
|
+
import { LinkedInModal, LinkedInProfile } from 'react-native-linkedin-oauth2';
|
|
71
|
+
|
|
72
|
+
const App = () => {
|
|
73
|
+
const [showModal, setShowModal] = useState(false);
|
|
74
|
+
const [user, setUser] = useState<LinkedInProfile | null>(null);
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
|
78
|
+
{user ? (
|
|
79
|
+
<View>
|
|
80
|
+
<Text>Welcome, {user.name}!</Text>
|
|
81
|
+
<Text>Email: {user.email}</Text>
|
|
82
|
+
</View>
|
|
83
|
+
) : (
|
|
84
|
+
<Button
|
|
85
|
+
title="Sign in with LinkedIn"
|
|
86
|
+
onPress={() => setShowModal(true)}
|
|
87
|
+
/>
|
|
88
|
+
)}
|
|
89
|
+
|
|
90
|
+
<LinkedInModal
|
|
91
|
+
isVisible={showModal}
|
|
92
|
+
clientId="YOUR_CLIENT_ID"
|
|
93
|
+
clientSecret="YOUR_CLIENT_SECRET"
|
|
94
|
+
redirectUri="https://yourapp.com/auth/linkedin/callback"
|
|
95
|
+
onSuccess={profile => {
|
|
96
|
+
console.log('Login successful:', profile);
|
|
97
|
+
setUser(profile);
|
|
98
|
+
setShowModal(false);
|
|
99
|
+
}}
|
|
100
|
+
onError={error => {
|
|
101
|
+
console.error('Login error:', error);
|
|
102
|
+
setShowModal(false);
|
|
103
|
+
}}
|
|
104
|
+
onClose={() => setShowModal(false)}
|
|
105
|
+
/>
|
|
106
|
+
</View>
|
|
107
|
+
);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export default App;
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## 📚 API Reference
|
|
114
|
+
|
|
115
|
+
### LinkedInModal Props
|
|
116
|
+
|
|
117
|
+
| Prop | Type | Required | Default | Description |
|
|
118
|
+
| ---------------- | ----------------------------------------------- | -------- | ---------------------------- | --------------------------------------------------------- |
|
|
119
|
+
| `isVisible` | `boolean` | ✅ | - | Controls the visibility of the modal |
|
|
120
|
+
| `clientId` | `string` | ✅ | - | Your LinkedIn Client ID |
|
|
121
|
+
| `clientSecret` | `string` | ✅ | - | Your LinkedIn Client Secret |
|
|
122
|
+
| `redirectUri` | `string` | ✅ | - | The redirect URI registered in LinkedIn Developer Console |
|
|
123
|
+
| `scope` | `string` | ❌ | `'openid email profile'` | Space-separated list of OAuth scopes |
|
|
124
|
+
| `onSuccess` | `(user: LinkedInProfile) => void` | ❌ | `() => {}` | Callback invoked when authentication succeeds |
|
|
125
|
+
| `onError` | `(error: Error) => void` | ❌ | `() => {}` | Callback invoked when an error occurs |
|
|
126
|
+
| `onClose` | `() => void` | ❌ | `() => {}` | Callback invoked when the modal is closed |
|
|
127
|
+
| `onLogout` | `() => void` | ❌ | `() => {}` | Callback invoked when logout completes |
|
|
128
|
+
| `logout` | `boolean` | ❌ | `false` | If true, shows LinkedIn logout page instead of login |
|
|
129
|
+
| `closeOnSuccess` | `boolean` | ❌ | `true` | Automatically close modal after successful login |
|
|
130
|
+
| `renderHeader` | `(props: { onClose: () => void }) => ReactNode` | ❌ | - | Custom header component |
|
|
131
|
+
| `renderLoading` | `() => ReactNode` | ❌ | - | Custom loading indicator |
|
|
132
|
+
| `containerStyle` | `StyleProp<ViewStyle>` | ❌ | - | Style for the container SafeAreaView |
|
|
133
|
+
| `wrapperStyle` | `StyleProp<ViewStyle>` | ❌ | - | Style for the wrapper View |
|
|
134
|
+
| `modalProps` | `Partial<Omit<ModalProps, 'visible'>>` | ❌ | `{ animationType: 'slide' }` | Additional props for React Native Modal |
|
|
135
|
+
|
|
136
|
+
### LinkedInProfile Type
|
|
137
|
+
|
|
138
|
+
The `LinkedInProfile` interface represents the user data returned after successful authentication:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
interface LinkedInProfile {
|
|
142
|
+
sub: string; // Unique user identifier
|
|
143
|
+
name: string; // Full name
|
|
144
|
+
given_name: string; // First name
|
|
145
|
+
family_name: string; // Last name
|
|
146
|
+
picture: string; // Profile picture URL
|
|
147
|
+
email?: string; // Email (if 'email' scope requested)
|
|
148
|
+
email_verified?: boolean; // Email verification status
|
|
149
|
+
locale: {
|
|
150
|
+
country: string; // Country code (e.g., 'US')
|
|
151
|
+
language: string; // Language code (e.g., 'en')
|
|
152
|
+
};
|
|
153
|
+
[key: string]: any; // Additional fields
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### LinkedInTokenResponse Type
|
|
158
|
+
|
|
159
|
+
The `LinkedInTokenResponse` interface represents the OAuth token response:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
interface LinkedInTokenResponse {
|
|
163
|
+
access_token: string; // Access token for API requests
|
|
164
|
+
expires_in: number; // Token expiration time in seconds
|
|
165
|
+
scope: string; // Granted scopes
|
|
166
|
+
token_type: string; // Token type (usually 'Bearer')
|
|
167
|
+
id_token?: string; // ID token (if OpenID scope requested)
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## 🎨 Advanced Usage
|
|
172
|
+
|
|
173
|
+
### Custom Scopes
|
|
174
|
+
|
|
175
|
+
Request additional permissions by specifying custom scopes:
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
<LinkedInModal
|
|
179
|
+
isVisible={showModal}
|
|
180
|
+
clientId="YOUR_CLIENT_ID"
|
|
181
|
+
clientSecret="YOUR_CLIENT_SECRET"
|
|
182
|
+
redirectUri="YOUR_REDIRECT_URI"
|
|
183
|
+
scope="openid profile email w_member_social" // Request posting permissions
|
|
184
|
+
onSuccess={profile => console.log(profile)}
|
|
185
|
+
onError={error => console.error(error)}
|
|
186
|
+
onClose={() => setShowModal(false)}
|
|
187
|
+
/>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Custom Header
|
|
191
|
+
|
|
192
|
+
Provide your own header component:
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
const CustomHeader = ({ onClose }: { onClose: () => void }) => (
|
|
196
|
+
<View
|
|
197
|
+
style={{
|
|
198
|
+
height: 60,
|
|
199
|
+
backgroundColor: '#0077B5',
|
|
200
|
+
justifyContent: 'center',
|
|
201
|
+
alignItems: 'center',
|
|
202
|
+
}}
|
|
203
|
+
>
|
|
204
|
+
<TouchableOpacity
|
|
205
|
+
onPress={onClose}
|
|
206
|
+
style={{ position: 'absolute', right: 20 }}
|
|
207
|
+
>
|
|
208
|
+
<Text style={{ color: 'white', fontSize: 16 }}>✕</Text>
|
|
209
|
+
</TouchableOpacity>
|
|
210
|
+
<Text style={{ color: 'white', fontSize: 18, fontWeight: 'bold' }}>
|
|
211
|
+
Sign in with LinkedIn
|
|
212
|
+
</Text>
|
|
213
|
+
</View>
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
<LinkedInModal
|
|
217
|
+
isVisible={showModal}
|
|
218
|
+
clientId="YOUR_CLIENT_ID"
|
|
219
|
+
clientSecret="YOUR_CLIENT_SECRET"
|
|
220
|
+
redirectUri="YOUR_REDIRECT_URI"
|
|
221
|
+
renderHeader={props => <CustomHeader {...props} />}
|
|
222
|
+
onSuccess={profile => console.log(profile)}
|
|
223
|
+
onError={error => console.error(error)}
|
|
224
|
+
onClose={() => setShowModal(false)}
|
|
225
|
+
/>;
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Custom Loading Indicator
|
|
229
|
+
|
|
230
|
+
Customize the loading experience:
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
const CustomLoading = () => (
|
|
234
|
+
<View
|
|
235
|
+
style={{
|
|
236
|
+
position: 'absolute',
|
|
237
|
+
top: 0,
|
|
238
|
+
left: 0,
|
|
239
|
+
right: 0,
|
|
240
|
+
bottom: 0,
|
|
241
|
+
justifyContent: 'center',
|
|
242
|
+
alignItems: 'center',
|
|
243
|
+
backgroundColor: 'rgba(0, 119, 181, 0.1)',
|
|
244
|
+
}}
|
|
245
|
+
>
|
|
246
|
+
<ActivityIndicator size="large" color="#0077B5" />
|
|
247
|
+
<Text style={{ marginTop: 10, color: '#0077B5' }}>
|
|
248
|
+
Connecting to LinkedIn...
|
|
249
|
+
</Text>
|
|
250
|
+
</View>
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
<LinkedInModal
|
|
254
|
+
isVisible={showModal}
|
|
255
|
+
clientId="YOUR_CLIENT_ID"
|
|
256
|
+
clientSecret="YOUR_CLIENT_SECRET"
|
|
257
|
+
redirectUri="YOUR_REDIRECT_URI"
|
|
258
|
+
renderLoading={() => <CustomLoading />}
|
|
259
|
+
onSuccess={profile => console.log(profile)}
|
|
260
|
+
onError={error => console.error(error)}
|
|
261
|
+
onClose={() => setShowModal(false)}
|
|
262
|
+
/>;
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Logout Functionality
|
|
266
|
+
|
|
267
|
+
Log out users from LinkedIn:
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
const [showLogoutModal, setShowLogoutModal] = useState(false);
|
|
271
|
+
|
|
272
|
+
<LinkedInModal
|
|
273
|
+
isVisible={showLogoutModal}
|
|
274
|
+
logout={true}
|
|
275
|
+
onLogout={() => {
|
|
276
|
+
console.log('User logged out');
|
|
277
|
+
setShowLogoutModal(false);
|
|
278
|
+
}}
|
|
279
|
+
onClose={() => setShowLogoutModal(false)}
|
|
280
|
+
/>;
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Custom Styling
|
|
284
|
+
|
|
285
|
+
Customize the modal appearance:
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
<LinkedInModal
|
|
289
|
+
isVisible={showModal}
|
|
290
|
+
clientId="YOUR_CLIENT_ID"
|
|
291
|
+
clientSecret="YOUR_CLIENT_SECRET"
|
|
292
|
+
redirectUri="YOUR_REDIRECT_URI"
|
|
293
|
+
containerStyle={{ backgroundColor: '#f5f5f5' }}
|
|
294
|
+
wrapperStyle={{ borderRadius: 10, overflow: 'hidden' }}
|
|
295
|
+
modalProps={{
|
|
296
|
+
animationType: 'fade',
|
|
297
|
+
transparent: true,
|
|
298
|
+
}}
|
|
299
|
+
onSuccess={profile => console.log(profile)}
|
|
300
|
+
onError={error => console.error(error)}
|
|
301
|
+
onClose={() => setShowModal(false)}
|
|
302
|
+
/>
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Keep Modal Open After Success
|
|
306
|
+
|
|
307
|
+
Sometimes you may want to manually control when to close the modal:
|
|
308
|
+
|
|
309
|
+
```tsx
|
|
310
|
+
<LinkedInModal
|
|
311
|
+
isVisible={showModal}
|
|
312
|
+
clientId="YOUR_CLIENT_ID"
|
|
313
|
+
clientSecret="YOUR_CLIENT_SECRET"
|
|
314
|
+
redirectUri="YOUR_REDIRECT_URI"
|
|
315
|
+
closeOnSuccess={false} // Don't auto-close
|
|
316
|
+
onSuccess={profile => {
|
|
317
|
+
console.log('Login successful:', profile);
|
|
318
|
+
// Do something with the profile...
|
|
319
|
+
// Then manually close when ready
|
|
320
|
+
setTimeout(() => setShowModal(false), 2000);
|
|
321
|
+
}}
|
|
322
|
+
onError={error => console.error(error)}
|
|
323
|
+
onClose={() => setShowModal(false)}
|
|
324
|
+
/>
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## 🔍 Complete Example
|
|
328
|
+
|
|
329
|
+
Here's a full-featured example with all the props and callbacks included:
|
|
330
|
+
|
|
331
|
+
```tsx
|
|
332
|
+
import React, { useState } from 'react';
|
|
333
|
+
import {
|
|
334
|
+
View,
|
|
335
|
+
Button,
|
|
336
|
+
Text,
|
|
337
|
+
Image,
|
|
338
|
+
StyleSheet,
|
|
339
|
+
TouchableOpacity,
|
|
340
|
+
ActivityIndicator,
|
|
341
|
+
} from 'react-native';
|
|
342
|
+
import { LinkedInModal, LinkedInProfile } from 'react-native-linkedin-oauth2';
|
|
343
|
+
|
|
344
|
+
const LINKEDIN_CONFIG = {
|
|
345
|
+
clientId: 'YOUR_CLIENT_ID',
|
|
346
|
+
clientSecret: 'YOUR_CLIENT_SECRET',
|
|
347
|
+
redirectUri: 'https://yourapp.com/auth/linkedin/callback',
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const App = () => {
|
|
351
|
+
const [showModal, setShowModal] = useState(false);
|
|
352
|
+
const [showLogoutModal, setShowLogoutModal] = useState(false);
|
|
353
|
+
const [user, setUser] = useState<LinkedInProfile | null>(null);
|
|
354
|
+
const [error, setError] = useState<string | null>(null);
|
|
355
|
+
|
|
356
|
+
const handleLogin = () => {
|
|
357
|
+
setError(null);
|
|
358
|
+
setShowModal(true);
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const handleSuccess = (profile: LinkedInProfile) => {
|
|
362
|
+
console.log('Authentication successful:', profile);
|
|
363
|
+
setUser(profile);
|
|
364
|
+
setError(null);
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const handleError = (err: Error) => {
|
|
368
|
+
console.error('Authentication error:', err);
|
|
369
|
+
setError(err.message);
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const handleLogout = () => {
|
|
373
|
+
setShowLogoutModal(true);
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const CustomHeader = ({ onClose }: { onClose: () => void }) => (
|
|
377
|
+
<View style={styles.header}>
|
|
378
|
+
<Text style={styles.headerTitle}>Sign in with LinkedIn</Text>
|
|
379
|
+
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
|
|
380
|
+
<Text style={styles.closeButtonText}>✕</Text>
|
|
381
|
+
</TouchableOpacity>
|
|
382
|
+
</View>
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
const CustomLoading = () => (
|
|
386
|
+
<View style={styles.loadingContainer}>
|
|
387
|
+
<ActivityIndicator size="large" color="#0077B5" />
|
|
388
|
+
<Text style={styles.loadingText}>Authenticating...</Text>
|
|
389
|
+
</View>
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
return (
|
|
393
|
+
<View style={styles.container}>
|
|
394
|
+
{user ? (
|
|
395
|
+
<View style={styles.profileContainer}>
|
|
396
|
+
<Image source={{ uri: user.picture }} style={styles.profileImage} />
|
|
397
|
+
<Text style={styles.userName}>{user.name}</Text>
|
|
398
|
+
<Text style={styles.userEmail}>{user.email}</Text>
|
|
399
|
+
<Text style={styles.userId}>ID: {user.sub}</Text>
|
|
400
|
+
<Button title="Logout" onPress={handleLogout} color="#dc3545" />
|
|
401
|
+
</View>
|
|
402
|
+
) : (
|
|
403
|
+
<View style={styles.loginContainer}>
|
|
404
|
+
<Text style={styles.title}>LinkedIn OAuth2 Example</Text>
|
|
405
|
+
{error && <Text style={styles.errorText}>{error}</Text>}
|
|
406
|
+
<Button
|
|
407
|
+
title="Sign in with LinkedIn"
|
|
408
|
+
onPress={handleLogin}
|
|
409
|
+
color="#0077B5"
|
|
410
|
+
/>
|
|
411
|
+
</View>
|
|
412
|
+
)}
|
|
413
|
+
|
|
414
|
+
{/* Login Modal */}
|
|
415
|
+
<LinkedInModal
|
|
416
|
+
isVisible={showModal}
|
|
417
|
+
clientId={LINKEDIN_CONFIG.clientId}
|
|
418
|
+
clientSecret={LINKEDIN_CONFIG.clientSecret}
|
|
419
|
+
redirectUri={LINKEDIN_CONFIG.redirectUri}
|
|
420
|
+
scope="openid profile email"
|
|
421
|
+
onSuccess={handleSuccess}
|
|
422
|
+
onError={handleError}
|
|
423
|
+
onClose={() => setShowModal(false)}
|
|
424
|
+
renderHeader={props => <CustomHeader {...props} />}
|
|
425
|
+
renderLoading={() => <CustomLoading />}
|
|
426
|
+
closeOnSuccess={true}
|
|
427
|
+
/>
|
|
428
|
+
|
|
429
|
+
{/* Logout Modal */}
|
|
430
|
+
<LinkedInModal
|
|
431
|
+
isVisible={showLogoutModal}
|
|
432
|
+
logout={true}
|
|
433
|
+
onLogout={() => {
|
|
434
|
+
console.log('Logout successful');
|
|
435
|
+
setUser(null);
|
|
436
|
+
setShowLogoutModal(false);
|
|
437
|
+
}}
|
|
438
|
+
onClose={() => setShowLogoutModal(false)}
|
|
439
|
+
/>
|
|
440
|
+
</View>
|
|
441
|
+
);
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const styles = StyleSheet.create({
|
|
445
|
+
container: {
|
|
446
|
+
flex: 1,
|
|
447
|
+
justifyContent: 'center',
|
|
448
|
+
alignItems: 'center',
|
|
449
|
+
backgroundColor: '#f5f5f5',
|
|
450
|
+
},
|
|
451
|
+
loginContainer: {
|
|
452
|
+
alignItems: 'center',
|
|
453
|
+
padding: 20,
|
|
454
|
+
},
|
|
455
|
+
title: {
|
|
456
|
+
fontSize: 24,
|
|
457
|
+
fontWeight: 'bold',
|
|
458
|
+
marginBottom: 20,
|
|
459
|
+
color: '#333',
|
|
460
|
+
},
|
|
461
|
+
profileContainer: {
|
|
462
|
+
alignItems: 'center',
|
|
463
|
+
padding: 20,
|
|
464
|
+
backgroundColor: 'white',
|
|
465
|
+
borderRadius: 10,
|
|
466
|
+
shadowColor: '#000',
|
|
467
|
+
shadowOffset: { width: 0, height: 2 },
|
|
468
|
+
shadowOpacity: 0.1,
|
|
469
|
+
shadowRadius: 4,
|
|
470
|
+
elevation: 3,
|
|
471
|
+
},
|
|
472
|
+
profileImage: {
|
|
473
|
+
width: 100,
|
|
474
|
+
height: 100,
|
|
475
|
+
borderRadius: 50,
|
|
476
|
+
marginBottom: 15,
|
|
477
|
+
},
|
|
478
|
+
userName: {
|
|
479
|
+
fontSize: 22,
|
|
480
|
+
fontWeight: 'bold',
|
|
481
|
+
color: '#333',
|
|
482
|
+
marginBottom: 5,
|
|
483
|
+
},
|
|
484
|
+
userEmail: {
|
|
485
|
+
fontSize: 16,
|
|
486
|
+
color: '#666',
|
|
487
|
+
marginBottom: 5,
|
|
488
|
+
},
|
|
489
|
+
userId: {
|
|
490
|
+
fontSize: 12,
|
|
491
|
+
color: '#999',
|
|
492
|
+
marginBottom: 20,
|
|
493
|
+
},
|
|
494
|
+
errorText: {
|
|
495
|
+
color: 'red',
|
|
496
|
+
marginBottom: 10,
|
|
497
|
+
textAlign: 'center',
|
|
498
|
+
},
|
|
499
|
+
header: {
|
|
500
|
+
height: 60,
|
|
501
|
+
backgroundColor: '#0077B5',
|
|
502
|
+
justifyContent: 'center',
|
|
503
|
+
alignItems: 'center',
|
|
504
|
+
flexDirection: 'row',
|
|
505
|
+
},
|
|
506
|
+
headerTitle: {
|
|
507
|
+
color: 'white',
|
|
508
|
+
fontSize: 18,
|
|
509
|
+
fontWeight: 'bold',
|
|
510
|
+
},
|
|
511
|
+
closeButton: {
|
|
512
|
+
position: 'absolute',
|
|
513
|
+
right: 20,
|
|
514
|
+
padding: 10,
|
|
515
|
+
},
|
|
516
|
+
closeButtonText: {
|
|
517
|
+
color: 'white',
|
|
518
|
+
fontSize: 24,
|
|
519
|
+
fontWeight: 'bold',
|
|
520
|
+
},
|
|
521
|
+
loadingContainer: {
|
|
522
|
+
position: 'absolute',
|
|
523
|
+
top: 0,
|
|
524
|
+
left: 0,
|
|
525
|
+
right: 0,
|
|
526
|
+
bottom: 0,
|
|
527
|
+
justifyContent: 'center',
|
|
528
|
+
alignItems: 'center',
|
|
529
|
+
backgroundColor: 'rgba(0, 119, 181, 0.1)',
|
|
530
|
+
},
|
|
531
|
+
loadingText: {
|
|
532
|
+
marginTop: 10,
|
|
533
|
+
color: '#0077B5',
|
|
534
|
+
fontSize: 16,
|
|
535
|
+
},
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
export default App;
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
## 🛠️ Troubleshooting
|
|
542
|
+
|
|
543
|
+
### Common Issues
|
|
544
|
+
|
|
545
|
+
#### 1. "Missing required props" Warning
|
|
546
|
+
|
|
547
|
+
**Problem:** You're seeing a warning about missing `clientId`, `clientSecret`, or `redirectUri`.
|
|
548
|
+
|
|
549
|
+
**Solution:** Ensure all three required props are provided:
|
|
550
|
+
|
|
551
|
+
```tsx
|
|
552
|
+
<LinkedInModal
|
|
553
|
+
isVisible={true}
|
|
554
|
+
clientId="YOUR_CLIENT_ID" // ✅ Required
|
|
555
|
+
clientSecret="YOUR_CLIENT_SECRET" // ✅ Required
|
|
556
|
+
redirectUri="YOUR_REDIRECT_URI" // ✅ Required
|
|
557
|
+
// ...other props
|
|
558
|
+
/>
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
#### 2. "Failed to get access token" Error
|
|
562
|
+
|
|
563
|
+
**Problem:** The token exchange fails.
|
|
564
|
+
|
|
565
|
+
**Possible causes:**
|
|
566
|
+
|
|
567
|
+
- Incorrect `clientSecret`
|
|
568
|
+
- Redirect URI mismatch between code and LinkedIn Developer Console
|
|
569
|
+
- Expired or invalid authorization code
|
|
570
|
+
|
|
571
|
+
**Solution:**
|
|
572
|
+
|
|
573
|
+
- Verify your credentials in the LinkedIn Developer Console
|
|
574
|
+
- Ensure the `redirectUri` prop matches exactly what's registered in your app settings
|
|
575
|
+
- Check that your app is not in a restricted mode in the developer console
|
|
576
|
+
|
|
577
|
+
#### 3. Network Request Failed
|
|
578
|
+
|
|
579
|
+
**Problem:** Unable to connect to LinkedIn servers.
|
|
580
|
+
|
|
581
|
+
**Solution:**
|
|
582
|
+
|
|
583
|
+
- Check your internet connection
|
|
584
|
+
- Ensure your app has network permissions (especially on Android)
|
|
585
|
+
- Verify that LinkedIn's OAuth endpoints are accessible from your network
|
|
586
|
+
|
|
587
|
+
#### 4. Email is Undefined
|
|
588
|
+
|
|
589
|
+
**Problem:** The `user.email` field is `undefined` after successful login.
|
|
590
|
+
|
|
591
|
+
**Solution:**
|
|
592
|
+
|
|
593
|
+
- Make sure you've requested the `email` scope:
|
|
594
|
+
```tsx
|
|
595
|
+
<LinkedInModal scope="openid profile email" />
|
|
596
|
+
```
|
|
597
|
+
- Verify that the `email` scope is approved in your LinkedIn app settings
|
|
598
|
+
|
|
599
|
+
#### 5. WebView Not Loading
|
|
600
|
+
|
|
601
|
+
**Problem:** The WebView appears blank or doesn't load.
|
|
602
|
+
|
|
603
|
+
**Solution:**
|
|
604
|
+
|
|
605
|
+
- Ensure `react-native-webview` is properly installed
|
|
606
|
+
- For iOS, run `cd ios && pod install`
|
|
607
|
+
- Check that you've linked the library correctly (this should be automatic with auto-linking)
|
|
608
|
+
|
|
609
|
+
#### 6. Modal Styling Issues
|
|
610
|
+
|
|
611
|
+
**Problem:** The modal doesn't look right or doesn't fit the screen.
|
|
612
|
+
|
|
613
|
+
**Solution:**
|
|
614
|
+
|
|
615
|
+
- Ensure `react-native-safe-area-context` is installed and configured
|
|
616
|
+
- Wrap your root app component with `SafeAreaProvider`:
|
|
617
|
+
|
|
618
|
+
```tsx
|
|
619
|
+
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
620
|
+
|
|
621
|
+
const App = () => (
|
|
622
|
+
<SafeAreaProvider>{/* Your app content */}</SafeAreaProvider>
|
|
623
|
+
);
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
## 🔒 Security Considerations
|
|
627
|
+
|
|
628
|
+
> [!CAUTION]
|
|
629
|
+
> **Client Secret Exposure:** The `clientSecret` is included in your React Native app bundle, which means it can be extracted by determined users. For maximum security, consider implementing a backend proxy that handles the token exchange instead of doing it directly in the mobile app.
|
|
630
|
+
|
|
631
|
+
### Recommended Secure Implementation
|
|
632
|
+
|
|
633
|
+
Instead of passing the `clientSecret` directly:
|
|
634
|
+
|
|
635
|
+
1. Create a backend endpoint that accepts the authorization code
|
|
636
|
+
2. Have your backend exchange the code for a token using the `clientSecret`
|
|
637
|
+
3. Return the profile data (or your own JWT) to the mobile app
|
|
638
|
+
|
|
639
|
+
This way, the `clientSecret` stays secure on your server.
|
|
640
|
+
|
|
641
|
+
## 📄 TypeScript Support
|
|
642
|
+
|
|
643
|
+
This package is written in TypeScript and includes full type definitions. No need for `@types/` packages!
|
|
644
|
+
|
|
645
|
+
**Exported Types:**
|
|
646
|
+
|
|
647
|
+
- `LinkedInProfile` - User profile data structure
|
|
648
|
+
- `LinkedInTokenResponse` - OAuth token response structure
|
|
649
|
+
- `LinkedInModalProps` - Component props interface
|
|
650
|
+
|
|
651
|
+
```typescript
|
|
652
|
+
import {
|
|
653
|
+
LinkedInModal,
|
|
654
|
+
LinkedInProfile,
|
|
655
|
+
LinkedInTokenResponse,
|
|
656
|
+
LinkedInModalProps,
|
|
657
|
+
} from 'react-native-linkedin-oauth2';
|
|
658
|
+
|
|
659
|
+
const handleSuccess = (profile: LinkedInProfile) => {
|
|
660
|
+
// TypeScript knows the structure of profile
|
|
661
|
+
console.log(profile.name, profile.email);
|
|
662
|
+
};
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
## 📱 Supported Platforms
|
|
666
|
+
|
|
667
|
+
- ✅ iOS
|
|
668
|
+
- ✅ Android
|
|
669
|
+
- ❌ Web (requires different OAuth flow)
|
|
670
|
+
|
|
671
|
+
## 🤝 Contributing
|
|
672
|
+
|
|
673
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
674
|
+
|
|
675
|
+
1. Fork the repository
|
|
676
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
677
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
678
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
679
|
+
5. Open a Pull Request
|
|
680
|
+
|
|
681
|
+
## 📝 License
|
|
682
|
+
|
|
683
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
684
|
+
|
|
685
|
+
## 👨💻 Author
|
|
686
|
+
|
|
687
|
+
**Nikhil Wankhede**
|
|
688
|
+
|
|
689
|
+
- GitHub: [@NikhilRW](https://github.com/NikhilRW)
|
|
690
|
+
|
|
691
|
+
## 🙏 Acknowledgments
|
|
692
|
+
|
|
693
|
+
- Built with [react-native-webview](https://github.com/react-native-webview/react-native-webview)
|
|
694
|
+
- Uses [react-native-safe-area-context](https://github.com/th3rdwave/react-native-safe-area-context)
|
|
695
|
+
|
|
696
|
+
## 📦 Related Packages
|
|
697
|
+
|
|
698
|
+
- [react-native-webview](https://www.npmjs.com/package/react-native-webview) - WebView component for React Native
|
|
699
|
+
- [react-native-safe-area-context](https://www.npmjs.com/package/react-native-safe-area-context) - Safe area context for React Native
|
|
700
|
+
|
|
701
|
+
## 🔗 Links
|
|
702
|
+
|
|
703
|
+
- [NPM Package](https://www.npmjs.com/package/react-native-linkedin-oauth2)
|
|
704
|
+
- [GitHub Repository](https://github.com/NikhilRW/react-native-linkedin-oauth2)
|
|
705
|
+
- [LinkedIn Developers Portal](https://www.linkedin.com/developers/)
|
|
706
|
+
- [LinkedIn OAuth 2.0 Documentation](https://docs.microsoft.com/en-us/linkedin/shared/authentication/authentication)
|
|
707
|
+
|
|
708
|
+
---
|
|
709
|
+
|
|
710
|
+
**Made with ❤️ by Nikhil Wankhede**
|