yapid-js 1.0.0

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 ADDED
@@ -0,0 +1,202 @@
1
+ # yapid-js
2
+
3
+ Official JavaScript SDK for [YapID](https://id.yaphub.xyz) — Anonymous persistent identity for the web.
4
+
5
+ No email. No password. No tracking. 12 words. That's it.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/yapid-js)](https://npmjs.com/package/yapid-js)
8
+ [![Privacy Score](https://img.shields.io/badge/Privacy%20Score-93%25-7c3aed)](https://id.yaphub.xyz)
9
+ [![License](https://img.shields.io/badge/License-BSL%201.1-amber)](https://github.com/yaphub/yapid/blob/main/LICENSE)
10
+
11
+ ---
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install yapid-js
17
+ ```
18
+
19
+ ---
20
+
21
+ ## Quick Start
22
+
23
+ ### Frontend (Browser)
24
+
25
+ ```html
26
+ <!-- Option 1: Script Tag (no npm needed) -->
27
+ <script src="https://id.yaphub.xyz/yapid-button.js"></script>
28
+ <yapid-button onlogin="onLogin"></yapid-button>
29
+
30
+ <script>
31
+ function onLogin(session) {
32
+ console.log('Logged in:', session.accountId);
33
+ }
34
+ </script>
35
+ ```
36
+
37
+ ```js
38
+ // Option 2: npm
39
+ import { YapID } from 'yapid-js';
40
+
41
+ const client = new YapID();
42
+
43
+ // Redirect to YapID login
44
+ client.login('https://your-site.com/callback');
45
+
46
+ // After redirect back — get token from URL
47
+ const { token, state } = client.getTokenFromUrl();
48
+ if (client.verifyState(state)) {
49
+ const session = await client.verify(token);
50
+ console.log(session.accountId);
51
+ }
52
+ ```
53
+
54
+ ### Backend (Node.js)
55
+
56
+ ```js
57
+ import { verifyToken, yapidMiddleware } from 'yapid-js/server';
58
+
59
+ // Single verify
60
+ const session = await verifyToken(req.headers.authorization?.replace('Bearer ', ''));
61
+ if (session.valid) {
62
+ console.log(session.accountId, session.isPremium);
63
+ }
64
+
65
+ // Express middleware
66
+ import express from 'express';
67
+ const app = express();
68
+
69
+ app.use('/api', yapidMiddleware({ required: true }));
70
+
71
+ app.get('/api/profile', (req, res) => {
72
+ res.json({ user: req.yapid });
73
+ });
74
+ ```
75
+
76
+ ### React
77
+
78
+ ```jsx
79
+ import { useYapID, YapIDProvider, YapIDButton } from 'yapid-js/react';
80
+
81
+ // Wrap your app
82
+ function App() {
83
+ return (
84
+ <YapIDProvider>
85
+ <MyApp />
86
+ </YapIDProvider>
87
+ );
88
+ }
89
+
90
+ // Use the hook
91
+ function Profile() {
92
+ const { isLoggedIn, accountId, isPremium, login, logout } = useYapID();
93
+
94
+ if (!isLoggedIn) {
95
+ return <button onClick={login}>Sign in with YapID</button>;
96
+ }
97
+
98
+ return (
99
+ <div>
100
+ <p>Welcome, {accountId}</p>
101
+ {isPremium && <p>★ Premium</p>}
102
+ <button onClick={logout}>Logout</button>
103
+ </div>
104
+ );
105
+ }
106
+
107
+ // Or just use the pre-built button
108
+ function Header() {
109
+ return (
110
+ <YapIDButton
111
+ theme="dark"
112
+ size="medium"
113
+ onLogin={(session) => console.log(session)}
114
+ />
115
+ );
116
+ }
117
+ ```
118
+
119
+ ---
120
+
121
+ ## API Reference
122
+
123
+ ### Client (`yapid-js`)
124
+
125
+ | Method | Description |
126
+ |---|---|
127
+ | `verify(token)` | Verify an access token |
128
+ | `login(redirectUrl)` | Redirect to YapID login |
129
+ | `getLoginUrl(redirectUrl)` | Get login URL without redirecting |
130
+ | `getTokenFromUrl()` | Extract token from URL hash after redirect |
131
+ | `verifyState(state)` | Verify CSRF state |
132
+ | `getUserInfo(token)` | Get user info (OIDC) |
133
+ | `refresh(refreshToken)` | Refresh access token |
134
+ | `logout(token)` | Logout current session |
135
+ | `logoutAll(token)` | Logout from all devices |
136
+ | `status()` | Check service status |
137
+
138
+ ### Server (`yapid-js/server`)
139
+
140
+ | Export | Description |
141
+ |---|---|
142
+ | `verifyToken(token)` | Verify token server-side |
143
+ | `yapidMiddleware(options)` | Express.js middleware |
144
+ | `YapIDServer` | Server SDK class |
145
+
146
+ ### React (`yapid-js/react`)
147
+
148
+ | Export | Description |
149
+ |---|---|
150
+ | `useYapID()` | React Hook |
151
+ | `YapIDProvider` | Context Provider |
152
+ | `YapIDButton` | Pre-built React Button |
153
+
154
+ ---
155
+
156
+ ## Session Object
157
+
158
+ ```ts
159
+ {
160
+ valid: boolean,
161
+ sub: string, // accountId (UUID)
162
+ accountId: string, // alias for sub
163
+ yapid_premium: boolean,
164
+ isPremium: boolean, // alias
165
+ yapid_avatar: string,
166
+ yapid_name: string | null,
167
+ scope: string, // 'openid profile'
168
+ expires_in: number, // seconds
169
+ profile: {
170
+ displayName: string | null,
171
+ avatarSeed: string,
172
+ }
173
+ }
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Privacy
179
+
180
+ YapID has a **93% privacy score** — higher than any commercial alternative:
181
+
182
+ | Service | Privacy Score |
183
+ |---|---|
184
+ | **YapID** | **93%** |
185
+ | Wallet Connect | 67% |
186
+ | Auth0 / Clerk | 41% |
187
+ | Sign in with Google | 12% |
188
+
189
+ - ✓ No email required
190
+ - ✓ No IP address stored
191
+ - ✓ AES-256-GCM session encryption
192
+ - ✓ Ed25519 cryptographic signatures
193
+ - ✓ SHA-256 wallet hash (server never sees public key)
194
+ - ✓ No third-party tracking
195
+
196
+ ---
197
+
198
+ ## License
199
+
200
+ BSL 1.1 — Source available, commercial competing use restricted for 4 years. Converts to Apache 2.0 in 2030.
201
+
202
+ [Full License](https://github.com/yaphub/yapid/blob/main/LICENSE)
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "yapid-js",
3
+ "version": "1.0.0",
4
+ "description": "Official JavaScript SDK for YapID — Anonymous persistent identity for the web",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "module": "./src/index.js",
8
+ "exports": {
9
+ ".": "./src/index.js",
10
+ "./server": "./src/server.js",
11
+ "./react": "./src/react.js"
12
+ },
13
+ "files": [
14
+ "src/",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "keywords": [
19
+ "yapid",
20
+ "yap",
21
+ "anonymous",
22
+ "identity",
23
+ "auth",
24
+ "oauth",
25
+ "privacy",
26
+ "web3",
27
+ "bip39",
28
+ "ed25519"
29
+ ],
30
+ "author": "YAP <contact@cabalspy.xyz>",
31
+ "license": "BSL-1.1",
32
+ "homepage": "https://id.yaphub.xyz",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/yaphub/yapid-js"
36
+ },
37
+ "bugs": {
38
+ "url": "https://github.com/yaphub/yapid-js/issues"
39
+ },
40
+ "peerDependencies": {
41
+ "react": ">=17.0.0"
42
+ },
43
+ "peerDependenciesMeta": {
44
+ "react": {
45
+ "optional": true
46
+ }
47
+ },
48
+ "engines": {
49
+ "node": ">=18.0.0"
50
+ }
51
+ }
package/src/index.js ADDED
@@ -0,0 +1,199 @@
1
+ /**
2
+ * yapid-js
3
+ * =========
4
+ * Official JavaScript/TypeScript SDK for YapID
5
+ * Anonymous persistent identity for the web.
6
+ *
7
+ * npm install yapid-js
8
+ *
9
+ * Usage (Browser):
10
+ * import { YapID } from 'yapid-js';
11
+ * const yapid = new YapID();
12
+ * const session = await yapid.verify(token);
13
+ *
14
+ * Usage (Node.js / Backend):
15
+ * import { verifyToken } from 'yapid-js/server';
16
+ * const result = await verifyToken(token);
17
+ */
18
+
19
+ const YAPID_ENDPOINT = 'https://id.yaphub.xyz';
20
+
21
+ // ── Types ─────────────────────────────────────────────────────
22
+ /**
23
+ * @typedef {Object} YapIDSession
24
+ * @property {boolean} valid
25
+ * @property {string} sub - accountId (UUID)
26
+ * @property {string} accountId - Alias für sub (Rückwärtskompatibel)
27
+ * @property {boolean} yapid_premium
28
+ * @property {boolean} isPremium - Alias für yapid_premium
29
+ * @property {string} yapid_avatar
30
+ * @property {string|null} yapid_name
31
+ * @property {string} scope
32
+ * @property {number} expires_in
33
+ * @property {Object} profile
34
+ * @property {string|null} profile.displayName
35
+ * @property {string} profile.avatarSeed
36
+ */
37
+
38
+ // ── Client SDK ────────────────────────────────────────────────
39
+ export class YapID {
40
+ constructor(options = {}) {
41
+ this.endpoint = options.endpoint || YAPID_ENDPOINT;
42
+ }
43
+
44
+ /**
45
+ * Token verifizieren
46
+ * @param {string} token - Access Token
47
+ * @returns {Promise<YapIDSession>}
48
+ */
49
+ async verify(token) {
50
+ const res = await fetch(`${this.endpoint}/api/verify`, {
51
+ method: 'POST',
52
+ headers: { 'Content-Type': 'application/json' },
53
+ body: JSON.stringify({ access_token: token, token }),
54
+ });
55
+ if (!res.ok) throw new Error(`YapID verify failed: ${res.status}`);
56
+ return res.json();
57
+ }
58
+
59
+ /**
60
+ * Login-URL generieren (OAuth Redirect)
61
+ * @param {string} redirectUrl - URL zu der nach Login zurückgeleitet wird
62
+ * @param {Object} options
63
+ * @param {string} options.scope - Standard: 'openid profile'
64
+ * @param {string} options.state - CSRF State (optional, wird auto-generiert)
65
+ * @returns {{ url: string, state: string }}
66
+ */
67
+ getLoginUrl(redirectUrl, options = {}) {
68
+ const state = options.state || this._generateState();
69
+ const url = new URL(`${this.endpoint}/login`);
70
+ url.searchParams.set('redirect', redirectUrl);
71
+ url.searchParams.set('state', state);
72
+ url.searchParams.set('scope', options.scope || 'openid profile');
73
+ return { url: url.toString(), state };
74
+ }
75
+
76
+ /**
77
+ * Redirect zu YapID Login
78
+ * @param {string} redirectUrl
79
+ * @param {Object} options
80
+ */
81
+ login(redirectUrl, options = {}) {
82
+ if (typeof window === 'undefined') throw new Error('login() only works in browser');
83
+ const { url, state } = this.getLoginUrl(redirectUrl || window.location.href, options);
84
+ sessionStorage.setItem('yapid_state', state);
85
+ window.location.href = url;
86
+ }
87
+
88
+ /**
89
+ * Token aus URL Hash nach Redirect extrahieren
90
+ * @returns {{ token: string|null, refreshToken: string|null, state: string|null }}
91
+ */
92
+ getTokenFromUrl() {
93
+ if (typeof window === 'undefined') return { token: null, refreshToken: null, state: null };
94
+ const hash = window.location.hash;
95
+ if (!hash.includes('yapid_token=')) return { token: null, refreshToken: null, state: null };
96
+ const params = new URLSearchParams(hash.slice(1));
97
+ return {
98
+ token: params.get('yapid_token'),
99
+ refreshToken: params.get('yapid_refresh'),
100
+ state: params.get('state'),
101
+ };
102
+ }
103
+
104
+ /**
105
+ * State verifizieren (CSRF Schutz)
106
+ * @param {string} returnedState
107
+ * @returns {boolean}
108
+ */
109
+ verifyState(returnedState) {
110
+ if (typeof window === 'undefined') return true;
111
+ const saved = sessionStorage.getItem('yapid_state');
112
+ sessionStorage.removeItem('yapid_state');
113
+ if (!saved || !returnedState) return true; // kein State = optional
114
+ return saved === returnedState;
115
+ }
116
+
117
+ /**
118
+ * UserInfo abrufen
119
+ * @param {string} token
120
+ * @returns {Promise<Object>}
121
+ */
122
+ async getUserInfo(token) {
123
+ const res = await fetch(`${this.endpoint}/api/userinfo`, {
124
+ headers: { Authorization: `Bearer ${token}` },
125
+ });
126
+ if (!res.ok) throw new Error(`YapID userinfo failed: ${res.status}`);
127
+ return res.json();
128
+ }
129
+
130
+ /**
131
+ * Token refreshen
132
+ * @param {string} refreshToken
133
+ * @returns {Promise<Object>}
134
+ */
135
+ async refresh(refreshToken) {
136
+ const res = await fetch(`${this.endpoint}/auth/refresh`, {
137
+ method: 'POST',
138
+ headers: { 'Content-Type': 'application/json' },
139
+ body: JSON.stringify({ refresh_token: refreshToken }),
140
+ });
141
+ if (!res.ok) throw new Error(`YapID refresh failed: ${res.status}`);
142
+ return res.json();
143
+ }
144
+
145
+ /**
146
+ * Logout (diese Session)
147
+ * @param {string} token
148
+ */
149
+ async logout(token) {
150
+ await fetch(`${this.endpoint}/auth/logout`, {
151
+ method: 'POST',
152
+ headers: { 'Content-Type': 'application/json' },
153
+ body: JSON.stringify({ access_token: token }),
154
+ }).catch(() => {});
155
+ }
156
+
157
+ /**
158
+ * Logout von allen Geräten
159
+ * @param {string} token
160
+ */
161
+ async logoutAll(token) {
162
+ const res = await fetch(`${this.endpoint}/auth/logout-all`, {
163
+ method: 'POST',
164
+ headers: { 'Content-Type': 'application/json' },
165
+ body: JSON.stringify({ access_token: token }),
166
+ });
167
+ if (!res.ok) throw new Error(`YapID logout-all failed: ${res.status}`);
168
+ return res.json();
169
+ }
170
+
171
+ /**
172
+ * Service Status prüfen
173
+ * @returns {Promise<Object>}
174
+ */
175
+ async status() {
176
+ const res = await fetch(`${this.endpoint}/api/status`);
177
+ return res.json();
178
+ }
179
+
180
+ _generateState() {
181
+ if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
182
+ return Array.from(crypto.getRandomValues(new Uint8Array(16)))
183
+ .map(b => b.toString(16).padStart(2, '0')).join('');
184
+ }
185
+ return Math.random().toString(36).slice(2) + Date.now().toString(36);
186
+ }
187
+ }
188
+
189
+ // ── Convenience Exports ───────────────────────────────────────
190
+ export const yapid = new YapID();
191
+
192
+ /**
193
+ * Quick verify — für einfache Nutzung
194
+ * @param {string} token
195
+ * @returns {Promise<YapIDSession>}
196
+ */
197
+ export const verifyToken = (token) => yapid.verify(token);
198
+
199
+ export default YapID;
package/src/react.js ADDED
@@ -0,0 +1,258 @@
1
+ /**
2
+ * yapid-js/react
3
+ * ===============
4
+ * React Hook und Komponenten für YapID
5
+ *
6
+ * import { useYapID, YapIDProvider, YapIDButton } from 'yapid-js/react';
7
+ */
8
+
9
+ import { useState, useEffect, useCallback, createContext, useContext } from 'react';
10
+ import { YapID } from './index.js';
11
+
12
+ const YAPID_ENDPOINT = 'https://id.yaphub.xyz';
13
+
14
+ // ── Context ───────────────────────────────────────────────────
15
+ const YapIDContext = createContext(null);
16
+
17
+ /**
18
+ * YapID Provider — wraps your app
19
+ *
20
+ * @example
21
+ * function App() {
22
+ * return (
23
+ * <YapIDProvider>
24
+ * <YourApp />
25
+ * </YapIDProvider>
26
+ * );
27
+ * }
28
+ */
29
+ export function YapIDProvider({ children, options = {} }) {
30
+ const value = useYapIDInternal(options);
31
+ return React.createElement(YapIDContext.Provider, { value }, children);
32
+ }
33
+
34
+ // ── Internal Hook ─────────────────────────────────────────────
35
+ function useYapIDInternal(options = {}) {
36
+ const [session, setSession] = useState(null);
37
+ const [loading, setLoading] = useState(true);
38
+ const [error, setError] = useState(null);
39
+
40
+ const client = new YapID(options);
41
+
42
+ // Redirect-Return prüfen und Token verifizieren
43
+ useEffect(() => {
44
+ async function init() {
45
+ try {
46
+ const { token, refreshToken, state } = client.getTokenFromUrl();
47
+
48
+ if (token) {
49
+ // State verifizieren
50
+ if (!client.verifyState(state)) {
51
+ setError('State mismatch — possible CSRF attack');
52
+ setLoading(false);
53
+ return;
54
+ }
55
+
56
+ // URL Hash entfernen
57
+ window.history.replaceState(null, '', window.location.pathname + window.location.search);
58
+
59
+ const data = await client.verify(token);
60
+ if (data.valid) {
61
+ setSession({ ...data, token, refreshToken });
62
+ }
63
+ }
64
+ } catch (e) {
65
+ setError(e.message);
66
+ } finally {
67
+ setLoading(false);
68
+ }
69
+ }
70
+
71
+ init();
72
+ }, []);
73
+
74
+ /**
75
+ * Login — redirectet zu YapID
76
+ * @param {string} redirectUrl - Standard: aktuelle URL
77
+ */
78
+ const login = useCallback((redirectUrl) => {
79
+ client.login(redirectUrl || window.location.href);
80
+ }, []);
81
+
82
+ /**
83
+ * Token verifizieren
84
+ * @param {string} token
85
+ */
86
+ const verify = useCallback(async (token) => {
87
+ try {
88
+ const data = await client.verify(token);
89
+ if (data.valid) setSession({ ...data, token });
90
+ return data;
91
+ } catch (e) {
92
+ setError(e.message);
93
+ return { valid: false };
94
+ }
95
+ }, []);
96
+
97
+ /**
98
+ * Logout
99
+ */
100
+ const logout = useCallback(async () => {
101
+ if (session?.token) {
102
+ await client.logout(session.token);
103
+ }
104
+ setSession(null);
105
+ }, [session]);
106
+
107
+ /**
108
+ * Logout von allen Geräten
109
+ */
110
+ const logoutAll = useCallback(async () => {
111
+ if (session?.token) {
112
+ await client.logoutAll(session.token);
113
+ }
114
+ setSession(null);
115
+ }, [session]);
116
+
117
+ return {
118
+ session,
119
+ loading,
120
+ error,
121
+ isLoggedIn: !!session?.valid,
122
+ accountId: session?.sub || session?.accountId || null,
123
+ isPremium: session?.yapid_premium || session?.isPremium || false,
124
+ profile: session?.profile || null,
125
+ token: session?.token || null,
126
+ login,
127
+ verify,
128
+ logout,
129
+ logoutAll,
130
+ };
131
+ }
132
+
133
+ /**
134
+ * useYapID Hook
135
+ *
136
+ * @example
137
+ * function MyComponent() {
138
+ * const { isLoggedIn, accountId, isPremium, login, logout } = useYapID();
139
+ *
140
+ * if (!isLoggedIn) {
141
+ * return <button onClick={login}>Sign in with YapID</button>;
142
+ * }
143
+ *
144
+ * return (
145
+ * <div>
146
+ * <p>Welcome, {accountId}</p>
147
+ * {isPremium && <p>★ Premium</p>}
148
+ * <button onClick={logout}>Logout</button>
149
+ * </div>
150
+ * );
151
+ * }
152
+ */
153
+ export function useYapID(options = {}) {
154
+ const context = useContext(YapIDContext);
155
+ // Wenn kein Provider — standalone Hook
156
+ if (!context) return useYapIDInternal(options);
157
+ return context;
158
+ }
159
+
160
+ /**
161
+ * YapIDButton React Komponente
162
+ *
163
+ * @example
164
+ * <YapIDButton
165
+ * theme="dark"
166
+ * size="medium"
167
+ * text="Sign in with YapID"
168
+ * onLogin={(session) => console.log(session)}
169
+ * />
170
+ */
171
+ export function YapIDButton({ theme = 'dark', size = 'medium', text = 'Sign in with YapID', onLogin, className, style }) {
172
+ const { isLoggedIn, accountId, isPremium, profile, login, logout } = useYapID();
173
+ const [showPanel, setShowPanel] = useState(false);
174
+
175
+ const sizes = {
176
+ small: { padding: '6px 14px', fontSize: '11px', height: '32px' },
177
+ medium: { padding: '9px 20px', fontSize: '12px', height: '40px' },
178
+ large: { padding: '12px 28px', fontSize: '14px', height: '48px' },
179
+ };
180
+
181
+ const themes = {
182
+ dark: { background: '#111', color: '#f0f0f0', border: '1px solid #2a2a2a' },
183
+ light: { background: '#fff', color: '#111', border: '1px solid #e0e0e0' },
184
+ purple: { background: '#7c3aed', color: '#fff', border: '1px solid #6d28d9' },
185
+ };
186
+
187
+ const baseStyle = {
188
+ display: 'inline-flex',
189
+ alignItems: 'center',
190
+ gap: '8px',
191
+ fontFamily: "'Space Mono', monospace",
192
+ fontWeight: 700,
193
+ cursor: 'pointer',
194
+ letterSpacing: '.04em',
195
+ borderRadius: '8px',
196
+ transition: 'all .2s',
197
+ whiteSpace: 'nowrap',
198
+ ...sizes[size] || sizes.medium,
199
+ ...themes[theme] || themes.dark,
200
+ ...style,
201
+ };
202
+
203
+ const handleClick = () => {
204
+ if (isLoggedIn) setShowPanel(p => !p);
205
+ else login();
206
+ };
207
+
208
+ useEffect(() => {
209
+ if (!isLoggedIn || !onLogin) return;
210
+ // onLogin nur nach Redirect-Return aufrufen — wird in useYapID gehandhabt
211
+ }, [isLoggedIn]);
212
+
213
+ const label = isLoggedIn
214
+ ? (profile?.displayName || (accountId || '').slice(0, 8) + '…')
215
+ : text;
216
+
217
+ return React.createElement('div', { style: { position: 'relative', display: 'inline-block' } },
218
+ React.createElement('button', { onClick: handleClick, style: baseStyle, className },
219
+ React.createElement('img', {
220
+ src: theme === 'light'
221
+ ? `${YAPID_ENDPOINT}/yaphub-dark.png`
222
+ : `${YAPID_ENDPOINT}/yaphub.png`,
223
+ alt: 'YapID',
224
+ style: { width: '14px', height: '14px', objectFit: 'contain' },
225
+ }),
226
+ label,
227
+ isLoggedIn && React.createElement('span', {
228
+ style: {
229
+ width: '7px', height: '7px', borderRadius: '50%',
230
+ background: '#22c55e', boxShadow: '0 0 6px #22c55e',
231
+ }
232
+ })
233
+ ),
234
+ showPanel && isLoggedIn && React.createElement('div', {
235
+ style: {
236
+ position: 'absolute', top: 'calc(100% + 8px)', left: 0,
237
+ background: '#111', border: '1px solid #2a2a2a', borderRadius: '10px',
238
+ padding: '16px', zIndex: 9999, minWidth: '220px',
239
+ fontFamily: "'Space Mono', monospace", fontSize: '11px', color: '#888',
240
+ boxShadow: '0 8px 32px rgba(0,0,0,.7)',
241
+ }
242
+ },
243
+ React.createElement('div', { style: { color: '#a78bfa', fontWeight: 700, marginBottom: '6px', fontSize: '12px' } },
244
+ 'YapID',
245
+ isPremium && React.createElement('span', {
246
+ style: { fontSize: '9px', color: '#f59e0b', border: '1px solid rgba(245,158,11,.3)', borderRadius: '8px', padding: '1px 6px', marginLeft: '6px' }
247
+ }, '★ Premium')
248
+ ),
249
+ React.createElement('div', { style: { fontSize: '9px', color: '#444', wordBreak: 'break-all', marginBottom: '14px', lineHeight: 1.7 } }, accountId),
250
+ React.createElement('button', {
251
+ onClick: () => { logout(); setShowPanel(false); },
252
+ style: { width: '100%', padding: '7px', background: 'none', border: '1px solid #2a2a2a', borderRadius: '6px', color: '#666', fontFamily: 'inherit', fontSize: '10px', cursor: 'pointer' }
253
+ }, 'Logout')
254
+ )
255
+ );
256
+ }
257
+
258
+ export default useYapID;
package/src/server.js ADDED
@@ -0,0 +1,97 @@
1
+ /**
2
+ * yapid-js/server
3
+ * ================
4
+ * Node.js Backend SDK für YapID
5
+ * Für serverseitige Token-Verifikation
6
+ *
7
+ * import { verifyToken, YapIDServer } from 'yapid-js/server';
8
+ */
9
+
10
+ const YAPID_ENDPOINT = 'https://id.yaphub.xyz';
11
+
12
+ /**
13
+ * Token serverseitig verifizieren
14
+ * @param {string} token - Access Token aus dem Request
15
+ * @returns {Promise<import('./index.js').YapIDSession>}
16
+ *
17
+ * @example
18
+ * // Express.js Middleware
19
+ * app.use(async (req, res, next) => {
20
+ * const token = req.headers.authorization?.replace('Bearer ', '');
21
+ * const session = await verifyToken(token);
22
+ * if (!session.valid) return res.status(401).json({ error: 'Unauthorized' });
23
+ * req.user = session;
24
+ * next();
25
+ * });
26
+ */
27
+ export async function verifyToken(token, options = {}) {
28
+ if (!token) return { valid: false };
29
+ const endpoint = options.endpoint || YAPID_ENDPOINT;
30
+
31
+ try {
32
+ const res = await fetch(`${endpoint}/api/verify`, {
33
+ method: 'POST',
34
+ headers: { 'Content-Type': 'application/json' },
35
+ body: JSON.stringify({ access_token: token, token }),
36
+ });
37
+ if (!res.ok) return { valid: false };
38
+ return res.json();
39
+ } catch {
40
+ return { valid: false };
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Express.js Middleware
46
+ * @param {Object} options
47
+ * @param {boolean} options.required - Wenn true, gibt 401 zurück wenn kein Token
48
+ * @returns {Function} Express Middleware
49
+ *
50
+ * @example
51
+ * import { yapidMiddleware } from 'yapid-js/server';
52
+ * app.use(yapidMiddleware());
53
+ * app.get('/protected', (req, res) => {
54
+ * res.json({ user: req.yapid });
55
+ * });
56
+ */
57
+ export function yapidMiddleware(options = {}) {
58
+ return async (req, res, next) => {
59
+ const token = req.headers.authorization?.replace('Bearer ', '')
60
+ || req.query?.yapid_token
61
+ || req.body?.yapid_token;
62
+
63
+ if (!token) {
64
+ if (options.required) {
65
+ return res.status(401).json({ error: 'YapID token required' });
66
+ }
67
+ req.yapid = null;
68
+ return next();
69
+ }
70
+
71
+ const session = await verifyToken(token, options);
72
+ if (!session.valid) {
73
+ if (options.required) {
74
+ return res.status(401).json({ error: 'Invalid YapID token' });
75
+ }
76
+ req.yapid = null;
77
+ return next();
78
+ }
79
+
80
+ req.yapid = session;
81
+ next();
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Server SDK Klasse
87
+ */
88
+ export class YapIDServer {
89
+ constructor(options = {}) {
90
+ this.endpoint = options.endpoint || YAPID_ENDPOINT;
91
+ }
92
+
93
+ async verify(token) { return verifyToken(token, { endpoint: this.endpoint }); }
94
+ middleware(options = {}) { return yapidMiddleware({ ...options, endpoint: this.endpoint }); }
95
+ }
96
+
97
+ export default YapIDServer;