@hyve-sdk/js 1.0.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/LICENSE +21 -0
- package/README.md +187 -0
- package/dist/index.d.mts +161 -0
- package/dist/index.d.ts +161 -0
- package/dist/index.js +353 -0
- package/dist/index.mjs +319 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Hyve
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# @hyve/sdk
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for web3 authentication and game integration, providing secure signature verification and utility functions.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @hyve/sdk
|
|
9
|
+
# or
|
|
10
|
+
npm install @hyve/sdk
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @hyve/sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- **Web3 Authentication**: EVM signature validation and verification
|
|
18
|
+
- **Modern & Legacy Token Support**: Dual authentication token formats
|
|
19
|
+
- **Security Utilities**: Domain validation and referrer checking
|
|
20
|
+
- **URL Parameter Parsing**: Easy extraction of authentication parameters
|
|
21
|
+
- **UUID Generation**: Built-in UUID v4 generation
|
|
22
|
+
|
|
23
|
+
## Core Components
|
|
24
|
+
|
|
25
|
+
### HyveClient
|
|
26
|
+
Main client class for SDK operations (currently minimal implementation).
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { HyveClient } from "@hyve/sdk";
|
|
30
|
+
|
|
31
|
+
const client = new HyveClient();
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Authentication Utilities
|
|
35
|
+
|
|
36
|
+
#### Parse URL Parameters
|
|
37
|
+
Extract authentication and game parameters from URL:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { parseUrlParams } from "@hyve/sdk";
|
|
41
|
+
|
|
42
|
+
const params = parseUrlParams(window.location.search);
|
|
43
|
+
// Returns:
|
|
44
|
+
// {
|
|
45
|
+
// signature: string,
|
|
46
|
+
// message: string,
|
|
47
|
+
// userId: string,
|
|
48
|
+
// gameStartTab: string,
|
|
49
|
+
// hyveToken: string,
|
|
50
|
+
// platform: string
|
|
51
|
+
// }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
#### Verify Authentication
|
|
55
|
+
Unified verification supporting both modern and legacy tokens:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { verifyAuthentication } from "@hyve/sdk";
|
|
59
|
+
|
|
60
|
+
const result = verifyAuthentication({
|
|
61
|
+
hyveToken: params.hyveToken, // Modern token format
|
|
62
|
+
signature: params.signature, // Legacy format
|
|
63
|
+
message: params.message // Legacy format
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (result.isValid) {
|
|
67
|
+
console.log('Authenticated address:', result.address);
|
|
68
|
+
console.log('Auth method used:', result.method); // 'modern' or 'legacy'
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### Modern Token Verification (Hyve Token)
|
|
73
|
+
Verify modern authentication tokens with expiration:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { verifyHyveToken } from "@hyve/sdk";
|
|
77
|
+
|
|
78
|
+
// Token format: signature.address.randomBase64.timestamp
|
|
79
|
+
const address = verifyHyveToken(hyveToken, 600); // 600 seconds max age
|
|
80
|
+
if (address) {
|
|
81
|
+
console.log('Valid token for address:', address);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### Legacy Token Verification
|
|
86
|
+
Verify legacy signed messages with metadata:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { handleVerifyMessage, validateSignature } from "@hyve/sdk";
|
|
90
|
+
|
|
91
|
+
// Method 1: Verify message with embedded metadata
|
|
92
|
+
const address = handleVerifyMessage(signature, message);
|
|
93
|
+
|
|
94
|
+
// Method 2: Simple signature validation
|
|
95
|
+
const isValid = validateSignature(signature, message, userId);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Security Utilities
|
|
99
|
+
|
|
100
|
+
#### Domain Validation
|
|
101
|
+
Check if current domain is allowed:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { isDomainAllowed } from "@hyve/sdk";
|
|
105
|
+
|
|
106
|
+
// Single domain
|
|
107
|
+
const allowed = isDomainAllowed('example.com', window.location.hostname);
|
|
108
|
+
|
|
109
|
+
// Multiple domains with wildcard support
|
|
110
|
+
const allowed = isDomainAllowed(
|
|
111
|
+
['example.com', '*.subdomain.com'],
|
|
112
|
+
window.location.hostname
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// Note: localhost is always allowed
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Utility Functions
|
|
119
|
+
|
|
120
|
+
#### UUID Generation
|
|
121
|
+
Generate random UUID v4:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { generateUUID } from "@hyve/sdk";
|
|
125
|
+
|
|
126
|
+
const id = generateUUID();
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Authentication Flow
|
|
130
|
+
|
|
131
|
+
### Modern Token Flow (Recommended)
|
|
132
|
+
1. User authenticates on platform
|
|
133
|
+
2. Platform generates hyve-token: `signature.address.random.timestamp`
|
|
134
|
+
3. Token passed via URL parameter `hyve-token`
|
|
135
|
+
4. SDK verifies token with `verifyHyveToken()` or `verifyAuthentication()`
|
|
136
|
+
|
|
137
|
+
### Legacy Token Flow
|
|
138
|
+
1. User signs message containing metadata (expiration, address)
|
|
139
|
+
2. Signature and message passed via URL parameters
|
|
140
|
+
3. SDK verifies with `handleVerifyMessage()` or `verifyAuthentication()`
|
|
141
|
+
|
|
142
|
+
## Security Considerations
|
|
143
|
+
|
|
144
|
+
- **Token Expiration**: Modern tokens expire after 10 minutes by default
|
|
145
|
+
- **Domain Validation**: Always validate origin domains in production
|
|
146
|
+
- **Signature Verification**: All signatures verified using ethers.js
|
|
147
|
+
- **Localhost Exception**: localhost always allowed for development
|
|
148
|
+
|
|
149
|
+
## TypeScript Support
|
|
150
|
+
|
|
151
|
+
Full TypeScript support with exported types for all utilities and return values.
|
|
152
|
+
|
|
153
|
+
## Dependencies
|
|
154
|
+
|
|
155
|
+
- **ethers**: ^6.13.7 - Ethereum signature verification
|
|
156
|
+
- **uuid**: (peer dependency) - UUID generation
|
|
157
|
+
|
|
158
|
+
## Build Configuration
|
|
159
|
+
|
|
160
|
+
The SDK builds to multiple formats:
|
|
161
|
+
- CommonJS (`dist/index.js`)
|
|
162
|
+
- ES Modules (`dist/index.mjs`)
|
|
163
|
+
- TypeScript declarations (`dist/index.d.ts`)
|
|
164
|
+
|
|
165
|
+
## Development
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# Install dependencies
|
|
169
|
+
bun install
|
|
170
|
+
|
|
171
|
+
# Type checking
|
|
172
|
+
bun run check-types
|
|
173
|
+
|
|
174
|
+
# Linting
|
|
175
|
+
bun run lint
|
|
176
|
+
|
|
177
|
+
# Build
|
|
178
|
+
bun run build
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Documentation
|
|
182
|
+
|
|
183
|
+
For complete documentation and examples, visit [https://docs.hyve.gg](https://docs.hyve.gg)
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telemetry configuration options
|
|
3
|
+
*/
|
|
4
|
+
interface TelemetryConfig {
|
|
5
|
+
/** API key for telemetry service */
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
/** Environment: true for dev, false for prod. Defaults to true (dev) */
|
|
8
|
+
isDev?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Telemetry event data structure
|
|
12
|
+
*/
|
|
13
|
+
interface TelemetryEvent {
|
|
14
|
+
/** Unique session identifier */
|
|
15
|
+
session_id: string;
|
|
16
|
+
/** Hyve user identifier (address) */
|
|
17
|
+
hyve_user_id: string;
|
|
18
|
+
/** Location where the event occurred */
|
|
19
|
+
event_location: string;
|
|
20
|
+
/** Main category of the event */
|
|
21
|
+
event_category: string;
|
|
22
|
+
/** Primary action taken */
|
|
23
|
+
event_action: string;
|
|
24
|
+
/** Sub-category for more granular classification (null if not provided) */
|
|
25
|
+
event_sub_category: string | null;
|
|
26
|
+
/** Sub-action for detailed tracking (null if not provided) */
|
|
27
|
+
event_sub_action: string | null;
|
|
28
|
+
/** Additional data as JSON string (null if not provided) */
|
|
29
|
+
event_details: string | null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Additional telemetry data that can be passed with events
|
|
33
|
+
*/
|
|
34
|
+
interface TelemetryAdditionalData {
|
|
35
|
+
/** Optional additional data object (will be JSON stringified and put in event_details) */
|
|
36
|
+
[key: string]: any;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* HyveClient provides telemetry and authentication functionality for Hyve games
|
|
41
|
+
*/
|
|
42
|
+
declare class HyveClient {
|
|
43
|
+
private telemetryConfig;
|
|
44
|
+
private telemetryApiUrl;
|
|
45
|
+
private sessionId;
|
|
46
|
+
private userId;
|
|
47
|
+
/**
|
|
48
|
+
* Creates a new HyveClient instance
|
|
49
|
+
* @param config Optional telemetry configuration
|
|
50
|
+
*/
|
|
51
|
+
constructor(config?: TelemetryConfig);
|
|
52
|
+
/**
|
|
53
|
+
* Authenticates a user from URL parameters
|
|
54
|
+
* @param urlParams URL parameters or search string
|
|
55
|
+
* @returns Promise resolving to boolean indicating success
|
|
56
|
+
*/
|
|
57
|
+
authenticateFromUrl(urlParams?: URLSearchParams | string): Promise<boolean>;
|
|
58
|
+
/**
|
|
59
|
+
* Sends a telemetry event
|
|
60
|
+
* @param eventLocation Location where the event occurred
|
|
61
|
+
* @param eventCategory Main category of the event
|
|
62
|
+
* @param eventAction Primary action taken
|
|
63
|
+
* @param eventSubCategory Optional sub-category
|
|
64
|
+
* @param eventSubAction Optional sub-action
|
|
65
|
+
* @param eventDetails Optional event details
|
|
66
|
+
* @param additionalData Optional additional data
|
|
67
|
+
* @returns Promise resolving to boolean indicating success
|
|
68
|
+
*/
|
|
69
|
+
sendTelemetry(eventLocation: string, eventCategory: string, eventAction: string, eventSubCategory?: string | null, eventSubAction?: string | null, eventDetails?: string | null, additionalData?: TelemetryAdditionalData | null): Promise<boolean>;
|
|
70
|
+
/**
|
|
71
|
+
* Updates the telemetry configuration
|
|
72
|
+
* @param config New telemetry configuration
|
|
73
|
+
*/
|
|
74
|
+
updateTelemetryConfig(config: TelemetryConfig): void;
|
|
75
|
+
/**
|
|
76
|
+
* Gets the current user ID
|
|
77
|
+
* @returns Current user ID or null if not authenticated
|
|
78
|
+
*/
|
|
79
|
+
getUserId(): string | null;
|
|
80
|
+
/**
|
|
81
|
+
* Gets the current session ID
|
|
82
|
+
* @returns Current session ID
|
|
83
|
+
*/
|
|
84
|
+
getSessionId(): string;
|
|
85
|
+
/**
|
|
86
|
+
* Checks if user is authenticated
|
|
87
|
+
* @returns Boolean indicating authentication status
|
|
88
|
+
*/
|
|
89
|
+
isUserAuthenticated(): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Logs out the current user
|
|
92
|
+
*/
|
|
93
|
+
logout(): void;
|
|
94
|
+
/**
|
|
95
|
+
* Resets the client state
|
|
96
|
+
*/
|
|
97
|
+
reset(): void;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Parses URL search parameters and returns commonly used values
|
|
102
|
+
* @param searchParams URLSearchParams object or string
|
|
103
|
+
* @returns Object containing parsed parameters with default values
|
|
104
|
+
*/
|
|
105
|
+
declare function parseUrlParams(searchParams: URLSearchParams | string): {
|
|
106
|
+
signature: string;
|
|
107
|
+
message: string;
|
|
108
|
+
gameStartTab: string;
|
|
109
|
+
hyveToken: string;
|
|
110
|
+
platform: string;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Validates an EVM signature against a message and user ID
|
|
114
|
+
* @param signature The signature to validate
|
|
115
|
+
* @param message The original message that was signed
|
|
116
|
+
* @returns Promise resolving to boolean indicating if signature is valid
|
|
117
|
+
*/
|
|
118
|
+
declare function validateSignature(signature: string, message: string): boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Verifies a signed message containing metadata with expiration and address (LEGACY)
|
|
121
|
+
* @param signature The signature to verify
|
|
122
|
+
* @param message The encoded message containing metadata (JSON)
|
|
123
|
+
* @returns The address that signed the message if valid, false otherwise
|
|
124
|
+
*/
|
|
125
|
+
declare function handleVerifyMessage(signature: string, message: string): string | false;
|
|
126
|
+
/**
|
|
127
|
+
* Verifies a modern hyve-token
|
|
128
|
+
* @param hyveToken The modern token in format: signature.address.randomBase64.timestamp
|
|
129
|
+
* @param maxAgeSec Maximum age in seconds (default: 600 = 10 minutes)
|
|
130
|
+
* @returns The verified address if valid, false otherwise
|
|
131
|
+
*/
|
|
132
|
+
declare function verifyHyveToken(hyveToken: string, maxAgeSec?: number): string | false;
|
|
133
|
+
/**
|
|
134
|
+
* Unified authentication verification - tries modern token first, then falls back to legacy
|
|
135
|
+
* @param params Object containing hyveToken, signature, message from URL params
|
|
136
|
+
* @param maxAgeSec Maximum age for modern tokens in seconds (default: 600)
|
|
137
|
+
* @returns Object with verification result and method used
|
|
138
|
+
*/
|
|
139
|
+
declare function verifyAuthentication(params: {
|
|
140
|
+
hyveToken?: string;
|
|
141
|
+
signature?: string;
|
|
142
|
+
message?: string;
|
|
143
|
+
}, maxAgeSec?: number): {
|
|
144
|
+
isValid: boolean;
|
|
145
|
+
address: string | null;
|
|
146
|
+
method: "modern" | "legacy" | "none";
|
|
147
|
+
error?: string;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Generates a random UUID (v4) using the uuid library
|
|
151
|
+
* @returns A string containing a randomly generated UUID
|
|
152
|
+
*/
|
|
153
|
+
declare function generateUUID(): string;
|
|
154
|
+
/**
|
|
155
|
+
* Checks if the current domain is in the list of allowed domains
|
|
156
|
+
* @param allowedDomains Array of allowed domains or a single domain string
|
|
157
|
+
* @returns Boolean indicating if the current domain is allowed
|
|
158
|
+
*/
|
|
159
|
+
declare function isDomainAllowed(allowedDomains?: string | string[], hostname?: string): boolean;
|
|
160
|
+
|
|
161
|
+
export { HyveClient, type TelemetryAdditionalData, type TelemetryConfig, type TelemetryEvent, generateUUID, handleVerifyMessage, isDomainAllowed, parseUrlParams, validateSignature, verifyAuthentication, verifyHyveToken };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telemetry configuration options
|
|
3
|
+
*/
|
|
4
|
+
interface TelemetryConfig {
|
|
5
|
+
/** API key for telemetry service */
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
/** Environment: true for dev, false for prod. Defaults to true (dev) */
|
|
8
|
+
isDev?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Telemetry event data structure
|
|
12
|
+
*/
|
|
13
|
+
interface TelemetryEvent {
|
|
14
|
+
/** Unique session identifier */
|
|
15
|
+
session_id: string;
|
|
16
|
+
/** Hyve user identifier (address) */
|
|
17
|
+
hyve_user_id: string;
|
|
18
|
+
/** Location where the event occurred */
|
|
19
|
+
event_location: string;
|
|
20
|
+
/** Main category of the event */
|
|
21
|
+
event_category: string;
|
|
22
|
+
/** Primary action taken */
|
|
23
|
+
event_action: string;
|
|
24
|
+
/** Sub-category for more granular classification (null if not provided) */
|
|
25
|
+
event_sub_category: string | null;
|
|
26
|
+
/** Sub-action for detailed tracking (null if not provided) */
|
|
27
|
+
event_sub_action: string | null;
|
|
28
|
+
/** Additional data as JSON string (null if not provided) */
|
|
29
|
+
event_details: string | null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Additional telemetry data that can be passed with events
|
|
33
|
+
*/
|
|
34
|
+
interface TelemetryAdditionalData {
|
|
35
|
+
/** Optional additional data object (will be JSON stringified and put in event_details) */
|
|
36
|
+
[key: string]: any;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* HyveClient provides telemetry and authentication functionality for Hyve games
|
|
41
|
+
*/
|
|
42
|
+
declare class HyveClient {
|
|
43
|
+
private telemetryConfig;
|
|
44
|
+
private telemetryApiUrl;
|
|
45
|
+
private sessionId;
|
|
46
|
+
private userId;
|
|
47
|
+
/**
|
|
48
|
+
* Creates a new HyveClient instance
|
|
49
|
+
* @param config Optional telemetry configuration
|
|
50
|
+
*/
|
|
51
|
+
constructor(config?: TelemetryConfig);
|
|
52
|
+
/**
|
|
53
|
+
* Authenticates a user from URL parameters
|
|
54
|
+
* @param urlParams URL parameters or search string
|
|
55
|
+
* @returns Promise resolving to boolean indicating success
|
|
56
|
+
*/
|
|
57
|
+
authenticateFromUrl(urlParams?: URLSearchParams | string): Promise<boolean>;
|
|
58
|
+
/**
|
|
59
|
+
* Sends a telemetry event
|
|
60
|
+
* @param eventLocation Location where the event occurred
|
|
61
|
+
* @param eventCategory Main category of the event
|
|
62
|
+
* @param eventAction Primary action taken
|
|
63
|
+
* @param eventSubCategory Optional sub-category
|
|
64
|
+
* @param eventSubAction Optional sub-action
|
|
65
|
+
* @param eventDetails Optional event details
|
|
66
|
+
* @param additionalData Optional additional data
|
|
67
|
+
* @returns Promise resolving to boolean indicating success
|
|
68
|
+
*/
|
|
69
|
+
sendTelemetry(eventLocation: string, eventCategory: string, eventAction: string, eventSubCategory?: string | null, eventSubAction?: string | null, eventDetails?: string | null, additionalData?: TelemetryAdditionalData | null): Promise<boolean>;
|
|
70
|
+
/**
|
|
71
|
+
* Updates the telemetry configuration
|
|
72
|
+
* @param config New telemetry configuration
|
|
73
|
+
*/
|
|
74
|
+
updateTelemetryConfig(config: TelemetryConfig): void;
|
|
75
|
+
/**
|
|
76
|
+
* Gets the current user ID
|
|
77
|
+
* @returns Current user ID or null if not authenticated
|
|
78
|
+
*/
|
|
79
|
+
getUserId(): string | null;
|
|
80
|
+
/**
|
|
81
|
+
* Gets the current session ID
|
|
82
|
+
* @returns Current session ID
|
|
83
|
+
*/
|
|
84
|
+
getSessionId(): string;
|
|
85
|
+
/**
|
|
86
|
+
* Checks if user is authenticated
|
|
87
|
+
* @returns Boolean indicating authentication status
|
|
88
|
+
*/
|
|
89
|
+
isUserAuthenticated(): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Logs out the current user
|
|
92
|
+
*/
|
|
93
|
+
logout(): void;
|
|
94
|
+
/**
|
|
95
|
+
* Resets the client state
|
|
96
|
+
*/
|
|
97
|
+
reset(): void;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Parses URL search parameters and returns commonly used values
|
|
102
|
+
* @param searchParams URLSearchParams object or string
|
|
103
|
+
* @returns Object containing parsed parameters with default values
|
|
104
|
+
*/
|
|
105
|
+
declare function parseUrlParams(searchParams: URLSearchParams | string): {
|
|
106
|
+
signature: string;
|
|
107
|
+
message: string;
|
|
108
|
+
gameStartTab: string;
|
|
109
|
+
hyveToken: string;
|
|
110
|
+
platform: string;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Validates an EVM signature against a message and user ID
|
|
114
|
+
* @param signature The signature to validate
|
|
115
|
+
* @param message The original message that was signed
|
|
116
|
+
* @returns Promise resolving to boolean indicating if signature is valid
|
|
117
|
+
*/
|
|
118
|
+
declare function validateSignature(signature: string, message: string): boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Verifies a signed message containing metadata with expiration and address (LEGACY)
|
|
121
|
+
* @param signature The signature to verify
|
|
122
|
+
* @param message The encoded message containing metadata (JSON)
|
|
123
|
+
* @returns The address that signed the message if valid, false otherwise
|
|
124
|
+
*/
|
|
125
|
+
declare function handleVerifyMessage(signature: string, message: string): string | false;
|
|
126
|
+
/**
|
|
127
|
+
* Verifies a modern hyve-token
|
|
128
|
+
* @param hyveToken The modern token in format: signature.address.randomBase64.timestamp
|
|
129
|
+
* @param maxAgeSec Maximum age in seconds (default: 600 = 10 minutes)
|
|
130
|
+
* @returns The verified address if valid, false otherwise
|
|
131
|
+
*/
|
|
132
|
+
declare function verifyHyveToken(hyveToken: string, maxAgeSec?: number): string | false;
|
|
133
|
+
/**
|
|
134
|
+
* Unified authentication verification - tries modern token first, then falls back to legacy
|
|
135
|
+
* @param params Object containing hyveToken, signature, message from URL params
|
|
136
|
+
* @param maxAgeSec Maximum age for modern tokens in seconds (default: 600)
|
|
137
|
+
* @returns Object with verification result and method used
|
|
138
|
+
*/
|
|
139
|
+
declare function verifyAuthentication(params: {
|
|
140
|
+
hyveToken?: string;
|
|
141
|
+
signature?: string;
|
|
142
|
+
message?: string;
|
|
143
|
+
}, maxAgeSec?: number): {
|
|
144
|
+
isValid: boolean;
|
|
145
|
+
address: string | null;
|
|
146
|
+
method: "modern" | "legacy" | "none";
|
|
147
|
+
error?: string;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Generates a random UUID (v4) using the uuid library
|
|
151
|
+
* @returns A string containing a randomly generated UUID
|
|
152
|
+
*/
|
|
153
|
+
declare function generateUUID(): string;
|
|
154
|
+
/**
|
|
155
|
+
* Checks if the current domain is in the list of allowed domains
|
|
156
|
+
* @param allowedDomains Array of allowed domains or a single domain string
|
|
157
|
+
* @returns Boolean indicating if the current domain is allowed
|
|
158
|
+
*/
|
|
159
|
+
declare function isDomainAllowed(allowedDomains?: string | string[], hostname?: string): boolean;
|
|
160
|
+
|
|
161
|
+
export { HyveClient, type TelemetryAdditionalData, type TelemetryConfig, type TelemetryEvent, generateUUID, handleVerifyMessage, isDomainAllowed, parseUrlParams, validateSignature, verifyAuthentication, verifyHyveToken };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
HyveClient: () => HyveClient,
|
|
24
|
+
generateUUID: () => generateUUID,
|
|
25
|
+
handleVerifyMessage: () => handleVerifyMessage,
|
|
26
|
+
isDomainAllowed: () => isDomainAllowed,
|
|
27
|
+
parseUrlParams: () => parseUrlParams,
|
|
28
|
+
validateSignature: () => validateSignature,
|
|
29
|
+
verifyAuthentication: () => verifyAuthentication,
|
|
30
|
+
verifyHyveToken: () => verifyHyveToken
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(index_exports);
|
|
33
|
+
|
|
34
|
+
// src/utils/index.ts
|
|
35
|
+
var import_ethers = require("ethers");
|
|
36
|
+
var import_uuid = require("uuid");
|
|
37
|
+
function parseUrlParams(searchParams) {
|
|
38
|
+
const params = typeof searchParams === "string" ? new URLSearchParams(searchParams) : searchParams;
|
|
39
|
+
return {
|
|
40
|
+
signature: params.get("signature") || "",
|
|
41
|
+
message: params.get("message") || "",
|
|
42
|
+
gameStartTab: params.get("game_start_tab") || "",
|
|
43
|
+
hyveToken: params.get("hyve-token") || "",
|
|
44
|
+
platform: params.get("platform") || ""
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function validateSignature(signature, message) {
|
|
48
|
+
try {
|
|
49
|
+
const recoveredAddress = import_ethers.ethers.verifyMessage(message, signature);
|
|
50
|
+
return !!recoveredAddress;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error("Signature validation error:", error);
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function handleVerifyMessage(signature, message) {
|
|
57
|
+
try {
|
|
58
|
+
const metadata = JSON.parse(decodeURI(message));
|
|
59
|
+
if (!metadata.expiration || metadata.expiration < Date.now()) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const userAddress = metadata.userId || metadata.address;
|
|
63
|
+
if (!userAddress) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
const byteMessage = new TextEncoder().encode(message);
|
|
67
|
+
const addressThatSignedMessage = import_ethers.ethers.verifyMessage(
|
|
68
|
+
byteMessage,
|
|
69
|
+
signature
|
|
70
|
+
);
|
|
71
|
+
if (!addressThatSignedMessage) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
if (addressThatSignedMessage.toLowerCase() !== userAddress.toLowerCase()) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
return userAddress;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error("Error verifying message:", error);
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function verifyHyveToken(hyveToken, maxAgeSec = 600) {
|
|
84
|
+
try {
|
|
85
|
+
const parts = hyveToken.split(".");
|
|
86
|
+
if (parts.length !== 4) {
|
|
87
|
+
console.error(
|
|
88
|
+
"Invalid hyve-token format: expected 4 parts, got",
|
|
89
|
+
parts.length
|
|
90
|
+
);
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
const [signature, address, randomBase64, timestampStr] = parts;
|
|
94
|
+
if (!signature || !address || !randomBase64 || !timestampStr) {
|
|
95
|
+
console.error("Missing hyve-token components");
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const message = `${address}.${randomBase64}.${timestampStr}`;
|
|
99
|
+
const recoveredAddress = import_ethers.ethers.verifyMessage(message, signature);
|
|
100
|
+
if (recoveredAddress.toLowerCase() !== address.toLowerCase()) {
|
|
101
|
+
console.error("Hyve-token signature verification failed");
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
const timestamp = parseInt(timestampStr, 10);
|
|
105
|
+
if (!Number.isFinite(timestamp)) {
|
|
106
|
+
console.error("Invalid hyve-token timestamp");
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
110
|
+
if (now - timestamp > maxAgeSec) {
|
|
111
|
+
console.error(
|
|
112
|
+
`Hyve-token expired (age: ${now - timestamp}s, max: ${maxAgeSec}s)`
|
|
113
|
+
);
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
return address;
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error("Hyve-token verification error:", error);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function verifyAuthentication(params, maxAgeSec = 600) {
|
|
123
|
+
if (params.hyveToken) {
|
|
124
|
+
const modernAddress = verifyHyveToken(params.hyveToken, maxAgeSec);
|
|
125
|
+
if (modernAddress) {
|
|
126
|
+
return {
|
|
127
|
+
isValid: true,
|
|
128
|
+
address: modernAddress,
|
|
129
|
+
method: "modern"
|
|
130
|
+
};
|
|
131
|
+
} else {
|
|
132
|
+
return {
|
|
133
|
+
isValid: false,
|
|
134
|
+
address: null,
|
|
135
|
+
method: "modern",
|
|
136
|
+
error: "Modern token verification failed"
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (params.signature && params.message) {
|
|
141
|
+
const legacyAddress = handleVerifyMessage(params.signature, params.message);
|
|
142
|
+
if (legacyAddress) {
|
|
143
|
+
return {
|
|
144
|
+
isValid: true,
|
|
145
|
+
address: legacyAddress,
|
|
146
|
+
method: "legacy"
|
|
147
|
+
};
|
|
148
|
+
} else {
|
|
149
|
+
return {
|
|
150
|
+
isValid: false,
|
|
151
|
+
address: null,
|
|
152
|
+
method: "legacy",
|
|
153
|
+
error: "Legacy token verification failed"
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
isValid: false,
|
|
159
|
+
address: null,
|
|
160
|
+
method: "none",
|
|
161
|
+
error: "No authentication tokens provided"
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function generateUUID() {
|
|
165
|
+
return (0, import_uuid.v4)();
|
|
166
|
+
}
|
|
167
|
+
function isDomainAllowed(allowedDomains, hostname) {
|
|
168
|
+
console.log("Hostname", hostname);
|
|
169
|
+
if (!allowedDomains) return true;
|
|
170
|
+
const targetHostname = hostname || "";
|
|
171
|
+
if (!targetHostname) return false;
|
|
172
|
+
if (targetHostname === "localhost" || targetHostname.startsWith("localhost:")) {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
const domains = Array.isArray(allowedDomains) ? allowedDomains : [allowedDomains];
|
|
176
|
+
return domains.some((domain) => {
|
|
177
|
+
if (domain === targetHostname) return true;
|
|
178
|
+
if (domain.startsWith("*.")) {
|
|
179
|
+
const baseDomain = domain.substring(2);
|
|
180
|
+
return targetHostname.endsWith(baseDomain) && targetHostname.length > baseDomain.length && targetHostname.charAt(targetHostname.length - baseDomain.length - 1) === ".";
|
|
181
|
+
}
|
|
182
|
+
return false;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/core/client.ts
|
|
187
|
+
var HyveClient = class {
|
|
188
|
+
telemetryConfig;
|
|
189
|
+
telemetryApiUrl;
|
|
190
|
+
sessionId;
|
|
191
|
+
userId = null;
|
|
192
|
+
/**
|
|
193
|
+
* Creates a new HyveClient instance
|
|
194
|
+
* @param config Optional telemetry configuration
|
|
195
|
+
*/
|
|
196
|
+
constructor(config) {
|
|
197
|
+
this.telemetryConfig = {
|
|
198
|
+
isDev: true,
|
|
199
|
+
// Default to dev environment
|
|
200
|
+
...config
|
|
201
|
+
};
|
|
202
|
+
this.telemetryApiUrl = this.telemetryConfig.isDev ? "https://product-api.dev.hyve.gg/api/v1/partners/analytics/events" : "https://product-api.prod.hyve.gg/api/v1/partners/analytics/events";
|
|
203
|
+
this.sessionId = generateUUID();
|
|
204
|
+
console.log("[Hyve SDK] Client initialized with sessionId:", this.sessionId);
|
|
205
|
+
console.log("[Hyve SDK] Telemetry environment:", this.telemetryConfig.isDev ? "dev" : "prod");
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Authenticates a user from URL parameters
|
|
209
|
+
* @param urlParams URL parameters or search string
|
|
210
|
+
* @returns Promise resolving to boolean indicating success
|
|
211
|
+
*/
|
|
212
|
+
async authenticateFromUrl(urlParams) {
|
|
213
|
+
try {
|
|
214
|
+
const params = urlParams ? parseUrlParams(urlParams) : parseUrlParams(window.location.search);
|
|
215
|
+
const authResult = verifyAuthentication({
|
|
216
|
+
hyveToken: params.hyveToken,
|
|
217
|
+
signature: params.signature,
|
|
218
|
+
message: params.message
|
|
219
|
+
});
|
|
220
|
+
if (authResult.isValid && authResult.address) {
|
|
221
|
+
this.userId = authResult.address;
|
|
222
|
+
console.log("[Hyve SDK] Authentication successful:", authResult.address);
|
|
223
|
+
console.log("[Hyve SDK] Authentication method:", authResult.method);
|
|
224
|
+
return true;
|
|
225
|
+
} else {
|
|
226
|
+
console.error("[Hyve SDK] Authentication failed:", authResult.error);
|
|
227
|
+
this.userId = null;
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
} catch (error) {
|
|
231
|
+
console.error("[Hyve SDK] Authentication error:", error);
|
|
232
|
+
this.userId = null;
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Sends a telemetry event
|
|
238
|
+
* @param eventLocation Location where the event occurred
|
|
239
|
+
* @param eventCategory Main category of the event
|
|
240
|
+
* @param eventAction Primary action taken
|
|
241
|
+
* @param eventSubCategory Optional sub-category
|
|
242
|
+
* @param eventSubAction Optional sub-action
|
|
243
|
+
* @param eventDetails Optional event details
|
|
244
|
+
* @param additionalData Optional additional data
|
|
245
|
+
* @returns Promise resolving to boolean indicating success
|
|
246
|
+
*/
|
|
247
|
+
async sendTelemetry(eventLocation, eventCategory, eventAction, eventSubCategory, eventSubAction, eventDetails, additionalData) {
|
|
248
|
+
if (!this.telemetryConfig.apiKey) {
|
|
249
|
+
console.error("[Hyve Telemetry] API key not configured");
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
if (!this.userId) {
|
|
253
|
+
console.warn("[Hyve Telemetry] No user ID - sending anonymous telemetry");
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
let finalEventDetails = null;
|
|
257
|
+
if (additionalData && Object.keys(additionalData).length > 0) {
|
|
258
|
+
finalEventDetails = JSON.stringify(additionalData);
|
|
259
|
+
} else if (eventDetails) {
|
|
260
|
+
finalEventDetails = eventDetails;
|
|
261
|
+
}
|
|
262
|
+
const telemetryEvent = {
|
|
263
|
+
session_id: this.sessionId,
|
|
264
|
+
hyve_user_id: this.userId || "anonymous",
|
|
265
|
+
event_location: eventLocation,
|
|
266
|
+
event_category: eventCategory,
|
|
267
|
+
event_action: eventAction,
|
|
268
|
+
event_sub_category: eventSubCategory || null,
|
|
269
|
+
event_sub_action: eventSubAction || null,
|
|
270
|
+
event_details: finalEventDetails
|
|
271
|
+
};
|
|
272
|
+
console.log("[Hyve Telemetry] Sending event:", telemetryEvent);
|
|
273
|
+
const response = await fetch(this.telemetryApiUrl, {
|
|
274
|
+
method: "POST",
|
|
275
|
+
headers: {
|
|
276
|
+
"Content-Type": "application/json",
|
|
277
|
+
"X-API-KEY": this.telemetryConfig.apiKey
|
|
278
|
+
},
|
|
279
|
+
body: JSON.stringify(telemetryEvent)
|
|
280
|
+
});
|
|
281
|
+
if (response.ok) {
|
|
282
|
+
console.log("[Hyve Telemetry] Event sent successfully:", response.status);
|
|
283
|
+
return true;
|
|
284
|
+
} else {
|
|
285
|
+
const errorText = await response.text();
|
|
286
|
+
console.error("[Hyve Telemetry] Failed to send event:", response.status, errorText);
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
} catch (error) {
|
|
290
|
+
console.error("[Hyve Telemetry] Error sending event:", error);
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Updates the telemetry configuration
|
|
296
|
+
* @param config New telemetry configuration
|
|
297
|
+
*/
|
|
298
|
+
updateTelemetryConfig(config) {
|
|
299
|
+
this.telemetryConfig = {
|
|
300
|
+
...this.telemetryConfig,
|
|
301
|
+
...config
|
|
302
|
+
};
|
|
303
|
+
this.telemetryApiUrl = this.telemetryConfig.isDev ? "https://product-api.dev.hyve.gg/api/v1/partners/analytics/events" : "https://product-api.prod.hyve.gg/api/v1/partners/analytics/events";
|
|
304
|
+
console.log("[Hyve SDK] Telemetry config updated");
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Gets the current user ID
|
|
308
|
+
* @returns Current user ID or null if not authenticated
|
|
309
|
+
*/
|
|
310
|
+
getUserId() {
|
|
311
|
+
return this.userId;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Gets the current session ID
|
|
315
|
+
* @returns Current session ID
|
|
316
|
+
*/
|
|
317
|
+
getSessionId() {
|
|
318
|
+
return this.sessionId;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Checks if user is authenticated
|
|
322
|
+
* @returns Boolean indicating authentication status
|
|
323
|
+
*/
|
|
324
|
+
isUserAuthenticated() {
|
|
325
|
+
return this.userId !== null;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Logs out the current user
|
|
329
|
+
*/
|
|
330
|
+
logout() {
|
|
331
|
+
this.userId = null;
|
|
332
|
+
console.log("[Hyve SDK] User logged out");
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Resets the client state
|
|
336
|
+
*/
|
|
337
|
+
reset() {
|
|
338
|
+
this.logout();
|
|
339
|
+
this.sessionId = generateUUID();
|
|
340
|
+
console.log("[Hyve SDK] Client reset with new sessionId:", this.sessionId);
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
344
|
+
0 && (module.exports = {
|
|
345
|
+
HyveClient,
|
|
346
|
+
generateUUID,
|
|
347
|
+
handleVerifyMessage,
|
|
348
|
+
isDomainAllowed,
|
|
349
|
+
parseUrlParams,
|
|
350
|
+
validateSignature,
|
|
351
|
+
verifyAuthentication,
|
|
352
|
+
verifyHyveToken
|
|
353
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
// src/utils/index.ts
|
|
2
|
+
import { ethers } from "ethers";
|
|
3
|
+
import { v4 as uuidv4 } from "uuid";
|
|
4
|
+
function parseUrlParams(searchParams) {
|
|
5
|
+
const params = typeof searchParams === "string" ? new URLSearchParams(searchParams) : searchParams;
|
|
6
|
+
return {
|
|
7
|
+
signature: params.get("signature") || "",
|
|
8
|
+
message: params.get("message") || "",
|
|
9
|
+
gameStartTab: params.get("game_start_tab") || "",
|
|
10
|
+
hyveToken: params.get("hyve-token") || "",
|
|
11
|
+
platform: params.get("platform") || ""
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function validateSignature(signature, message) {
|
|
15
|
+
try {
|
|
16
|
+
const recoveredAddress = ethers.verifyMessage(message, signature);
|
|
17
|
+
return !!recoveredAddress;
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error("Signature validation error:", error);
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function handleVerifyMessage(signature, message) {
|
|
24
|
+
try {
|
|
25
|
+
const metadata = JSON.parse(decodeURI(message));
|
|
26
|
+
if (!metadata.expiration || metadata.expiration < Date.now()) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const userAddress = metadata.userId || metadata.address;
|
|
30
|
+
if (!userAddress) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const byteMessage = new TextEncoder().encode(message);
|
|
34
|
+
const addressThatSignedMessage = ethers.verifyMessage(
|
|
35
|
+
byteMessage,
|
|
36
|
+
signature
|
|
37
|
+
);
|
|
38
|
+
if (!addressThatSignedMessage) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
if (addressThatSignedMessage.toLowerCase() !== userAddress.toLowerCase()) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return userAddress;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error("Error verifying message:", error);
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function verifyHyveToken(hyveToken, maxAgeSec = 600) {
|
|
51
|
+
try {
|
|
52
|
+
const parts = hyveToken.split(".");
|
|
53
|
+
if (parts.length !== 4) {
|
|
54
|
+
console.error(
|
|
55
|
+
"Invalid hyve-token format: expected 4 parts, got",
|
|
56
|
+
parts.length
|
|
57
|
+
);
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const [signature, address, randomBase64, timestampStr] = parts;
|
|
61
|
+
if (!signature || !address || !randomBase64 || !timestampStr) {
|
|
62
|
+
console.error("Missing hyve-token components");
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
const message = `${address}.${randomBase64}.${timestampStr}`;
|
|
66
|
+
const recoveredAddress = ethers.verifyMessage(message, signature);
|
|
67
|
+
if (recoveredAddress.toLowerCase() !== address.toLowerCase()) {
|
|
68
|
+
console.error("Hyve-token signature verification failed");
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
const timestamp = parseInt(timestampStr, 10);
|
|
72
|
+
if (!Number.isFinite(timestamp)) {
|
|
73
|
+
console.error("Invalid hyve-token timestamp");
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
77
|
+
if (now - timestamp > maxAgeSec) {
|
|
78
|
+
console.error(
|
|
79
|
+
`Hyve-token expired (age: ${now - timestamp}s, max: ${maxAgeSec}s)`
|
|
80
|
+
);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return address;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error("Hyve-token verification error:", error);
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function verifyAuthentication(params, maxAgeSec = 600) {
|
|
90
|
+
if (params.hyveToken) {
|
|
91
|
+
const modernAddress = verifyHyveToken(params.hyveToken, maxAgeSec);
|
|
92
|
+
if (modernAddress) {
|
|
93
|
+
return {
|
|
94
|
+
isValid: true,
|
|
95
|
+
address: modernAddress,
|
|
96
|
+
method: "modern"
|
|
97
|
+
};
|
|
98
|
+
} else {
|
|
99
|
+
return {
|
|
100
|
+
isValid: false,
|
|
101
|
+
address: null,
|
|
102
|
+
method: "modern",
|
|
103
|
+
error: "Modern token verification failed"
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (params.signature && params.message) {
|
|
108
|
+
const legacyAddress = handleVerifyMessage(params.signature, params.message);
|
|
109
|
+
if (legacyAddress) {
|
|
110
|
+
return {
|
|
111
|
+
isValid: true,
|
|
112
|
+
address: legacyAddress,
|
|
113
|
+
method: "legacy"
|
|
114
|
+
};
|
|
115
|
+
} else {
|
|
116
|
+
return {
|
|
117
|
+
isValid: false,
|
|
118
|
+
address: null,
|
|
119
|
+
method: "legacy",
|
|
120
|
+
error: "Legacy token verification failed"
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
isValid: false,
|
|
126
|
+
address: null,
|
|
127
|
+
method: "none",
|
|
128
|
+
error: "No authentication tokens provided"
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function generateUUID() {
|
|
132
|
+
return uuidv4();
|
|
133
|
+
}
|
|
134
|
+
function isDomainAllowed(allowedDomains, hostname) {
|
|
135
|
+
console.log("Hostname", hostname);
|
|
136
|
+
if (!allowedDomains) return true;
|
|
137
|
+
const targetHostname = hostname || "";
|
|
138
|
+
if (!targetHostname) return false;
|
|
139
|
+
if (targetHostname === "localhost" || targetHostname.startsWith("localhost:")) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
const domains = Array.isArray(allowedDomains) ? allowedDomains : [allowedDomains];
|
|
143
|
+
return domains.some((domain) => {
|
|
144
|
+
if (domain === targetHostname) return true;
|
|
145
|
+
if (domain.startsWith("*.")) {
|
|
146
|
+
const baseDomain = domain.substring(2);
|
|
147
|
+
return targetHostname.endsWith(baseDomain) && targetHostname.length > baseDomain.length && targetHostname.charAt(targetHostname.length - baseDomain.length - 1) === ".";
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/core/client.ts
|
|
154
|
+
var HyveClient = class {
|
|
155
|
+
telemetryConfig;
|
|
156
|
+
telemetryApiUrl;
|
|
157
|
+
sessionId;
|
|
158
|
+
userId = null;
|
|
159
|
+
/**
|
|
160
|
+
* Creates a new HyveClient instance
|
|
161
|
+
* @param config Optional telemetry configuration
|
|
162
|
+
*/
|
|
163
|
+
constructor(config) {
|
|
164
|
+
this.telemetryConfig = {
|
|
165
|
+
isDev: true,
|
|
166
|
+
// Default to dev environment
|
|
167
|
+
...config
|
|
168
|
+
};
|
|
169
|
+
this.telemetryApiUrl = this.telemetryConfig.isDev ? "https://product-api.dev.hyve.gg/api/v1/partners/analytics/events" : "https://product-api.prod.hyve.gg/api/v1/partners/analytics/events";
|
|
170
|
+
this.sessionId = generateUUID();
|
|
171
|
+
console.log("[Hyve SDK] Client initialized with sessionId:", this.sessionId);
|
|
172
|
+
console.log("[Hyve SDK] Telemetry environment:", this.telemetryConfig.isDev ? "dev" : "prod");
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Authenticates a user from URL parameters
|
|
176
|
+
* @param urlParams URL parameters or search string
|
|
177
|
+
* @returns Promise resolving to boolean indicating success
|
|
178
|
+
*/
|
|
179
|
+
async authenticateFromUrl(urlParams) {
|
|
180
|
+
try {
|
|
181
|
+
const params = urlParams ? parseUrlParams(urlParams) : parseUrlParams(window.location.search);
|
|
182
|
+
const authResult = verifyAuthentication({
|
|
183
|
+
hyveToken: params.hyveToken,
|
|
184
|
+
signature: params.signature,
|
|
185
|
+
message: params.message
|
|
186
|
+
});
|
|
187
|
+
if (authResult.isValid && authResult.address) {
|
|
188
|
+
this.userId = authResult.address;
|
|
189
|
+
console.log("[Hyve SDK] Authentication successful:", authResult.address);
|
|
190
|
+
console.log("[Hyve SDK] Authentication method:", authResult.method);
|
|
191
|
+
return true;
|
|
192
|
+
} else {
|
|
193
|
+
console.error("[Hyve SDK] Authentication failed:", authResult.error);
|
|
194
|
+
this.userId = null;
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.error("[Hyve SDK] Authentication error:", error);
|
|
199
|
+
this.userId = null;
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Sends a telemetry event
|
|
205
|
+
* @param eventLocation Location where the event occurred
|
|
206
|
+
* @param eventCategory Main category of the event
|
|
207
|
+
* @param eventAction Primary action taken
|
|
208
|
+
* @param eventSubCategory Optional sub-category
|
|
209
|
+
* @param eventSubAction Optional sub-action
|
|
210
|
+
* @param eventDetails Optional event details
|
|
211
|
+
* @param additionalData Optional additional data
|
|
212
|
+
* @returns Promise resolving to boolean indicating success
|
|
213
|
+
*/
|
|
214
|
+
async sendTelemetry(eventLocation, eventCategory, eventAction, eventSubCategory, eventSubAction, eventDetails, additionalData) {
|
|
215
|
+
if (!this.telemetryConfig.apiKey) {
|
|
216
|
+
console.error("[Hyve Telemetry] API key not configured");
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
if (!this.userId) {
|
|
220
|
+
console.warn("[Hyve Telemetry] No user ID - sending anonymous telemetry");
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
let finalEventDetails = null;
|
|
224
|
+
if (additionalData && Object.keys(additionalData).length > 0) {
|
|
225
|
+
finalEventDetails = JSON.stringify(additionalData);
|
|
226
|
+
} else if (eventDetails) {
|
|
227
|
+
finalEventDetails = eventDetails;
|
|
228
|
+
}
|
|
229
|
+
const telemetryEvent = {
|
|
230
|
+
session_id: this.sessionId,
|
|
231
|
+
hyve_user_id: this.userId || "anonymous",
|
|
232
|
+
event_location: eventLocation,
|
|
233
|
+
event_category: eventCategory,
|
|
234
|
+
event_action: eventAction,
|
|
235
|
+
event_sub_category: eventSubCategory || null,
|
|
236
|
+
event_sub_action: eventSubAction || null,
|
|
237
|
+
event_details: finalEventDetails
|
|
238
|
+
};
|
|
239
|
+
console.log("[Hyve Telemetry] Sending event:", telemetryEvent);
|
|
240
|
+
const response = await fetch(this.telemetryApiUrl, {
|
|
241
|
+
method: "POST",
|
|
242
|
+
headers: {
|
|
243
|
+
"Content-Type": "application/json",
|
|
244
|
+
"X-API-KEY": this.telemetryConfig.apiKey
|
|
245
|
+
},
|
|
246
|
+
body: JSON.stringify(telemetryEvent)
|
|
247
|
+
});
|
|
248
|
+
if (response.ok) {
|
|
249
|
+
console.log("[Hyve Telemetry] Event sent successfully:", response.status);
|
|
250
|
+
return true;
|
|
251
|
+
} else {
|
|
252
|
+
const errorText = await response.text();
|
|
253
|
+
console.error("[Hyve Telemetry] Failed to send event:", response.status, errorText);
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.error("[Hyve Telemetry] Error sending event:", error);
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Updates the telemetry configuration
|
|
263
|
+
* @param config New telemetry configuration
|
|
264
|
+
*/
|
|
265
|
+
updateTelemetryConfig(config) {
|
|
266
|
+
this.telemetryConfig = {
|
|
267
|
+
...this.telemetryConfig,
|
|
268
|
+
...config
|
|
269
|
+
};
|
|
270
|
+
this.telemetryApiUrl = this.telemetryConfig.isDev ? "https://product-api.dev.hyve.gg/api/v1/partners/analytics/events" : "https://product-api.prod.hyve.gg/api/v1/partners/analytics/events";
|
|
271
|
+
console.log("[Hyve SDK] Telemetry config updated");
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Gets the current user ID
|
|
275
|
+
* @returns Current user ID or null if not authenticated
|
|
276
|
+
*/
|
|
277
|
+
getUserId() {
|
|
278
|
+
return this.userId;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Gets the current session ID
|
|
282
|
+
* @returns Current session ID
|
|
283
|
+
*/
|
|
284
|
+
getSessionId() {
|
|
285
|
+
return this.sessionId;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Checks if user is authenticated
|
|
289
|
+
* @returns Boolean indicating authentication status
|
|
290
|
+
*/
|
|
291
|
+
isUserAuthenticated() {
|
|
292
|
+
return this.userId !== null;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Logs out the current user
|
|
296
|
+
*/
|
|
297
|
+
logout() {
|
|
298
|
+
this.userId = null;
|
|
299
|
+
console.log("[Hyve SDK] User logged out");
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Resets the client state
|
|
303
|
+
*/
|
|
304
|
+
reset() {
|
|
305
|
+
this.logout();
|
|
306
|
+
this.sessionId = generateUUID();
|
|
307
|
+
console.log("[Hyve SDK] Client reset with new sessionId:", this.sessionId);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
export {
|
|
311
|
+
HyveClient,
|
|
312
|
+
generateUUID,
|
|
313
|
+
handleVerifyMessage,
|
|
314
|
+
isDomainAllowed,
|
|
315
|
+
parseUrlParams,
|
|
316
|
+
validateSignature,
|
|
317
|
+
verifyAuthentication,
|
|
318
|
+
verifyHyveToken
|
|
319
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hyve-sdk/js",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Hyve SDK - TypeScript wrapper for Hyve game server integration",
|
|
5
|
+
"private": false,
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "restricted",
|
|
8
|
+
"registry": "https://registry.npmjs.org/"
|
|
9
|
+
},
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"module": "dist/index.mjs",
|
|
12
|
+
"types": "dist/index.d.ts",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"hyve",
|
|
20
|
+
"game",
|
|
21
|
+
"sdk",
|
|
22
|
+
"multiplayer",
|
|
23
|
+
"realtime",
|
|
24
|
+
"gaming",
|
|
25
|
+
"platform"
|
|
26
|
+
],
|
|
27
|
+
"author": "Hyve",
|
|
28
|
+
"homepage": "https://docs.hyve.gg",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/hyve/web-mono.git",
|
|
32
|
+
"directory": "packages/sdk"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/hyve/web-mono/issues"
|
|
36
|
+
},
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"ethers": "^6.13.7",
|
|
40
|
+
"uuid": "^10.0.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/minimatch": "^5.1.2",
|
|
44
|
+
"@types/uuid": "^10.0.0",
|
|
45
|
+
"tsup": "^8.4.0",
|
|
46
|
+
"typescript": "^5.3.3",
|
|
47
|
+
"@repo/eslint-config": "0.0.0",
|
|
48
|
+
"@repo/typescript-config": "0.0.0"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"lint": "eslint . --max-warnings 0",
|
|
52
|
+
"check-types": "tsc --noEmit",
|
|
53
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
54
|
+
"publish:npm": "pnpm publish --access restricted",
|
|
55
|
+
"publish:dry-run": "pnpm publish --dry-run --access restricted --no-git-checks"
|
|
56
|
+
}
|
|
57
|
+
}
|