@oxyhq/services 5.7.2 → 5.7.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 +287 -80
- package/lib/commonjs/core/index.js +0 -57
- package/lib/commonjs/core/index.js.map +1 -1
- package/lib/commonjs/node/index.js +49 -2
- package/lib/commonjs/node/index.js.map +1 -1
- package/lib/commonjs/node/middleware.js +227 -0
- package/lib/commonjs/node/middleware.js.map +1 -0
- package/lib/commonjs/ui/context/OxyContext.js +9 -29
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSessionSocket.js +1 -4
- package/lib/commonjs/ui/hooks/useSessionSocket.js.map +1 -1
- package/lib/commonjs/ui/index.js +16 -1
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/zero-config/index.js +25 -0
- package/lib/commonjs/ui/zero-config/index.js.map +1 -0
- package/lib/commonjs/ui/zero-config/provider.js +278 -0
- package/lib/commonjs/ui/zero-config/provider.js.map +1 -0
- package/lib/module/core/index.js +0 -57
- package/lib/module/core/index.js.map +1 -1
- package/lib/module/node/index.js +11 -1
- package/lib/module/node/index.js.map +1 -1
- package/lib/module/node/middleware.js +199 -0
- package/lib/module/node/middleware.js.map +1 -0
- package/lib/module/ui/context/OxyContext.js +9 -29
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/useSessionSocket.js +1 -4
- package/lib/module/ui/hooks/useSessionSocket.js.map +1 -1
- package/lib/module/ui/index.js +16 -1
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/zero-config/index.js +8 -0
- package/lib/module/ui/zero-config/index.js.map +1 -0
- package/lib/module/ui/zero-config/provider.js +270 -0
- package/lib/module/ui/zero-config/provider.js.map +1 -0
- package/lib/typescript/core/index.d.ts +0 -6
- package/lib/typescript/core/index.d.ts.map +1 -1
- package/lib/typescript/node/index.d.ts +7 -1
- package/lib/typescript/node/index.d.ts.map +1 -1
- package/lib/typescript/node/middleware.d.ts +92 -0
- package/lib/typescript/node/middleware.d.ts.map +1 -0
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useSessionSocket.d.ts.map +1 -1
- package/lib/typescript/ui/index.d.ts +2 -1
- package/lib/typescript/ui/index.d.ts.map +1 -1
- package/lib/typescript/ui/zero-config/index.d.ts +5 -0
- package/lib/typescript/ui/zero-config/index.d.ts.map +1 -0
- package/lib/typescript/ui/zero-config/provider.d.ts +84 -0
- package/lib/typescript/ui/zero-config/provider.d.ts.map +1 -0
- package/package.json +6 -1
- package/src/core/index.ts +0 -60
- package/src/node/index.ts +17 -1
- package/src/node/middleware.ts +234 -0
- package/src/ui/context/OxyContext.tsx +9 -29
- package/src/ui/hooks/useSessionSocket.ts +1 -4
- package/src/ui/index.ts +19 -1
- package/src/ui/zero-config/index.ts +11 -0
- package/src/ui/zero-config/provider.tsx +310 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-config OxyHQ Provider and Hook for React/React Native
|
|
3
|
+
*
|
|
4
|
+
* This provides a simplified, one-line setup for frontend authentication
|
|
5
|
+
* with automatic token management and API integration.
|
|
6
|
+
*/
|
|
7
|
+
import React, { ReactNode } from 'react';
|
|
8
|
+
import { OxyServices } from '../../core';
|
|
9
|
+
import { User } from '../../models/interfaces';
|
|
10
|
+
export interface OxyZeroConfigState {
|
|
11
|
+
user: User | null;
|
|
12
|
+
isAuthenticated: boolean;
|
|
13
|
+
isLoading: boolean;
|
|
14
|
+
error: string | null;
|
|
15
|
+
login: (username: string, password: string) => Promise<User>;
|
|
16
|
+
logout: () => Promise<void>;
|
|
17
|
+
register: (username: string, email: string, password: string) => Promise<User>;
|
|
18
|
+
api: OxyServices;
|
|
19
|
+
}
|
|
20
|
+
export interface OxyZeroConfigProviderProps {
|
|
21
|
+
children: ReactNode;
|
|
22
|
+
/** Base URL of your Oxy API server (defaults to process.env.REACT_APP_OXY_API_URL or http://localhost:3001) */
|
|
23
|
+
apiUrl?: string;
|
|
24
|
+
/** Called when authentication state changes */
|
|
25
|
+
onAuthChange?: (user: User | null) => void;
|
|
26
|
+
/** Storage key prefix (default: 'oxy_zero') */
|
|
27
|
+
storagePrefix?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Zero-config provider for OxyHQ Services
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```tsx
|
|
34
|
+
* import { OxyZeroConfigProvider } from '@oxyhq/services/ui';
|
|
35
|
+
*
|
|
36
|
+
* function App() {
|
|
37
|
+
* return (
|
|
38
|
+
* <OxyZeroConfigProvider>
|
|
39
|
+
* <MyApp />
|
|
40
|
+
* </OxyZeroConfigProvider>
|
|
41
|
+
* );
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare const OxyZeroConfigProvider: React.FC<OxyZeroConfigProviderProps>;
|
|
46
|
+
/**
|
|
47
|
+
* Zero-config hook for OxyHQ Services
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```tsx
|
|
51
|
+
* function MyComponent() {
|
|
52
|
+
* const { user, login, logout, isAuthenticated } = useOxyZeroConfig();
|
|
53
|
+
*
|
|
54
|
+
* const handleLogin = () => {
|
|
55
|
+
* login('username', 'password');
|
|
56
|
+
* };
|
|
57
|
+
*
|
|
58
|
+
* if (isAuthenticated) {
|
|
59
|
+
* return <div>Welcome, {user?.username}!</div>;
|
|
60
|
+
* }
|
|
61
|
+
*
|
|
62
|
+
* return <button onClick={handleLogin}>Login</button>;
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare const useOxyZeroConfig: () => OxyZeroConfigState;
|
|
67
|
+
/**
|
|
68
|
+
* Hook for automatic API client with authentication
|
|
69
|
+
* This automatically includes the auth token in requests
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```tsx
|
|
73
|
+
* function ProfileComponent() {
|
|
74
|
+
* const api = useOxyApi();
|
|
75
|
+
*
|
|
76
|
+
* const updateProfile = async (data) => {
|
|
77
|
+
* const user = await api.updateProfile(data);
|
|
78
|
+
* // Token is automatically included
|
|
79
|
+
* };
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare const useOxyApi: () => OxyServices;
|
|
84
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../../src/ui/zero-config/provider.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,EAAkD,SAAS,EAAe,MAAM,OAAO,CAAC;AACtG,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,IAAI,EAAiB,MAAM,yBAAyB,CAAC;AAE9D,MAAM,WAAW,kBAAkB;IAEjC,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAGrB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAG/E,GAAG,EAAE,WAAW,CAAC;CAClB;AAID,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,SAAS,CAAC;IACpB,+GAA+G;IAC/G,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC;IAC3C,+CAA+C;IAC/C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CA6MtE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,gBAAgB,QAAO,kBAMnC,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,SAAS,QAAO,WAG5B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/services",
|
|
3
|
-
"version": "5.7.
|
|
3
|
+
"version": "5.7.4",
|
|
4
4
|
"description": "Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -23,6 +23,11 @@
|
|
|
23
23
|
"require": "./lib/commonjs/ui/index.js",
|
|
24
24
|
"types": "./lib/typescript/ui/index.d.ts"
|
|
25
25
|
},
|
|
26
|
+
"./node": {
|
|
27
|
+
"import": "./lib/module/node/index.js",
|
|
28
|
+
"require": "./lib/commonjs/node/index.js",
|
|
29
|
+
"types": "./lib/typescript/node/index.d.ts"
|
|
30
|
+
},
|
|
26
31
|
"./full": {
|
|
27
32
|
"import": "./lib/module/index.js",
|
|
28
33
|
"require": "./lib/commonjs/index.js",
|
package/src/core/index.ts
CHANGED
|
@@ -1381,24 +1381,6 @@ export class OxyServices {
|
|
|
1381
1381
|
});
|
|
1382
1382
|
}
|
|
1383
1383
|
|
|
1384
|
-
// Validate JWT format before attempting to decode
|
|
1385
|
-
if (!this.isValidJwtFormat(token)) {
|
|
1386
|
-
const error = {
|
|
1387
|
-
message: 'Invalid token format',
|
|
1388
|
-
code: 'INVALID_TOKEN_FORMAT',
|
|
1389
|
-
status: 401
|
|
1390
|
-
};
|
|
1391
|
-
|
|
1392
|
-
if (onError) {
|
|
1393
|
-
return onError(error);
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
return res.status(401).json({
|
|
1397
|
-
message: 'Invalid token format',
|
|
1398
|
-
code: 'INVALID_TOKEN_FORMAT'
|
|
1399
|
-
});
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
1384
|
// Create a temporary OxyServices instance with the token to validate it
|
|
1403
1385
|
const tempOxyServices = new OxyServices({
|
|
1404
1386
|
baseURL: this.client.defaults.baseURL || ''
|
|
@@ -1431,9 +1413,6 @@ export class OxyServices {
|
|
|
1431
1413
|
const decoded = jwtDecode<JwtPayload>(token);
|
|
1432
1414
|
userId = decoded.userId || decoded.id;
|
|
1433
1415
|
} catch (decodeError) {
|
|
1434
|
-
console.error('JWT decode error:', decodeError);
|
|
1435
|
-
console.error('Token being decoded:', token.substring(0, 20) + '...');
|
|
1436
|
-
|
|
1437
1416
|
const error = {
|
|
1438
1417
|
message: 'Invalid token payload',
|
|
1439
1418
|
code: 'INVALID_PAYLOAD',
|
|
@@ -1520,14 +1499,6 @@ export class OxyServices {
|
|
|
1520
1499
|
};
|
|
1521
1500
|
}
|
|
1522
1501
|
|
|
1523
|
-
// Validate JWT format before attempting to decode
|
|
1524
|
-
if (!this.isValidJwtFormat(token)) {
|
|
1525
|
-
return {
|
|
1526
|
-
valid: false,
|
|
1527
|
-
error: 'Invalid token format'
|
|
1528
|
-
};
|
|
1529
|
-
}
|
|
1530
|
-
|
|
1531
1502
|
// Create a temporary OxyServices instance with the token
|
|
1532
1503
|
const tempOxyServices = new OxyServices({
|
|
1533
1504
|
baseURL: this.client.defaults.baseURL || ''
|
|
@@ -1585,37 +1556,6 @@ export class OxyServices {
|
|
|
1585
1556
|
}
|
|
1586
1557
|
}
|
|
1587
1558
|
|
|
1588
|
-
/**
|
|
1589
|
-
* Validate JWT token format
|
|
1590
|
-
* @param token - The token to validate
|
|
1591
|
-
* @returns Boolean indicating if the token has valid JWT format
|
|
1592
|
-
*/
|
|
1593
|
-
public isValidJwtFormat(token: string): boolean {
|
|
1594
|
-
if (!token || typeof token !== 'string') {
|
|
1595
|
-
return false;
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
// JWT tokens should have 3 parts separated by dots
|
|
1599
|
-
const parts = token.split('.');
|
|
1600
|
-
if (parts.length !== 3) {
|
|
1601
|
-
return false;
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
// Each part should be a valid base64 string
|
|
1605
|
-
try {
|
|
1606
|
-
parts.forEach(part => {
|
|
1607
|
-
if (!part || part.trim() === '') {
|
|
1608
|
-
throw new Error('Empty part');
|
|
1609
|
-
}
|
|
1610
|
-
// Try to decode base64 (this will throw if invalid)
|
|
1611
|
-
Buffer.from(part, 'base64');
|
|
1612
|
-
});
|
|
1613
|
-
return true;
|
|
1614
|
-
} catch (error) {
|
|
1615
|
-
return false;
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
|
|
1619
1559
|
/**
|
|
1620
1560
|
* Centralized error handling
|
|
1621
1561
|
* @private
|
package/src/node/index.ts
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Node.js-specific exports for OxyHQ Services
|
|
3
|
+
*
|
|
4
|
+
* This module provides zero-config Express.js middleware and utilities
|
|
5
|
+
* for backend integration with OxyHQ Services.
|
|
3
6
|
*/
|
|
4
7
|
|
|
8
|
+
// Export the zero-config middleware
|
|
9
|
+
export {
|
|
10
|
+
createOxyAuth,
|
|
11
|
+
createOptionalOxyAuth,
|
|
12
|
+
createOxyExpressApp,
|
|
13
|
+
type OxyAuthConfig,
|
|
14
|
+
type AuthenticatedRequest
|
|
15
|
+
} from './middleware';
|
|
16
|
+
|
|
5
17
|
// ------------- Core Imports -------------
|
|
6
18
|
import { OxyServices, OXY_CLOUD_URL } from '../core'; // Adjusted path
|
|
7
19
|
import { createAuth } from './createAuth';
|
|
@@ -16,6 +28,10 @@ export { createAuth };
|
|
|
16
28
|
// ------------- Model Exports -------------
|
|
17
29
|
export { Models }; // Export all models as a namespace
|
|
18
30
|
export * from '../models/interfaces'; // Export all models directly
|
|
31
|
+
export * from '../models/secureSession';
|
|
32
|
+
|
|
33
|
+
// Re-export utilities
|
|
34
|
+
export { DeviceManager } from '../utils/deviceManager';
|
|
19
35
|
|
|
20
36
|
// Default export for consistency or specific use cases if needed
|
|
21
37
|
export default OxyServices;
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-config Express middleware for OxyHQ Services authentication
|
|
3
|
+
*
|
|
4
|
+
* This provides a simple, one-line solution for adding authentication to Express apps.
|
|
5
|
+
* Simply import and use: app.use('/api', createOxyAuth())
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { OxyServices } from '../core';
|
|
9
|
+
import { ApiError } from '../models/interfaces';
|
|
10
|
+
|
|
11
|
+
export interface OxyAuthConfig {
|
|
12
|
+
/** Base URL of your Oxy API server */
|
|
13
|
+
baseURL?: string;
|
|
14
|
+
/** Whether to load full user data (default: true) */
|
|
15
|
+
loadUser?: boolean;
|
|
16
|
+
/** Routes that don't require authentication */
|
|
17
|
+
publicPaths?: string[];
|
|
18
|
+
/** Custom error handler */
|
|
19
|
+
onError?: (error: ApiError, req: any, res: any) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface AuthenticatedRequest {
|
|
23
|
+
user?: any;
|
|
24
|
+
userId?: string;
|
|
25
|
+
accessToken?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Creates zero-config authentication middleware for Express.js
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* import express from 'express';
|
|
34
|
+
* import { createOxyAuth } from '@oxyhq/services/node';
|
|
35
|
+
*
|
|
36
|
+
* const app = express();
|
|
37
|
+
*
|
|
38
|
+
* // Zero-config auth for all /api routes
|
|
39
|
+
* app.use('/api', createOxyAuth());
|
|
40
|
+
*
|
|
41
|
+
* // Now all routes under /api automatically have req.user available
|
|
42
|
+
* app.get('/api/profile', (req, res) => {
|
|
43
|
+
* res.json({ user: req.user }); // req.user is automatically available
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function createOxyAuth(config: OxyAuthConfig = {}) {
|
|
48
|
+
const {
|
|
49
|
+
baseURL = process.env.OXY_API_URL || 'http://localhost:3001',
|
|
50
|
+
loadUser = true,
|
|
51
|
+
publicPaths = [],
|
|
52
|
+
onError
|
|
53
|
+
} = config;
|
|
54
|
+
|
|
55
|
+
const oxy = new OxyServices({ baseURL });
|
|
56
|
+
|
|
57
|
+
return async (req: any, res: any, next: any) => {
|
|
58
|
+
// Check if this is a public path
|
|
59
|
+
const isPublicPath = publicPaths.some(path =>
|
|
60
|
+
req.path === path || req.path.startsWith(path + '/')
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (isPublicPath) {
|
|
64
|
+
return next();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const authHeader = req.headers['authorization'];
|
|
69
|
+
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
|
|
70
|
+
|
|
71
|
+
if (!token) {
|
|
72
|
+
const error: ApiError = {
|
|
73
|
+
message: 'Access token required',
|
|
74
|
+
code: 'MISSING_TOKEN',
|
|
75
|
+
status: 401
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
if (onError) {
|
|
79
|
+
return onError(error, req, res);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return res.status(401).json({
|
|
83
|
+
error: 'Access token required',
|
|
84
|
+
code: 'MISSING_TOKEN'
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Validate token using the OxyServices client
|
|
89
|
+
const authResult = await oxy.authenticateToken(token);
|
|
90
|
+
|
|
91
|
+
if (!authResult.valid) {
|
|
92
|
+
const error: ApiError = {
|
|
93
|
+
message: authResult.error || 'Invalid token',
|
|
94
|
+
code: 'INVALID_TOKEN',
|
|
95
|
+
status: 403
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (onError) {
|
|
99
|
+
return onError(error, req, res);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return res.status(403).json({
|
|
103
|
+
error: authResult.error || 'Invalid token',
|
|
104
|
+
code: 'INVALID_TOKEN'
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Attach user data to request
|
|
109
|
+
req.userId = authResult.userId;
|
|
110
|
+
req.accessToken = token;
|
|
111
|
+
|
|
112
|
+
if (loadUser && authResult.user) {
|
|
113
|
+
req.user = authResult.user;
|
|
114
|
+
} else {
|
|
115
|
+
req.user = { id: authResult.userId };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
next();
|
|
119
|
+
} catch (error: any) {
|
|
120
|
+
const apiError: ApiError = {
|
|
121
|
+
message: error.message || 'Authentication failed',
|
|
122
|
+
code: error.code || 'AUTH_ERROR',
|
|
123
|
+
status: error.status || 500
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
if (onError) {
|
|
127
|
+
return onError(apiError, req, res);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
res.status(apiError.status).json({
|
|
131
|
+
error: apiError.message,
|
|
132
|
+
code: apiError.code
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Creates optional authentication middleware
|
|
140
|
+
* This middleware will attach user data if a valid token is present, but won't fail if missing
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```typescript
|
|
144
|
+
* import { createOptionalOxyAuth } from '@oxyhq/services/node';
|
|
145
|
+
*
|
|
146
|
+
* app.use('/api', createOptionalOxyAuth());
|
|
147
|
+
*
|
|
148
|
+
* app.get('/api/content', (req, res) => {
|
|
149
|
+
* if (req.user) {
|
|
150
|
+
* // User is authenticated, show personalized content
|
|
151
|
+
* res.json({ content: 'personalized', user: req.user });
|
|
152
|
+
* } else {
|
|
153
|
+
* // Anonymous user, show public content
|
|
154
|
+
* res.json({ content: 'public' });
|
|
155
|
+
* }
|
|
156
|
+
* });
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
export function createOptionalOxyAuth(config: OxyAuthConfig = {}) {
|
|
160
|
+
const {
|
|
161
|
+
baseURL = process.env.OXY_API_URL || 'http://localhost:3001',
|
|
162
|
+
loadUser = true
|
|
163
|
+
} = config;
|
|
164
|
+
|
|
165
|
+
const oxy = new OxyServices({ baseURL });
|
|
166
|
+
|
|
167
|
+
return async (req: any, res: any, next: any) => {
|
|
168
|
+
try {
|
|
169
|
+
const authHeader = req.headers['authorization'];
|
|
170
|
+
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
|
|
171
|
+
|
|
172
|
+
if (!token) {
|
|
173
|
+
// No token provided, continue without authentication
|
|
174
|
+
return next();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Validate token using the OxyServices client
|
|
178
|
+
const authResult = await oxy.authenticateToken(token);
|
|
179
|
+
|
|
180
|
+
if (authResult.valid) {
|
|
181
|
+
// Attach user data to request if token is valid
|
|
182
|
+
req.userId = authResult.userId;
|
|
183
|
+
req.accessToken = token;
|
|
184
|
+
|
|
185
|
+
if (loadUser && authResult.user) {
|
|
186
|
+
req.user = authResult.user;
|
|
187
|
+
} else {
|
|
188
|
+
req.user = { id: authResult.userId };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
next();
|
|
193
|
+
} catch (error) {
|
|
194
|
+
// If there's an error, continue without authentication
|
|
195
|
+
// This makes the middleware truly optional
|
|
196
|
+
next();
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Utility function to quickly set up a complete Express app with authentication
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```typescript
|
|
206
|
+
* import { createOxyExpressApp } from '@oxyhq/services/node';
|
|
207
|
+
*
|
|
208
|
+
* const app = createOxyExpressApp();
|
|
209
|
+
*
|
|
210
|
+
* // All routes automatically have authentication and req.user available
|
|
211
|
+
* app.get('/api/profile', (req, res) => {
|
|
212
|
+
* res.json({ user: req.user });
|
|
213
|
+
* });
|
|
214
|
+
*
|
|
215
|
+
* app.listen(3000);
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
export function createOxyExpressApp(config: OxyAuthConfig & {
|
|
219
|
+
/** Express app configuration */
|
|
220
|
+
cors?: boolean;
|
|
221
|
+
/** JSON body parser limit */
|
|
222
|
+
jsonLimit?: string;
|
|
223
|
+
/** Additional middleware to apply */
|
|
224
|
+
middleware?: any[];
|
|
225
|
+
} = {}) {
|
|
226
|
+
// This is a lightweight helper - users should import express themselves
|
|
227
|
+
// We'll provide the middleware setup instructions instead
|
|
228
|
+
|
|
229
|
+
throw new Error('createOxyExpressApp is not implemented yet. Please use createOxyAuth() middleware with your existing Express app.');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Re-export for convenience
|
|
233
|
+
export { OxyServices } from '../core';
|
|
234
|
+
export * from '../models/interfaces';
|
|
@@ -138,18 +138,6 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
138
138
|
// Add a new state to track token restoration
|
|
139
139
|
const [tokenReady, setTokenReady] = React.useState(false);
|
|
140
140
|
|
|
141
|
-
// Add refs to prevent duplicate API calls
|
|
142
|
-
const initAuthRef = React.useRef(false);
|
|
143
|
-
const tokenRestoreRef = React.useRef(false);
|
|
144
|
-
|
|
145
|
-
// Development warning about React StrictMode
|
|
146
|
-
React.useEffect(() => {
|
|
147
|
-
if (__DEV__) {
|
|
148
|
-
console.log('🔍 OxyContext: React StrictMode may cause effects to run twice in development');
|
|
149
|
-
console.log('🔍 This is normal and helps detect side effects. Production builds will not have this behavior.');
|
|
150
|
-
}
|
|
151
|
-
}, []);
|
|
152
|
-
|
|
153
141
|
// Storage keys (memoized to prevent infinite loops)
|
|
154
142
|
const keys = useMemo(() => getSecureStorageKeys(storageKeyPrefix), [storageKeyPrefix]);
|
|
155
143
|
|
|
@@ -171,12 +159,8 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
171
159
|
// Effect to initialize authentication state
|
|
172
160
|
useEffect(() => {
|
|
173
161
|
const initAuth = async () => {
|
|
174
|
-
if (!storage
|
|
175
|
-
|
|
176
|
-
// Prevent multiple simultaneous initializations
|
|
177
|
-
if (isLoading) return;
|
|
162
|
+
if (!storage) return;
|
|
178
163
|
|
|
179
|
-
initAuthRef.current = true;
|
|
180
164
|
useAuthStore.setState({ isLoading: true });
|
|
181
165
|
try {
|
|
182
166
|
// Load stored sessions
|
|
@@ -270,16 +254,15 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
270
254
|
}
|
|
271
255
|
};
|
|
272
256
|
|
|
273
|
-
if (storage
|
|
257
|
+
if (storage) {
|
|
274
258
|
initAuth();
|
|
275
259
|
}
|
|
276
|
-
}, [storage, oxyServices, keys
|
|
260
|
+
}, [storage, oxyServices, keys, onAuthStateChange]);
|
|
277
261
|
|
|
278
|
-
// Effect to restore token on app load or session switch
|
|
262
|
+
// Effect to restore token on app load or session switch
|
|
279
263
|
useEffect(() => {
|
|
280
264
|
const restoreToken = async () => {
|
|
281
|
-
if (activeSessionId && oxyServices
|
|
282
|
-
tokenRestoreRef.current = true;
|
|
265
|
+
if (activeSessionId && oxyServices) {
|
|
283
266
|
try {
|
|
284
267
|
await oxyServices.getTokenBySession(activeSessionId);
|
|
285
268
|
setTokenReady(true);
|
|
@@ -288,16 +271,13 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
288
271
|
await logout();
|
|
289
272
|
setTokenReady(false);
|
|
290
273
|
}
|
|
291
|
-
} else
|
|
274
|
+
} else {
|
|
292
275
|
setTokenReady(true); // No session, so token is not needed
|
|
293
276
|
}
|
|
294
277
|
};
|
|
295
|
-
|
|
296
|
-
// Only run
|
|
297
|
-
|
|
298
|
-
restoreToken();
|
|
299
|
-
}
|
|
300
|
-
}, [activeSessionId, oxyServices, tokenReady]); // Added tokenReady to prevent re-runs
|
|
278
|
+
restoreToken();
|
|
279
|
+
// Only run when activeSessionId or oxyServices changes
|
|
280
|
+
}, [activeSessionId, oxyServices]);
|
|
301
281
|
|
|
302
282
|
// Remove invalid session
|
|
303
283
|
const removeInvalidSession = useCallback(async (sessionId: string): Promise<void> => {
|
|
@@ -13,10 +13,9 @@ interface UseSessionSocketProps {
|
|
|
13
13
|
|
|
14
14
|
export function useSessionSocket({ userId, activeSessionId, refreshSessions, logout, baseURL, onRemoteSignOut }: UseSessionSocketProps) {
|
|
15
15
|
const socketRef = useRef<any>(null);
|
|
16
|
-
const connectedRef = useRef(false);
|
|
17
16
|
|
|
18
17
|
useEffect(() => {
|
|
19
|
-
if (!userId || !baseURL
|
|
18
|
+
if (!userId || !baseURL) return;
|
|
20
19
|
|
|
21
20
|
if (!socketRef.current) {
|
|
22
21
|
socketRef.current = io(baseURL, {
|
|
@@ -27,7 +26,6 @@ export function useSessionSocket({ userId, activeSessionId, refreshSessions, log
|
|
|
27
26
|
|
|
28
27
|
socket.on('connect', () => {
|
|
29
28
|
console.log('Socket connected:', socket.id);
|
|
30
|
-
connectedRef.current = true;
|
|
31
29
|
});
|
|
32
30
|
|
|
33
31
|
socket.emit('join', { userId: `user:${userId}` });
|
|
@@ -45,7 +43,6 @@ export function useSessionSocket({ userId, activeSessionId, refreshSessions, log
|
|
|
45
43
|
});
|
|
46
44
|
|
|
47
45
|
return () => {
|
|
48
|
-
connectedRef.current = false;
|
|
49
46
|
socket.emit('leave', { userId: `user:${userId}` });
|
|
50
47
|
socket.off('session_update');
|
|
51
48
|
};
|
package/src/ui/index.ts
CHANGED
|
@@ -9,6 +9,9 @@ import isFrontend from './isFrontend';
|
|
|
9
9
|
// Real UI exports
|
|
10
10
|
let OxyProvider, OxySignInButton, OxyLogo, Avatar, FollowButton, OxyPayButton, FontLoader, setupFonts, OxyIcon, useOxy, useOxyAuth, useOxyUser, useOxyKarma, useOxyPayments, useOxyDevices, useOxyNotifications, useOxySocket, useOxyQR, useOxyIAP, OxyContextProvider, OxyContextState, OxyContextProviderProps, useFollow, ProfileScreen, OxyRouter, useAuthStore, fontFamilies, fontStyles, toast;
|
|
11
11
|
|
|
12
|
+
// Zero-config exports
|
|
13
|
+
let OxyZeroConfigProvider, useOxyZeroConfig, useOxyApi;
|
|
14
|
+
|
|
12
15
|
if (isFrontend) {
|
|
13
16
|
OxyProvider = require('./components/OxyProvider').default;
|
|
14
17
|
OxySignInButton = require('./components/OxySignInButton').default;
|
|
@@ -30,6 +33,11 @@ if (isFrontend) {
|
|
|
30
33
|
fontFamilies = require('./styles/fonts').fontFamilies;
|
|
31
34
|
fontStyles = require('./styles/fonts').fontStyles;
|
|
32
35
|
toast = require('../lib/sonner').toast;
|
|
36
|
+
|
|
37
|
+
// Zero-config components
|
|
38
|
+
OxyZeroConfigProvider = require('./zero-config').OxyZeroConfigProvider;
|
|
39
|
+
useOxyZeroConfig = require('./zero-config').useOxyZeroConfig;
|
|
40
|
+
useOxyApi = require('./zero-config').useOxyApi;
|
|
33
41
|
} else {
|
|
34
42
|
// Backend: no-op fallbacks
|
|
35
43
|
const noopComponent = () => null;
|
|
@@ -54,6 +62,11 @@ if (isFrontend) {
|
|
|
54
62
|
fontFamilies = {};
|
|
55
63
|
fontStyles = {};
|
|
56
64
|
toast = () => {};
|
|
65
|
+
|
|
66
|
+
// Zero-config no-ops
|
|
67
|
+
OxyZeroConfigProvider = noopComponent;
|
|
68
|
+
useOxyZeroConfig = noopHook;
|
|
69
|
+
useOxyApi = () => ({});
|
|
57
70
|
}
|
|
58
71
|
|
|
59
72
|
export {
|
|
@@ -76,7 +89,12 @@ export {
|
|
|
76
89
|
useAuthStore,
|
|
77
90
|
fontFamilies,
|
|
78
91
|
fontStyles,
|
|
79
|
-
toast
|
|
92
|
+
toast,
|
|
93
|
+
|
|
94
|
+
// Zero-config exports
|
|
95
|
+
OxyZeroConfigProvider,
|
|
96
|
+
useOxyZeroConfig,
|
|
97
|
+
useOxyApi
|
|
80
98
|
};
|
|
81
99
|
|
|
82
100
|
// Re-export core services for convenience in UI context
|