@microcosmmoney/auth-core 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.
@@ -0,0 +1,9 @@
1
+ import { TokenManager } from './token-manager';
2
+ export declare class ApiClient {
3
+ private tokenManager;
4
+ constructor(tokenManager: TokenManager);
5
+ fetch<T>(path: string, options?: RequestInit): Promise<T>;
6
+ get<T>(path: string): Promise<T>;
7
+ post<T>(path: string, body: unknown): Promise<T>;
8
+ patch<T>(path: string, body: unknown): Promise<T>;
9
+ }
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApiClient = void 0;
4
+ const OPEN_API_BASE = 'https://api.microcosm.money/v1';
5
+ class ApiClient {
6
+ constructor(tokenManager) {
7
+ this.tokenManager = tokenManager;
8
+ }
9
+ async fetch(path, options = {}) {
10
+ const token = await this.tokenManager.getAccessToken();
11
+ const headers = {
12
+ 'Accept': 'application/json',
13
+ ...(options.headers || {}),
14
+ };
15
+ if (token) {
16
+ headers['Authorization'] = `Bearer ${token}`;
17
+ }
18
+ if (options.body && !headers['Content-Type']) {
19
+ headers['Content-Type'] = 'application/json';
20
+ }
21
+ const response = await fetch(`${OPEN_API_BASE}${path}`, {
22
+ ...options,
23
+ headers,
24
+ });
25
+ if (response.status === 429) {
26
+ const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
27
+ await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
28
+ return this.fetch(path, options);
29
+ }
30
+ const contentType = response.headers.get('content-type');
31
+ if (!contentType?.includes('application/json')) {
32
+ const text = await response.text();
33
+ throw new Error(`Non-JSON response from API: ${text.substring(0, 200)}`);
34
+ }
35
+ const data = await response.json();
36
+ if (!response.ok) {
37
+ throw new Error(data.detail || data.error?.message || data.message || `API error ${response.status}`);
38
+ }
39
+ return data;
40
+ }
41
+ async get(path) {
42
+ return this.fetch(path);
43
+ }
44
+ async post(path, body) {
45
+ return this.fetch(path, {
46
+ method: 'POST',
47
+ body: JSON.stringify(body),
48
+ });
49
+ }
50
+ async patch(path, body) {
51
+ return this.fetch(path, {
52
+ method: 'PATCH',
53
+ body: JSON.stringify(body),
54
+ });
55
+ }
56
+ }
57
+ exports.ApiClient = ApiClient;
@@ -0,0 +1,26 @@
1
+ import { MicrocosmAuthConfig, User, LoginOptions } from './types';
2
+ import { ApiClient } from './api-client';
3
+ export declare class MicrocosmAuthClient {
4
+ private config;
5
+ private storage;
6
+ private tokenManager;
7
+ private apiClient;
8
+ private listeners;
9
+ constructor(config: MicrocosmAuthConfig);
10
+ login(options?: LoginOptions): void;
11
+ handleCallback(): Promise<User>;
12
+ logout(): Promise<void>;
13
+ getUser(): User | null;
14
+ setUser(user: User): void;
15
+ isAuthenticated(): boolean;
16
+ getAccessToken(): Promise<string | null>;
17
+ getApiClient(): ApiClient;
18
+ onAuthStateChange(callback: (user: User | null) => void): () => void;
19
+ private fetchUserProfile;
20
+ private mapProfileToUser;
21
+ private resolveRedirectUri;
22
+ private generateState;
23
+ private safeJson;
24
+ private notifyListeners;
25
+ private log;
26
+ }
package/dist/client.js ADDED
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MicrocosmAuthClient = void 0;
4
+ const token_manager_1 = require("./token-manager");
5
+ const storage_1 = require("./storage");
6
+ const api_client_1 = require("./api-client");
7
+ const DEFAULT_CONFIG = {
8
+ scope: ['openid', 'profile', 'email'],
9
+ authEndpoint: 'https://microcosm.money',
10
+ tokenExchangeUri: '/api/auth/exchange',
11
+ profileUri: '/api/users/profile',
12
+ storage: 'localStorage',
13
+ autoRefresh: true,
14
+ refreshBuffer: 300,
15
+ debug: false,
16
+ };
17
+ class MicrocosmAuthClient {
18
+ constructor(config) {
19
+ this.listeners = new Set();
20
+ this.config = { ...DEFAULT_CONFIG, ...config };
21
+ this.storage = new storage_1.Storage(this.config.storage);
22
+ this.tokenManager = new token_manager_1.TokenManager(this.config, this.storage);
23
+ this.apiClient = new api_client_1.ApiClient(this.tokenManager);
24
+ if (this.config.autoRefresh && this.tokenManager.hasValidToken()) {
25
+ this.tokenManager.scheduleRefresh();
26
+ }
27
+ this.log('Initialized', { clientId: this.config.clientId });
28
+ }
29
+ login(options) {
30
+ const state = this.generateState();
31
+ this.storage.set(storage_1.STORAGE_KEYS.OAUTH_STATE, state);
32
+ const redirectUri = this.resolveRedirectUri();
33
+ const params = new URLSearchParams({
34
+ oauth: 'true',
35
+ response_type: 'code',
36
+ client_id: this.config.clientId,
37
+ redirect_uri: redirectUri,
38
+ scope: this.config.scope.join(' '),
39
+ state: state,
40
+ });
41
+ if (options?.prompt) {
42
+ params.set('prompt', options.prompt);
43
+ }
44
+ const authUrl = `${this.config.authEndpoint}/login?${params.toString()}`;
45
+ this.log('Redirecting to:', authUrl);
46
+ if (typeof window !== 'undefined') {
47
+ window.location.href = authUrl;
48
+ }
49
+ }
50
+ async handleCallback() {
51
+ if (typeof window === 'undefined') {
52
+ throw new Error('handleCallback must be called in browser');
53
+ }
54
+ const params = new URLSearchParams(window.location.search);
55
+ const code = params.get('code');
56
+ const state = params.get('state');
57
+ const error = params.get('error');
58
+ if (error) {
59
+ throw new Error(params.get('error_description') || error);
60
+ }
61
+ if (!code) {
62
+ throw new Error('Missing authorization code');
63
+ }
64
+ const savedState = this.storage.get(storage_1.STORAGE_KEYS.OAUTH_STATE);
65
+ if (state && savedState && state !== savedState) {
66
+ throw new Error('Invalid state parameter (possible CSRF)');
67
+ }
68
+ this.storage.remove(storage_1.STORAGE_KEYS.OAUTH_STATE);
69
+ this.log('Exchanging code for tokens...');
70
+ const tokenResponse = await fetch(this.config.tokenExchangeUri, {
71
+ method: 'POST',
72
+ headers: { 'Content-Type': 'application/json' },
73
+ body: JSON.stringify({ code }),
74
+ });
75
+ if (!tokenResponse.ok) {
76
+ const errorData = await this.safeJson(tokenResponse);
77
+ throw new Error(errorData.error_description || errorData.error || 'Token exchange failed');
78
+ }
79
+ const tokenData = await tokenResponse.json();
80
+ const tokens = {
81
+ accessToken: tokenData.access_token,
82
+ refreshToken: tokenData.refresh_token,
83
+ expiresAt: Date.now() + (tokenData.expires_in || 3600) * 1000,
84
+ };
85
+ this.tokenManager.setTokens(tokens);
86
+ const user = await this.fetchUserProfile(tokens.accessToken);
87
+ this.storage.set(storage_1.STORAGE_KEYS.USER, JSON.stringify(user));
88
+ this.notifyListeners(user);
89
+ this.log('Login successful:', user.uid);
90
+ return user;
91
+ }
92
+ async logout() {
93
+ this.tokenManager.clear();
94
+ this.storage.remove(storage_1.STORAGE_KEYS.USER);
95
+ this.notifyListeners(null);
96
+ this.log('Logged out');
97
+ }
98
+ getUser() {
99
+ const userJson = this.storage.get(storage_1.STORAGE_KEYS.USER);
100
+ if (!userJson)
101
+ return null;
102
+ try {
103
+ return JSON.parse(userJson);
104
+ }
105
+ catch {
106
+ return null;
107
+ }
108
+ }
109
+ setUser(user) {
110
+ this.storage.set(storage_1.STORAGE_KEYS.USER, JSON.stringify(user));
111
+ this.notifyListeners(user);
112
+ }
113
+ isAuthenticated() {
114
+ return this.tokenManager.hasValidToken() && this.getUser() !== null;
115
+ }
116
+ async getAccessToken() {
117
+ return this.tokenManager.getAccessToken();
118
+ }
119
+ getApiClient() {
120
+ return this.apiClient;
121
+ }
122
+ onAuthStateChange(callback) {
123
+ this.listeners.add(callback);
124
+ return () => this.listeners.delete(callback);
125
+ }
126
+ async fetchUserProfile(accessToken) {
127
+ const response = await fetch(this.config.profileUri, {
128
+ headers: { Authorization: `Bearer ${accessToken}` },
129
+ });
130
+ if (!response.ok) {
131
+ this.log('Profile fetch failed, using minimal user');
132
+ return { uid: '', email: '', role: 'user' };
133
+ }
134
+ const data = await response.json();
135
+ const profile = data.user || data;
136
+ return this.mapProfileToUser(profile);
137
+ }
138
+ mapProfileToUser(profile) {
139
+ return {
140
+ uid: profile.uid,
141
+ email: profile.email || '',
142
+ displayName: profile.display_name || null,
143
+ avatarUrl: profile.avatar_url || null,
144
+ role: profile.role || 'user',
145
+ level: profile.level || undefined,
146
+ title: profile.title || null,
147
+ stationId: profile.station_id || null,
148
+ emailVerified: profile.email_verified,
149
+ };
150
+ }
151
+ resolveRedirectUri() {
152
+ const uri = this.config.redirectUri;
153
+ if (uri.startsWith('http'))
154
+ return uri;
155
+ if (typeof window !== 'undefined') {
156
+ return `${window.location.origin}${uri.startsWith('/') ? '' : '/'}${uri}`;
157
+ }
158
+ return uri;
159
+ }
160
+ generateState() {
161
+ if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
162
+ const array = new Uint8Array(32);
163
+ crypto.getRandomValues(array);
164
+ return Array.from(array, b => b.toString(16).padStart(2, '0')).join('');
165
+ }
166
+ return Math.random().toString(36).substring(2) + Date.now().toString(36);
167
+ }
168
+ async safeJson(response) {
169
+ const contentType = response.headers.get('content-type');
170
+ if (contentType?.includes('application/json')) {
171
+ return response.json();
172
+ }
173
+ const text = await response.text();
174
+ return { error: 'Non-JSON Response', message: text.substring(0, 200) };
175
+ }
176
+ notifyListeners(user) {
177
+ this.listeners.forEach(callback => {
178
+ try {
179
+ callback(user);
180
+ }
181
+ catch (e) {
182
+ console.error('[MicrocosmAuth] Listener error:', e);
183
+ }
184
+ });
185
+ }
186
+ log(...args) {
187
+ if (this.config.debug) {
188
+ console.log('[MicrocosmAuth]', ...args);
189
+ }
190
+ }
191
+ }
192
+ exports.MicrocosmAuthClient = MicrocosmAuthClient;
@@ -0,0 +1,7 @@
1
+ export { MicrocosmAuthClient } from './client';
2
+ export { TokenManager } from './token-manager';
3
+ export { Storage, STORAGE_KEYS } from './storage';
4
+ export { ApiClient } from './api-client';
5
+ export { MicrocosmAPI } from './open-api';
6
+ export type { MicrocosmAPIConfig } from './open-api';
7
+ export type { MicrocosmAuthConfig, ResolvedConfig, User, TokenData, AuthState, LoginOptions, TokenExchangeResponse, UserProfileResponse, ApiResponse, MCDBalance, MCCBalance, MCCPrice, } from './types';
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MicrocosmAPI = exports.ApiClient = exports.STORAGE_KEYS = exports.Storage = exports.TokenManager = exports.MicrocosmAuthClient = void 0;
4
+ // AI-generated · AI-managed · AI-maintained
5
+ var client_1 = require("./client");
6
+ Object.defineProperty(exports, "MicrocosmAuthClient", { enumerable: true, get: function () { return client_1.MicrocosmAuthClient; } });
7
+ var token_manager_1 = require("./token-manager");
8
+ Object.defineProperty(exports, "TokenManager", { enumerable: true, get: function () { return token_manager_1.TokenManager; } });
9
+ var storage_1 = require("./storage");
10
+ Object.defineProperty(exports, "Storage", { enumerable: true, get: function () { return storage_1.Storage; } });
11
+ Object.defineProperty(exports, "STORAGE_KEYS", { enumerable: true, get: function () { return storage_1.STORAGE_KEYS; } });
12
+ var api_client_1 = require("./api-client");
13
+ Object.defineProperty(exports, "ApiClient", { enumerable: true, get: function () { return api_client_1.ApiClient; } });
14
+ var open_api_1 = require("./open-api");
15
+ Object.defineProperty(exports, "MicrocosmAPI", { enumerable: true, get: function () { return open_api_1.MicrocosmAPI; } });
@@ -0,0 +1,75 @@
1
+ export interface MicrocosmAPIConfig {
2
+ baseUrl?: string;
3
+ timeout?: number;
4
+ }
5
+ interface ApiRes<T> {
6
+ success: boolean;
7
+ data: T;
8
+ }
9
+ export declare class MicrocosmAPI {
10
+ private base;
11
+ constructor(config?: MicrocosmAPIConfig);
12
+ readonly mcc: {
13
+ getPrice: () => Promise<ApiRes<any>>;
14
+ getStats: () => Promise<ApiRes<any>>;
15
+ getBalance: (token: string) => Promise<ApiRes<any>>;
16
+ getHolders: () => Promise<ApiRes<any>>;
17
+ getMiningHistory: (days?: number) => Promise<ApiRes<any>>;
18
+ };
19
+ readonly mcd: {
20
+ getBalance: (token: string) => Promise<ApiRes<any>>;
21
+ getStats: () => Promise<ApiRes<any>>;
22
+ };
23
+ readonly reincarnation: {
24
+ getPool: () => Promise<ApiRes<any>>;
25
+ getBuybackPrice: () => Promise<ApiRes<any>>;
26
+ };
27
+ readonly mining: {
28
+ getRecords: (token: string, params?: {
29
+ limit?: number;
30
+ offset?: number;
31
+ }) => Promise<ApiRes<any>>;
32
+ getStats: (token: string) => Promise<ApiRes<any>>;
33
+ };
34
+ readonly users: {
35
+ getProfile: (token: string) => Promise<ApiRes<any>>;
36
+ getLevel: (token: string) => Promise<ApiRes<any>>;
37
+ };
38
+ readonly dashboard: {
39
+ getMarket: () => Promise<ApiRes<any>>;
40
+ getPlatform: () => Promise<ApiRes<any>>;
41
+ getUserSummary: (wallet: string) => Promise<ApiRes<any>>;
42
+ };
43
+ readonly territory: {
44
+ getCollection: () => Promise<ApiRes<any>>;
45
+ getNft: (mint: string) => Promise<ApiRes<any>>;
46
+ getUserNfts: (wallet: string) => Promise<ApiRes<any>>;
47
+ };
48
+ readonly auctions: {
49
+ getConfig: () => Promise<ApiRes<any>>;
50
+ getActive: () => Promise<ApiRes<any>>;
51
+ getDetails: (id: number) => Promise<ApiRes<any>>;
52
+ getBids: (id: number) => Promise<ApiRes<any>>;
53
+ };
54
+ readonly techTree: {
55
+ getConfig: () => Promise<ApiRes<any>>;
56
+ getUser: (token: string) => Promise<ApiRes<any>>;
57
+ getBonus: (token: string) => Promise<ApiRes<any>>;
58
+ unlock: (token: string, nodeId: string) => Promise<ApiRes<any>>;
59
+ upgrade: (token: string, nodeId: string) => Promise<ApiRes<any>>;
60
+ };
61
+ readonly fragment: {
62
+ getConfig: () => Promise<ApiRes<any>>;
63
+ getVaults: () => Promise<ApiRes<any>>;
64
+ getVault: (id: number) => Promise<ApiRes<any>>;
65
+ };
66
+ readonly lending: {
67
+ getPool: () => Promise<ApiRes<any>>;
68
+ getStats: () => Promise<ApiRes<any>>;
69
+ getPosition: (wallet: string) => Promise<ApiRes<any>>;
70
+ };
71
+ readonly organizations: {
72
+ getList: (token: string) => Promise<ApiRes<any>>;
73
+ };
74
+ }
75
+ export {};
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MicrocosmAPI = void 0;
4
+ // AI-generated · AI-managed · AI-maintained
5
+ const DEFAULT_BASE = 'https://api.microcosm.money/v1';
6
+ async function request(baseUrl, path, token) {
7
+ const headers = { 'Accept': 'application/json' };
8
+ if (token)
9
+ headers['Authorization'] = `Bearer ${token}`;
10
+ const res = await fetch(`${baseUrl}${path}`, { headers });
11
+ if (res.status === 429) {
12
+ const retry = parseInt(res.headers.get('Retry-After') || '60');
13
+ await new Promise(r => setTimeout(r, retry * 1000));
14
+ return request(baseUrl, path, token);
15
+ }
16
+ const data = await res.json();
17
+ if (!res.ok)
18
+ throw new Error(data.detail || `API error ${res.status}`);
19
+ return data;
20
+ }
21
+ async function postRequest(baseUrl, path, body, token) {
22
+ const headers = {
23
+ 'Accept': 'application/json',
24
+ 'Content-Type': 'application/json',
25
+ };
26
+ if (token)
27
+ headers['Authorization'] = `Bearer ${token}`;
28
+ const res = await fetch(`${baseUrl}${path}`, {
29
+ method: 'POST',
30
+ headers,
31
+ body: JSON.stringify(body),
32
+ });
33
+ const data = await res.json();
34
+ if (!res.ok)
35
+ throw new Error(data.detail || `API error ${res.status}`);
36
+ return data;
37
+ }
38
+ class MicrocosmAPI {
39
+ constructor(config) {
40
+ this.mcc = {
41
+ getPrice: () => request(this.base, '/mcc/price'),
42
+ getStats: () => request(this.base, '/mcc/stats'),
43
+ getBalance: (token) => request(this.base, '/mcc/balance', token),
44
+ getHolders: () => request(this.base, '/reincarnation/holders'),
45
+ getMiningHistory: (days = 30) => request(this.base, `/reincarnation/mining-history?days=${days}`),
46
+ };
47
+ this.mcd = {
48
+ getBalance: (token) => request(this.base, '/mcd/balance', token),
49
+ getStats: () => request(this.base, '/mcd/stats'),
50
+ };
51
+ this.reincarnation = {
52
+ getPool: () => request(this.base, '/reincarnation/pool'),
53
+ getBuybackPrice: () => request(this.base, '/reincarnation/buyback-price'),
54
+ };
55
+ this.mining = {
56
+ getRecords: (token, params) => {
57
+ const qs = params ? `?limit=${params.limit || 20}&offset=${params.offset || 0}` : '';
58
+ return request(this.base, `/mining/records${qs}`, token);
59
+ },
60
+ getStats: (token) => request(this.base, '/mining/stats', token),
61
+ };
62
+ this.users = {
63
+ getProfile: (token) => request(this.base, '/users/me', token),
64
+ getLevel: (token) => request(this.base, '/users/me/level', token),
65
+ };
66
+ this.dashboard = {
67
+ getMarket: () => request(this.base, '/dashboard/market'),
68
+ getPlatform: () => request(this.base, '/dashboard/platform'),
69
+ getUserSummary: (wallet) => request(this.base, `/dashboard/user/${wallet}`),
70
+ };
71
+ this.territory = {
72
+ getCollection: () => request(this.base, '/territory/collection'),
73
+ getNft: (mint) => request(this.base, `/territory/nft/${mint}`),
74
+ getUserNfts: (wallet) => request(this.base, `/territory/nfts/${wallet}`),
75
+ };
76
+ this.auctions = {
77
+ getConfig: () => request(this.base, '/auction-solana/config'),
78
+ getActive: () => request(this.base, '/auction-solana/active'),
79
+ getDetails: (id) => request(this.base, `/auction-solana/auction/${id}`),
80
+ getBids: (id) => request(this.base, `/auction-solana/auction/${id}/bids`),
81
+ };
82
+ this.techTree = {
83
+ getConfig: () => request(this.base, '/tech-tree/config'),
84
+ getUser: (token) => request(this.base, '/tech-tree/user', token),
85
+ getBonus: (token) => request(this.base, '/tech-tree/bonus', token),
86
+ unlock: (token, nodeId) => postRequest(this.base, '/tech-tree/unlock', { node_id: nodeId }, token),
87
+ upgrade: (token, nodeId) => postRequest(this.base, '/tech-tree/upgrade', { node_id: nodeId }, token),
88
+ };
89
+ this.fragment = {
90
+ getConfig: () => request(this.base, '/fragment/config'),
91
+ getVaults: () => request(this.base, '/fragment/vaults'),
92
+ getVault: (id) => request(this.base, `/fragment/vault/${id}`),
93
+ };
94
+ this.lending = {
95
+ getPool: () => request(this.base, '/lending/pool'),
96
+ getStats: () => request(this.base, '/lending/stats'),
97
+ getPosition: (wallet) => request(this.base, `/lending/position/${wallet}`),
98
+ };
99
+ this.organizations = {
100
+ getList: (token) => request(this.base, '/organizations', token),
101
+ };
102
+ this.base = (config?.baseUrl || DEFAULT_BASE).replace(/\/$/, '');
103
+ }
104
+ }
105
+ exports.MicrocosmAPI = MicrocosmAPI;
@@ -0,0 +1,17 @@
1
+ export declare class Storage {
2
+ private type;
3
+ private memoryStore;
4
+ constructor(type?: 'localStorage' | 'sessionStorage' | 'memory');
5
+ private prefixKey;
6
+ get(key: string): string | null;
7
+ set(key: string, value: string): void;
8
+ remove(key: string): void;
9
+ clear(): void;
10
+ }
11
+ export declare const STORAGE_KEYS: {
12
+ readonly ACCESS_TOKEN: "access_token";
13
+ readonly REFRESH_TOKEN: "refresh_token";
14
+ readonly EXPIRES_AT: "token_expires_at";
15
+ readonly USER: "user";
16
+ readonly OAUTH_STATE: "oauth_state";
17
+ };
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.STORAGE_KEYS = exports.Storage = void 0;
4
+ // AI-generated · AI-managed · AI-maintained
5
+ const KEY_PREFIX = 'mc_';
6
+ class Storage {
7
+ constructor(type = 'localStorage') {
8
+ this.memoryStore = new Map();
9
+ this.type = type;
10
+ }
11
+ prefixKey(key) {
12
+ return key.startsWith(KEY_PREFIX) ? key : `${KEY_PREFIX}${key}`;
13
+ }
14
+ get(key) {
15
+ const prefixed = this.prefixKey(key);
16
+ try {
17
+ if (this.type === 'memory') {
18
+ return this.memoryStore.get(prefixed) ?? null;
19
+ }
20
+ if (typeof window === 'undefined')
21
+ return null;
22
+ const store = this.type === 'localStorage' ? window.localStorage : window.sessionStorage;
23
+ return store.getItem(prefixed);
24
+ }
25
+ catch {
26
+ return this.memoryStore.get(prefixed) ?? null;
27
+ }
28
+ }
29
+ set(key, value) {
30
+ const prefixed = this.prefixKey(key);
31
+ try {
32
+ if (this.type === 'memory') {
33
+ this.memoryStore.set(prefixed, value);
34
+ return;
35
+ }
36
+ if (typeof window === 'undefined') {
37
+ this.memoryStore.set(prefixed, value);
38
+ return;
39
+ }
40
+ const store = this.type === 'localStorage' ? window.localStorage : window.sessionStorage;
41
+ store.setItem(prefixed, value);
42
+ }
43
+ catch {
44
+ this.memoryStore.set(prefixed, value);
45
+ }
46
+ }
47
+ remove(key) {
48
+ const prefixed = this.prefixKey(key);
49
+ try {
50
+ this.memoryStore.delete(prefixed);
51
+ if (this.type !== 'memory' && typeof window !== 'undefined') {
52
+ const store = this.type === 'localStorage' ? window.localStorage : window.sessionStorage;
53
+ store.removeItem(prefixed);
54
+ }
55
+ }
56
+ catch {
57
+ this.memoryStore.delete(prefixed);
58
+ }
59
+ }
60
+ clear() {
61
+ const keysToRemove = [];
62
+ try {
63
+ if (this.type !== 'memory' && typeof window !== 'undefined') {
64
+ const store = this.type === 'localStorage' ? window.localStorage : window.sessionStorage;
65
+ for (let i = 0; i < store.length; i++) {
66
+ const key = store.key(i);
67
+ if (key?.startsWith(KEY_PREFIX)) {
68
+ keysToRemove.push(key);
69
+ }
70
+ }
71
+ keysToRemove.forEach(k => store.removeItem(k));
72
+ }
73
+ }
74
+ catch {
75
+ }
76
+ this.memoryStore.clear();
77
+ }
78
+ }
79
+ exports.Storage = Storage;
80
+ exports.STORAGE_KEYS = {
81
+ ACCESS_TOKEN: 'access_token',
82
+ REFRESH_TOKEN: 'refresh_token',
83
+ EXPIRES_AT: 'token_expires_at',
84
+ USER: 'user',
85
+ OAUTH_STATE: 'oauth_state',
86
+ };
@@ -0,0 +1,18 @@
1
+ import { ResolvedConfig, TokenData } from './types';
2
+ import { Storage } from './storage';
3
+ export declare class TokenManager {
4
+ private config;
5
+ private storage;
6
+ private refreshTimer;
7
+ private refreshPromise;
8
+ constructor(config: ResolvedConfig, storage: Storage);
9
+ setTokens(data: TokenData): void;
10
+ getAccessToken(): Promise<string | null>;
11
+ getRawAccessToken(): string | null;
12
+ hasValidToken(): boolean;
13
+ isExpired(): boolean;
14
+ clear(): void;
15
+ scheduleRefresh(): void;
16
+ private refreshToken;
17
+ private doRefresh;
18
+ }
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TokenManager = void 0;
4
+ const storage_1 = require("./storage");
5
+ class TokenManager {
6
+ constructor(config, storage) {
7
+ this.refreshTimer = null;
8
+ this.refreshPromise = null;
9
+ this.config = config;
10
+ this.storage = storage;
11
+ }
12
+ setTokens(data) {
13
+ this.storage.set(storage_1.STORAGE_KEYS.ACCESS_TOKEN, data.accessToken);
14
+ this.storage.set(storage_1.STORAGE_KEYS.REFRESH_TOKEN, data.refreshToken);
15
+ this.storage.set(storage_1.STORAGE_KEYS.EXPIRES_AT, data.expiresAt.toString());
16
+ if (this.config.autoRefresh) {
17
+ this.scheduleRefresh();
18
+ }
19
+ }
20
+ async getAccessToken() {
21
+ const accessToken = this.storage.get(storage_1.STORAGE_KEYS.ACCESS_TOKEN);
22
+ const expiresAt = parseInt(this.storage.get(storage_1.STORAGE_KEYS.EXPIRES_AT) || '0', 10);
23
+ if (!accessToken)
24
+ return null;
25
+ const bufferMs = this.config.refreshBuffer * 1000;
26
+ if (expiresAt - Date.now() < bufferMs) {
27
+ return this.refreshToken();
28
+ }
29
+ return accessToken;
30
+ }
31
+ getRawAccessToken() {
32
+ return this.storage.get(storage_1.STORAGE_KEYS.ACCESS_TOKEN);
33
+ }
34
+ hasValidToken() {
35
+ return !!this.storage.get(storage_1.STORAGE_KEYS.REFRESH_TOKEN);
36
+ }
37
+ isExpired() {
38
+ const expiresAt = parseInt(this.storage.get(storage_1.STORAGE_KEYS.EXPIRES_AT) || '0', 10);
39
+ return Date.now() >= expiresAt;
40
+ }
41
+ clear() {
42
+ if (this.refreshTimer) {
43
+ clearTimeout(this.refreshTimer);
44
+ this.refreshTimer = null;
45
+ }
46
+ this.storage.remove(storage_1.STORAGE_KEYS.ACCESS_TOKEN);
47
+ this.storage.remove(storage_1.STORAGE_KEYS.REFRESH_TOKEN);
48
+ this.storage.remove(storage_1.STORAGE_KEYS.EXPIRES_AT);
49
+ }
50
+ scheduleRefresh() {
51
+ if (this.refreshTimer) {
52
+ clearTimeout(this.refreshTimer);
53
+ }
54
+ const expiresAt = parseInt(this.storage.get(storage_1.STORAGE_KEYS.EXPIRES_AT) || '0', 10);
55
+ const bufferMs = this.config.refreshBuffer * 1000;
56
+ const delay = Math.max(0, expiresAt - Date.now() - bufferMs);
57
+ if (delay > 0 && delay < 86400000) {
58
+ this.refreshTimer = setTimeout(() => this.refreshToken(), delay);
59
+ }
60
+ }
61
+ async refreshToken() {
62
+ if (this.refreshPromise) {
63
+ return this.refreshPromise;
64
+ }
65
+ this.refreshPromise = this.doRefresh();
66
+ try {
67
+ return await this.refreshPromise;
68
+ }
69
+ finally {
70
+ this.refreshPromise = null;
71
+ }
72
+ }
73
+ async doRefresh() {
74
+ const refreshToken = this.storage.get(storage_1.STORAGE_KEYS.REFRESH_TOKEN);
75
+ if (!refreshToken)
76
+ return null;
77
+ try {
78
+ const response = await fetch(this.config.tokenExchangeUri, {
79
+ method: 'POST',
80
+ headers: { 'Content-Type': 'application/json' },
81
+ body: JSON.stringify({
82
+ grant_type: 'refresh_token',
83
+ refresh_token: refreshToken,
84
+ }),
85
+ });
86
+ if (!response.ok) {
87
+ this.clear();
88
+ return null;
89
+ }
90
+ const contentType = response.headers.get('content-type');
91
+ if (!contentType?.includes('application/json')) {
92
+ this.clear();
93
+ return null;
94
+ }
95
+ const data = await response.json();
96
+ if (!data.access_token) {
97
+ this.clear();
98
+ return null;
99
+ }
100
+ this.setTokens({
101
+ accessToken: data.access_token,
102
+ refreshToken: data.refresh_token || refreshToken,
103
+ expiresAt: Date.now() + (data.expires_in || 3600) * 1000,
104
+ });
105
+ return data.access_token;
106
+ }
107
+ catch (error) {
108
+ console.error('[MicrocosmAuth] Token refresh failed:', error);
109
+ return null;
110
+ }
111
+ }
112
+ }
113
+ exports.TokenManager = TokenManager;
@@ -0,0 +1,82 @@
1
+ export interface MicrocosmAuthConfig {
2
+ clientId: string;
3
+ redirectUri: string;
4
+ scope?: string[];
5
+ authEndpoint?: string;
6
+ tokenExchangeUri?: string;
7
+ profileUri?: string;
8
+ storage?: 'localStorage' | 'sessionStorage' | 'memory';
9
+ autoRefresh?: boolean;
10
+ refreshBuffer?: number;
11
+ debug?: boolean;
12
+ }
13
+ export type ResolvedConfig = Required<MicrocosmAuthConfig>;
14
+ export interface User {
15
+ uid: string;
16
+ email: string;
17
+ displayName?: string | null;
18
+ avatarUrl?: string | null;
19
+ role: 'admin' | 'user' | 'agent';
20
+ level?: 'recruit' | 'prospect' | 'miner';
21
+ title?: 'commander' | 'pioneer' | 'warden' | 'admiral' | null;
22
+ stationId?: number | null;
23
+ emailVerified?: boolean;
24
+ }
25
+ export interface TokenData {
26
+ accessToken: string;
27
+ refreshToken: string;
28
+ expiresAt: number;
29
+ }
30
+ export interface AuthState {
31
+ user: User | null;
32
+ isAuthenticated: boolean;
33
+ isLoading: boolean;
34
+ error: Error | null;
35
+ }
36
+ export interface LoginOptions {
37
+ prompt?: 'login' | 'consent';
38
+ }
39
+ export interface TokenExchangeResponse {
40
+ access_token: string;
41
+ refresh_token: string;
42
+ expires_in: number;
43
+ user_id?: string;
44
+ }
45
+ export interface UserProfileResponse {
46
+ uid: string;
47
+ email?: string;
48
+ display_name?: string | null;
49
+ avatar_url?: string | null;
50
+ role?: string;
51
+ level?: string;
52
+ title?: string | null;
53
+ station_id?: number | null;
54
+ email_verified?: boolean;
55
+ user?: UserProfileResponse;
56
+ }
57
+ export interface ApiResponse<T> {
58
+ success: boolean;
59
+ data: T | null;
60
+ error: string | null;
61
+ message?: string | null;
62
+ }
63
+ export interface MCDBalance {
64
+ total_balance: string;
65
+ available_balance: string;
66
+ frozen_balance: string;
67
+ }
68
+ export interface MCCBalance {
69
+ total_balance: number;
70
+ available_balance: number;
71
+ locked_balance: number;
72
+ on_chain_balance?: number;
73
+ wallet_address?: string;
74
+ }
75
+ export interface MCCPrice {
76
+ price: number;
77
+ price_change_24h?: number;
78
+ volume_24h?: number;
79
+ market_cap?: number;
80
+ source: string;
81
+ updated_at: string;
82
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@microcosmmoney/auth-core",
3
+ "version": "1.0.0",
4
+ "description": "Microcosm OAuth 2.0 authentication core library",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": ["dist"],
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "dev": "tsc --watch"
11
+ },
12
+ "peerDependencies": {},
13
+ "devDependencies": {
14
+ "typescript": "^5.3.0"
15
+ },
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/MicrocosmMoney/Microcosm"
20
+ }
21
+ }