@localzet/data-connector 1.0.1 → 1.0.3

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.
@@ -1,14 +1,18 @@
1
- export interface MixIdConfig {
2
- apiBase: string;
1
+ import { StorageAdapter } from './storage';
2
+ export interface Config {
3
+ server: string;
3
4
  clientId: string;
4
- clientSecret: string;
5
+ clientSecret?: string;
5
6
  accessToken?: string;
6
7
  refreshToken?: string;
7
8
  }
8
- declare class MixIdApi {
9
+ declare class API {
9
10
  private config;
10
- setConfig(config: MixIdConfig): void;
11
- getConfig(): MixIdConfig | null;
11
+ private storage;
12
+ constructor(storage?: StorageAdapter);
13
+ setStorageAdapter(adapter: StorageAdapter): void;
14
+ setConfig(config: Config): void;
15
+ getConfig(): Config | null;
12
16
  clearConfig(): void;
13
17
  private request;
14
18
  private refreshAccessToken;
@@ -71,6 +75,6 @@ declare class MixIdApi {
71
75
  success: boolean;
72
76
  }>;
73
77
  }
74
- export declare const mixIdApi: MixIdApi;
78
+ export declare const api: API;
75
79
  export {};
76
- //# sourceMappingURL=mixIdApi.d.ts.map
80
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/api/api.ts"],"names":[],"mappings":"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"}
@@ -1,99 +1,87 @@
1
- const MIX_ID_API_BASE = typeof window !== 'undefined' && window.__MIX_ID_API_BASE
2
- ? window.__MIX_ID_API_BASE
3
- : (typeof import.meta !== 'undefined' && import.meta.env?.VITE_MIX_ID_API_BASE)
4
- ? import.meta.env.VITE_MIX_ID_API_BASE
5
- : 'https://data-center.zorin.cloud/api';
6
- class MixIdApi {
7
- constructor() {
1
+ import { createDefaultStorage } from './storage';
2
+ const MIX_ID_API_BASE = import.meta?.env?.VITE_MIX_ID_API_BASE ?? 'https://data-center.zorin.cloud/api';
3
+ class API {
4
+ constructor(storage) {
8
5
  Object.defineProperty(this, "config", {
9
6
  enumerable: true,
10
7
  configurable: true,
11
8
  writable: true,
12
9
  value: null
13
10
  });
11
+ Object.defineProperty(this, "storage", {
12
+ enumerable: true,
13
+ configurable: true,
14
+ writable: true,
15
+ value: void 0
16
+ });
17
+ this.storage = storage || createDefaultStorage();
18
+ }
19
+ setStorageAdapter(adapter) {
20
+ this.storage = adapter;
14
21
  }
15
22
  setConfig(config) {
16
23
  this.config = config;
17
- // Save config without tokens (tokens saved separately)
18
- const { accessToken, refreshToken, ...configWithoutTokens } = config;
19
- if (typeof window !== 'undefined' && window.localStorage) {
20
- localStorage.setItem('mixId_config', JSON.stringify(configWithoutTokens));
21
- if (config.accessToken) {
22
- localStorage.setItem('mixId_accessToken', config.accessToken);
23
- }
24
- if (config.refreshToken) {
25
- localStorage.setItem('mixId_refreshToken', config.refreshToken);
26
- }
27
- // Dispatch custom event for same-tab updates
28
- window.dispatchEvent(new Event('mixid-config-changed'));
24
+ try {
25
+ this.storage.setItem('mixid_config', JSON.stringify({
26
+ server: config.server,
27
+ clientId: config.clientId,
28
+ }));
29
29
  }
30
+ catch (e) {
31
+ console.error('Error saving MIX ID config:', e);
32
+ }
33
+ window?.dispatchEvent(new Event('mixid-config-changed'));
30
34
  }
31
35
  getConfig() {
32
36
  if (!this.config) {
33
- if (typeof window !== 'undefined' && window.localStorage) {
34
- try {
35
- const accessToken = localStorage.getItem('mixId_accessToken');
36
- const refreshToken = localStorage.getItem('mixId_refreshToken');
37
- const stored = localStorage.getItem('mixId_config');
38
- if (stored) {
39
- const parsed = JSON.parse(stored);
40
- this.config = {
41
- ...parsed,
42
- accessToken: accessToken || undefined,
43
- refreshToken: refreshToken || undefined
44
- };
45
- }
46
- else if (accessToken || refreshToken) {
47
- // If we have tokens but no config, try to restore from tokens
48
- console.warn('MIX ID config missing but tokens found. Please reconfigure MIX ID.');
49
- }
50
- }
51
- catch (error) {
52
- console.error('Error loading MIX ID config:', error);
53
- this.config = null;
37
+ try {
38
+ const stored = this.storage.getItem('mixid_config');
39
+ if (stored) {
40
+ this.config = { ...JSON.parse(stored) };
54
41
  }
55
42
  }
43
+ catch (error) {
44
+ console.error('Error loading MIX ID config:', error);
45
+ this.config = null;
46
+ }
56
47
  }
57
48
  return this.config;
58
49
  }
59
50
  clearConfig() {
60
51
  this.config = null;
61
- if (typeof window !== 'undefined' && window.localStorage) {
62
- localStorage.removeItem('mixId_config');
63
- localStorage.removeItem('mixId_accessToken');
64
- localStorage.removeItem('mixId_refreshToken');
65
- // Dispatch custom event for same-tab updates
66
- window.dispatchEvent(new Event('mixid-config-changed'));
52
+ try {
53
+ this.storage.removeItem('mixid_config');
54
+ }
55
+ catch (e) {
56
+ console.error('Error clearing MIX ID config:', e);
67
57
  }
58
+ window?.dispatchEvent(new Event('mixid-config-changed'));
68
59
  }
69
60
  async request(endpoint, options = {}) {
70
61
  const config = this.getConfig();
71
62
  if (!config) {
72
63
  throw new Error('MIX ID not configured');
73
64
  }
74
- const token = config.accessToken || (typeof window !== 'undefined' && window.localStorage
75
- ? localStorage.getItem('mixId_accessToken')
76
- : null);
77
65
  const headers = {
78
66
  'Content-Type': 'application/json',
79
67
  ...(options.headers || {}),
80
68
  };
69
+ const token = config.accessToken || null;
81
70
  if (token) {
82
71
  headers['Authorization'] = `Bearer ${token}`;
83
72
  }
84
- const response = await fetch(`${config.apiBase || MIX_ID_API_BASE}${endpoint}`, {
73
+ const response = await fetch(`${config.server || MIX_ID_API_BASE}${endpoint}`, {
85
74
  ...options,
86
75
  headers,
87
76
  });
88
77
  if (response.status === 401) {
89
- // Try to refresh token
90
78
  const refreshed = await this.refreshAccessToken();
91
79
  if (refreshed) {
92
80
  const retryHeaders = {
93
81
  ...headers,
94
- 'Authorization': `Bearer ${refreshed}`,
82
+ Authorization: `Bearer ${refreshed}`,
95
83
  };
96
- const retryResponse = await fetch(`${config.apiBase || MIX_ID_API_BASE}${endpoint}`, {
84
+ const retryResponse = await fetch(`${config.server || MIX_ID_API_BASE}${endpoint}`, {
97
85
  ...options,
98
86
  headers: retryHeaders,
99
87
  });
@@ -111,13 +99,11 @@ class MixIdApi {
111
99
  }
112
100
  async refreshAccessToken() {
113
101
  const config = this.getConfig();
114
- const refreshToken = config?.refreshToken || (typeof window !== 'undefined' && window.localStorage
115
- ? localStorage.getItem('mixId_refreshToken')
116
- : null);
102
+ const refreshToken = config?.refreshToken;
117
103
  if (!refreshToken)
118
104
  return null;
119
105
  try {
120
- const response = await fetch(`${config?.apiBase || MIX_ID_API_BASE}/auth/refresh`, {
106
+ const response = await fetch(`${config?.server || MIX_ID_API_BASE}/auth/refresh`, {
121
107
  method: 'POST',
122
108
  headers: { 'Content-Type': 'application/json' },
123
109
  body: JSON.stringify({ refreshToken }),
@@ -126,18 +112,9 @@ class MixIdApi {
126
112
  return null;
127
113
  const data = await response.json();
128
114
  if (data.accessToken) {
129
- if (typeof window !== 'undefined' && window.localStorage) {
130
- localStorage.setItem('mixId_accessToken', data.accessToken);
131
- }
132
115
  if (this.config) {
133
116
  this.config.accessToken = data.accessToken;
134
- // Save updated config
135
- const { accessToken: _, refreshToken: __, ...configWithoutTokens } = this.config;
136
- if (typeof window !== 'undefined' && window.localStorage) {
137
- localStorage.setItem('mixId_config', JSON.stringify(configWithoutTokens));
138
- }
139
117
  }
140
- // Dispatch custom event for same-tab updates
141
118
  if (typeof window !== 'undefined') {
142
119
  window.dispatchEvent(new Event('mixid-config-changed'));
143
120
  }
@@ -149,7 +126,6 @@ class MixIdApi {
149
126
  }
150
127
  return null;
151
128
  }
152
- // OAuth flow
153
129
  async initiateOAuth(redirectUri, state) {
154
130
  const config = this.getConfig();
155
131
  if (!config) {
@@ -169,26 +145,34 @@ class MixIdApi {
169
145
  if (!config) {
170
146
  throw new Error('MIX ID not configured');
171
147
  }
172
- // Don't use this.request() here because we don't have a token yet
173
- // Make direct fetch without Authorization header
174
- const response = await fetch(`${config.apiBase || MIX_ID_API_BASE}/auth/oauth/token`, {
148
+ // Обмен кода на токен. Не отправляем clientSecret из браузера по умолчанию.
149
+ // Рекомендуется использовать PKCE или выполнять обмен кода на сервере (server-side exchange).
150
+ const body = {
151
+ code,
152
+ clientId: config.clientId,
153
+ redirectUri,
154
+ };
155
+ const allowClientSecretInBrowser = import.meta?.env?.VITE_ALLOW_CLIENT_SECRET_IN_BROWSER === 'true';
156
+ if (config.clientSecret) {
157
+ if (typeof window === 'undefined' || allowClientSecretInBrowser) {
158
+ body.clientSecret = config.clientSecret;
159
+ }
160
+ else {
161
+ console.warn('clientSecret присутствует, но не отправляется из браузера по соображениям безопасности. Используйте PKCE или server-side exchange.');
162
+ }
163
+ }
164
+ const response = await fetch(`${config.server || MIX_ID_API_BASE}/auth/oauth/token`, {
175
165
  method: 'POST',
176
166
  headers: {
177
167
  'Content-Type': 'application/json',
178
168
  },
179
- body: JSON.stringify({
180
- code,
181
- clientId: config.clientId,
182
- clientSecret: config.clientSecret,
183
- redirectUri,
184
- }),
169
+ body: JSON.stringify(body),
185
170
  });
186
171
  if (!response.ok) {
187
172
  const error = await response.json().catch(() => ({ error: 'Unknown error' }));
188
173
  throw new Error(error.error || `HTTP ${response.status}`);
189
174
  }
190
175
  const data = await response.json();
191
- // Save tokens
192
176
  this.setConfig({
193
177
  ...config,
194
178
  accessToken: data.access_token,
@@ -196,7 +180,6 @@ class MixIdApi {
196
180
  });
197
181
  return data;
198
182
  }
199
- // Sync
200
183
  async getSyncStatus() {
201
184
  return this.request('/sync/status');
202
185
  }
@@ -216,17 +199,14 @@ class MixIdApi {
216
199
  return this.request('/sync/settings');
217
200
  }
218
201
  async uploadData(dataType, data) {
219
- // Split large data into chunks to avoid 413 Payload Too Large
220
- const CHUNK_SIZE = 100; // Number of items per chunk
202
+ const CHUNK_SIZE = 100;
221
203
  const dataEntries = Object.entries(data);
222
204
  if (dataEntries.length <= CHUNK_SIZE) {
223
- // Small enough to send in one request
224
205
  return this.request('/sync/data', {
225
206
  method: 'POST',
226
207
  body: JSON.stringify({ dataType, data }),
227
208
  });
228
209
  }
229
- // Split into chunks
230
210
  const chunks = [];
231
211
  for (let i = 0; i < dataEntries.length; i += CHUNK_SIZE) {
232
212
  const chunk = {};
@@ -235,7 +215,6 @@ class MixIdApi {
235
215
  }
236
216
  chunks.push(chunk);
237
217
  }
238
- // Upload chunks sequentially
239
218
  for (const chunk of chunks) {
240
219
  await this.request('/sync/data', {
241
220
  method: 'POST',
@@ -255,14 +234,12 @@ class MixIdApi {
255
234
  params.append('dataTypes', dataTypes.join(','));
256
235
  return this.request(`/sync/check-updates?${params.toString()}`);
257
236
  }
258
- // Session heartbeat
259
237
  async heartbeat(deviceInfo) {
260
238
  return this.request('/sessions/heartbeat', {
261
239
  method: 'POST',
262
240
  body: JSON.stringify({ deviceInfo }),
263
241
  });
264
242
  }
265
- // Session management
266
243
  async getSessions() {
267
244
  return this.request('/sessions');
268
245
  }
@@ -272,4 +249,4 @@ class MixIdApi {
272
249
  });
273
250
  }
274
251
  }
275
- export const mixIdApi = new MixIdApi();
252
+ export const api = new API();
@@ -1,4 +1,4 @@
1
- export * from './mixIdApi';
1
+ export * from './api';
2
2
  export * from './websocket';
3
3
  export * from './offlineQueue';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,CAAA;AACrB,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA"}
package/dist/api/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export * from './mixIdApi';
1
+ export * from './api';
2
2
  export * from './websocket';
3
3
  export * from './offlineQueue';
@@ -0,0 +1,18 @@
1
+ export interface StorageAdapter {
2
+ getItem(key: string): string | null;
3
+ setItem(key: string, value: string): void;
4
+ removeItem(key: string): void;
5
+ }
6
+ export declare class InMemoryStorage implements StorageAdapter {
7
+ private store;
8
+ getItem(key: string): string | null;
9
+ setItem(key: string, value: string): void;
10
+ removeItem(key: string): void;
11
+ }
12
+ export declare class SessionStorageAdapter implements StorageAdapter {
13
+ getItem(key: string): string | null;
14
+ setItem(key: string, value: string): void;
15
+ removeItem(key: string): void;
16
+ }
17
+ export declare function createDefaultStorage(): StorageAdapter;
18
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/api/storage.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IACnC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACzC,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED,qBAAa,eAAgB,YAAW,cAAc;IACpD,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,GAAG,EAAE,MAAM;IAGnB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAGlC,UAAU,CAAC,GAAG,EAAE,MAAM;CAGvB;AAED,qBAAa,qBAAsB,YAAW,cAAc;IAC1D,OAAO,CAAC,GAAG,EAAE,MAAM;IAInB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAIlC,UAAU,CAAC,GAAG,EAAE,MAAM;CAIvB;AAED,wBAAgB,oBAAoB,IAAI,cAAc,CAerD"}
@@ -0,0 +1,53 @@
1
+ export class InMemoryStorage {
2
+ constructor() {
3
+ Object.defineProperty(this, "store", {
4
+ enumerable: true,
5
+ configurable: true,
6
+ writable: true,
7
+ value: new Map()
8
+ });
9
+ }
10
+ getItem(key) {
11
+ return this.store.has(key) ? this.store.get(key) : null;
12
+ }
13
+ setItem(key, value) {
14
+ this.store.set(key, value);
15
+ }
16
+ removeItem(key) {
17
+ this.store.delete(key);
18
+ }
19
+ }
20
+ export class SessionStorageAdapter {
21
+ getItem(key) {
22
+ if (typeof window === 'undefined' || !window.sessionStorage)
23
+ return null;
24
+ return window.sessionStorage.getItem(key);
25
+ }
26
+ setItem(key, value) {
27
+ if (typeof window === 'undefined' || !window.sessionStorage)
28
+ return;
29
+ window.sessionStorage.setItem(key, value);
30
+ }
31
+ removeItem(key) {
32
+ if (typeof window === 'undefined' || !window.sessionStorage)
33
+ return;
34
+ window.sessionStorage.removeItem(key);
35
+ }
36
+ }
37
+ export function createDefaultStorage() {
38
+ try {
39
+ if (typeof window !== 'undefined' && window.sessionStorage) {
40
+ // Разрешаем sessionStorage только если явно включено через env (безопасность по умолчанию)
41
+ // В сборках Vite можно установить VITE_ALLOW_PERSISTENT_STORAGE=true для тестов
42
+ // Но лучше использовать безопасное хранилище на бекенде / secure native storage
43
+ // @ts-ignore
44
+ if (import.meta?.env?.VITE_ALLOW_PERSISTENT_STORAGE === 'true') {
45
+ return new SessionStorageAdapter();
46
+ }
47
+ }
48
+ }
49
+ catch (e) {
50
+ // ignore
51
+ }
52
+ return new InMemoryStorage();
53
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../../src/api/websocket.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB;AAED,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAA;AAEvE,cAAM,eAAe;IACnB,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,iBAAiB,CAAI;IAC7B,OAAO,CAAC,oBAAoB,CAAK;IACjC,OAAO,CAAC,cAAc,CAAO;IAC7B,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,aAAa,CAAqD;IAC1E,OAAO,CAAC,YAAY,CAAyB;IAC7C,OAAO,CAAC,QAAQ,CAA6D;;IAkB7E,OAAO;IAkEP,OAAO,CAAC,gBAAgB;IAqBxB,OAAO,CAAC,aAAa;IAoBrB,IAAI,CAAC,OAAO,EAAE,gBAAgB;IAS9B,OAAO,CAAC,iBAAiB;IASzB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB;IAOpD,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB;IAOrD,UAAU;IAeV,WAAW,IAAI,OAAO;CAGvB;AAED,eAAO,MAAM,QAAQ,iBAAwB,CAAA"}
1
+ {"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../../src/api/websocket.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB;AAED,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAA;AAEvE,cAAM,eAAe;IACnB,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,iBAAiB,CAAI;IAC7B,OAAO,CAAC,oBAAoB,CAAK;IACjC,OAAO,CAAC,cAAc,CAAO;IAC7B,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,aAAa,CAAqD;IAC1E,OAAO,CAAC,YAAY,CAAyB;IAC7C,OAAO,CAAC,QAAQ,CAA6D;;IAkB7E,OAAO;IAgEP,OAAO,CAAC,gBAAgB;IAqBxB,OAAO,CAAC,aAAa;IAoBrB,IAAI,CAAC,OAAO,EAAE,gBAAgB;IAS9B,OAAO,CAAC,iBAAiB;IASzB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB;IAOpD,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB;IAOrD,UAAU;IAeV,WAAW,IAAI,OAAO;CAGvB;AAED,eAAO,MAAM,QAAQ,iBAAwB,CAAA"}
@@ -1,4 +1,4 @@
1
- import { mixIdApi } from './mixIdApi';
1
+ import { api } from './api';
2
2
  class WebSocketClient {
3
3
  constructor() {
4
4
  Object.defineProperty(this, "ws", {
@@ -66,16 +66,14 @@ class WebSocketClient {
66
66
  if (this.isConnecting || (this.ws && this.ws.readyState === WebSocket.OPEN)) {
67
67
  return;
68
68
  }
69
- const config = mixIdApi.getConfig();
69
+ const config = api.getConfig();
70
70
  if (!config || !config.accessToken) {
71
71
  return;
72
72
  }
73
73
  this.isConnecting = true;
74
74
  try {
75
- const apiBase = config.apiBase || (typeof import.meta !== 'undefined' && import.meta.env?.VITE_MIX_ID_API_BASE)
76
- ? (import.meta.env?.VITE_MIX_ID_API_BASE || 'https://data-center.zorin.cloud/api')
77
- : 'https://data-center.zorin.cloud/api';
78
- const wsUrl = apiBase.replace(/^http/, 'ws').replace(/\/api$/, '') + '/ws';
75
+ const server = config.server || import.meta?.env?.VITE_MIX_ID_API_BASE || 'https://data-center.zorin.cloud/api';
76
+ const wsUrl = server.replace(/^http/, 'ws').replace(/\/api$/, '') + '/ws';
79
77
  const ws = new WebSocket(`${wsUrl}?token=${config.accessToken}`);
80
78
  ws.onopen = () => {
81
79
  this.ws = ws;
@@ -1,9 +1,9 @@
1
1
  export interface MixIdConnectionProps {
2
- onConnected?: () => void;
2
+ onConnected?: (code?: string) => void;
3
3
  onDisconnected?: () => void;
4
4
  showSyncSettings?: boolean;
5
5
  showSyncData?: boolean;
6
- apiBase?: string;
6
+ server?: string;
7
7
  clientId?: string;
8
8
  clientSecret?: string;
9
9
  notifications?: {
@@ -14,5 +14,5 @@ export interface MixIdConnectionProps {
14
14
  }) => void;
15
15
  };
16
16
  }
17
- export default function MixIdConnection({ onConnected, onDisconnected, showSyncSettings, showSyncData, apiBase, clientId, clientSecret, notifications, }: MixIdConnectionProps): import("react/jsx-runtime").JSX.Element;
17
+ export default function MixIdConnection({ onConnected, onDisconnected, showSyncSettings, showSyncData, server, clientId, clientSecret, notifications, }: MixIdConnectionProps): import("react/jsx-runtime").JSX.Element;
18
18
  //# sourceMappingURL=MixIdConnection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MixIdConnection.d.ts","sourceRoot":"","sources":["../../src/components/MixIdConnection.tsx"],"names":[],"mappings":"AAkBA,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;IACxB,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE;QACd,IAAI,EAAE,CAAC,OAAO,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAA;SAAE,KAAK,IAAI,CAAA;KAC5E,CAAA;CACF;AAED,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EACtC,WAAW,EACX,cAAc,EACd,gBAAuB,EACvB,YAAmB,EACnB,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,aAAa,GACd,EAAE,oBAAoB,2CAwStB"}
1
+ {"version":3,"file":"MixIdConnection.d.ts","sourceRoot":"","sources":["../../src/components/MixIdConnection.tsx"],"names":[],"mappings":"AAkBA,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IACrC,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE;QACd,IAAI,EAAE,CAAC,OAAO,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAA;SAAE,KAAK,IAAI,CAAA;KAC5E,CAAA;CACF;AAED,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EACtC,WAAW,EACX,cAAc,EACd,gBAAuB,EACvB,YAAmB,EACnB,MAAM,EACN,QAAQ,EACR,YAAY,EACZ,aAAa,GACd,EAAE,oBAAoB,2CAqStB"}
@@ -3,9 +3,9 @@ import { useState, useEffect } from 'react';
3
3
  import { Paper, Group, Button, Text, Badge, Modal, Stack, Switch, Alert, Loader, } from '@mantine/core';
4
4
  import { IconPlug, IconSettings, IconLogout, IconX } from '@tabler/icons-react';
5
5
  import { useDisclosure } from '@mantine/hooks';
6
- import { mixIdApi } from '../api/mixIdApi';
6
+ import { api } from '../api/api';
7
7
  import { useMixIdStatus } from '../hooks/useMixIdStatus';
8
- export default function MixIdConnection({ onConnected, onDisconnected, showSyncSettings = true, showSyncData = true, apiBase, clientId, clientSecret, notifications, }) {
8
+ export default function MixIdConnection({ onConnected, onDisconnected, showSyncSettings = true, showSyncData = true, server, clientId, clientSecret, notifications, }) {
9
9
  const { isConnected, syncStatus, hasConfig } = useMixIdStatus();
10
10
  const [loading, setLoading] = useState(true);
11
11
  const [syncStatusData, setSyncStatusData] = useState(null);
@@ -17,12 +17,12 @@ export default function MixIdConnection({ onConnected, onDisconnected, showSyncS
17
17
  }, []);
18
18
  const checkConnection = async () => {
19
19
  try {
20
- const config = mixIdApi.getConfig();
20
+ const config = api.getConfig();
21
21
  if (!config || !config.accessToken) {
22
22
  setSyncStatusData(null);
23
23
  return;
24
24
  }
25
- const status = await mixIdApi.getSyncStatus();
25
+ const status = await api.getSyncStatus();
26
26
  setSyncStatusData(status);
27
27
  setSyncSettings(status.syncSettings);
28
28
  setSyncData(status.syncData);
@@ -37,20 +37,13 @@ export default function MixIdConnection({ onConnected, onDisconnected, showSyncS
37
37
  const handleConnect = async () => {
38
38
  try {
39
39
  // Get config from props or environment
40
- const finalApiBase = apiBase ||
41
- (typeof import.meta !== 'undefined' && import.meta.env?.VITE_MIX_ID_API_BASE)
42
- ? (import.meta.env?.VITE_MIX_ID_API_BASE || 'https://data-center.zorin.cloud/api')
43
- : 'https://data-center.zorin.cloud/api';
40
+ const finalApiBase = server || import.meta?.env?.VITE_MIX_ID_API_BASE || 'https://data-center.zorin.cloud/api';
44
41
  const finalClientId = clientId ||
45
42
  (typeof import.meta !== 'undefined' && import.meta.env?.VITE_MIX_ID_CLIENT_ID)
46
43
  ? (import.meta.env?.VITE_MIX_ID_CLIENT_ID || '')
47
44
  : '';
48
- const finalClientSecret = clientSecret ||
49
- (typeof import.meta !== 'undefined' && import.meta.env?.VITE_MIX_ID_CLIENT_SECRET)
50
- ? (import.meta.env?.VITE_MIX_ID_CLIENT_SECRET || '')
51
- : '';
52
- if (!finalClientId || !finalClientSecret) {
53
- const message = 'MIX ID не настроен. Укажите VITE_MIX_ID_CLIENT_ID и VITE_MIX_ID_CLIENT_SECRET';
45
+ if (!finalClientId) {
46
+ const message = 'MIX ID не настроен. Укажите VITE_MIX_ID_CLIENT_ID (clientId)';
54
47
  if (notifications) {
55
48
  notifications.show({
56
49
  title: 'Ошибка',
@@ -63,14 +56,17 @@ export default function MixIdConnection({ onConnected, onDisconnected, showSyncS
63
56
  }
64
57
  return;
65
58
  }
66
- mixIdApi.setConfig({
67
- apiBase: finalApiBase || 'https://data-center.zorin.cloud/api',
59
+ // Не сохраняем clientSecret в клиентском приложении.
60
+ // Если у вас есть clientSecret, выполняйте обмен кода на сервере (server-side),
61
+ // либо включите PKCE на бэкенде. Для desktop-приложений (Electron) можно
62
+ // передать `clientSecret` через безопасный адаптер хранения (secure native store).
63
+ api.setConfig({
64
+ server: finalApiBase || 'https://data-center.zorin.cloud/api',
68
65
  clientId: finalClientId,
69
- clientSecret: finalClientSecret
70
66
  });
71
67
  // Initiate OAuth flow
72
68
  const redirectUri = typeof window !== 'undefined' ? window.location.origin + '/mixid-callback' : '';
73
- const { authorizationUrl, code } = await mixIdApi.initiateOAuth(redirectUri);
69
+ const { authorizationUrl, code } = await api.initiateOAuth(redirectUri);
74
70
  // Open OAuth window
75
71
  if (typeof window === 'undefined')
76
72
  return;
@@ -88,8 +84,10 @@ export default function MixIdConnection({ onConnected, onDisconnected, showSyncS
88
84
  oauthWindow?.close();
89
85
  try {
90
86
  const { code: callbackCode } = event.data;
91
- await mixIdApi.exchangeCodeForToken(callbackCode || code, redirectUri);
92
- // Dispatch event to trigger WebSocket connection and status update
87
+ // Delegate token exchange to the embedding app/backend for security.
88
+ // Return the authorization code to the parent via callback.
89
+ onConnected?.(callbackCode || code);
90
+ // Trigger status update so parent can check connection state
93
91
  window.dispatchEvent(new Event('mixid-config-changed'));
94
92
  await checkConnection();
95
93
  if (notifications) {
@@ -99,7 +97,6 @@ export default function MixIdConnection({ onConnected, onDisconnected, showSyncS
99
97
  color: 'green',
100
98
  });
101
99
  }
102
- onConnected?.();
103
100
  openSettings();
104
101
  }
105
102
  catch (error) {
@@ -145,7 +142,7 @@ export default function MixIdConnection({ onConnected, onDisconnected, showSyncS
145
142
  return;
146
143
  if (!confirm('Вы уверены, что хотите отключить MIX ID?'))
147
144
  return;
148
- mixIdApi.clearConfig();
145
+ api.clearConfig();
149
146
  // Dispatch event to trigger WebSocket disconnection and status update
150
147
  window.dispatchEvent(new Event('mixid-config-changed'));
151
148
  setSyncStatusData(null);
@@ -160,7 +157,7 @@ export default function MixIdConnection({ onConnected, onDisconnected, showSyncS
160
157
  };
161
158
  const handleSaveSettings = async () => {
162
159
  try {
163
- await mixIdApi.updateSyncPreferences(syncSettings, syncData);
160
+ await api.updateSyncPreferences(syncSettings, syncData);
164
161
  if (notifications) {
165
162
  notifications.show({
166
163
  title: 'Успешно',
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useCallback, useState } from 'react';
2
- import { mixIdApi } from '../api/mixIdApi';
2
+ import { api } from '../api/api';
3
3
  import { wsClient } from '../api/websocket';
4
4
  export function useMixIdSession(options = {}) {
5
5
  const { onSessionDeleted, onSessionExpired, onSessionInvalid, heartbeatInterval = 30 * 1000, // 30 seconds
@@ -7,11 +7,11 @@ export function useMixIdSession(options = {}) {
7
7
  const [currentSessionId, setCurrentSessionId] = useState(null);
8
8
  const sendHeartbeat = useCallback(async () => {
9
9
  try {
10
- const config = mixIdApi.getConfig();
10
+ const config = api.getConfig();
11
11
  if (!config || !config.accessToken) {
12
12
  return;
13
13
  }
14
- const result = await mixIdApi.heartbeat({
14
+ const result = await api.heartbeat({
15
15
  platform: typeof navigator !== 'undefined' ? navigator.platform : 'unknown',
16
16
  userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'unknown',
17
17
  timestamp: new Date().toISOString(),
@@ -26,13 +26,13 @@ export function useMixIdSession(options = {}) {
26
26
  // If heartbeat fails with 401/403, session might be deleted or expired
27
27
  if (error instanceof Error) {
28
28
  if (error.message.includes('401') || error.message.includes('403')) {
29
- mixIdApi.clearConfig();
29
+ api.clearConfig();
30
30
  wsClient.disconnect();
31
31
  onSessionExpired?.();
32
32
  }
33
33
  else if (error.message.includes('404')) {
34
34
  // Session not found - might have been deleted
35
- mixIdApi.clearConfig();
35
+ api.clearConfig();
36
36
  wsClient.disconnect();
37
37
  onSessionDeleted?.();
38
38
  }
@@ -44,18 +44,18 @@ export function useMixIdSession(options = {}) {
44
44
  }, [onSessionDeleted, onSessionExpired, onSessionInvalid]);
45
45
  const checkSession = useCallback(async () => {
46
46
  try {
47
- const config = mixIdApi.getConfig();
47
+ const config = api.getConfig();
48
48
  if (!config || !config.accessToken) {
49
49
  return;
50
50
  }
51
51
  // Try to get sessions to verify current session exists
52
- const sessions = await mixIdApi.getSessions();
52
+ const sessions = await api.getSessions();
53
53
  // If we have a current session ID, check if it still exists
54
54
  if (currentSessionId) {
55
55
  const sessionExists = sessions.some(s => s.id === currentSessionId);
56
56
  if (!sessionExists) {
57
57
  // Session was deleted
58
- mixIdApi.clearConfig();
58
+ api.clearConfig();
59
59
  wsClient.disconnect();
60
60
  onSessionDeleted?.();
61
61
  }
@@ -64,14 +64,14 @@ export function useMixIdSession(options = {}) {
64
64
  catch (error) {
65
65
  console.error('Failed to check session:', error);
66
66
  if (error instanceof Error && (error.message.includes('401') || error.message.includes('403'))) {
67
- mixIdApi.clearConfig();
67
+ api.clearConfig();
68
68
  wsClient.disconnect();
69
69
  onSessionExpired?.();
70
70
  }
71
71
  }
72
72
  }, [currentSessionId, onSessionDeleted, onSessionExpired]);
73
73
  useEffect(() => {
74
- const config = mixIdApi.getConfig();
74
+ const config = api.getConfig();
75
75
  if (!config || !config.accessToken) {
76
76
  return;
77
77
  }
@@ -85,7 +85,7 @@ export function useMixIdSession(options = {}) {
85
85
  if (message.sessionId) {
86
86
  if (currentSessionId && message.sessionId === currentSessionId) {
87
87
  // Our session was deleted
88
- mixIdApi.clearConfig();
88
+ api.clearConfig();
89
89
  wsClient.disconnect();
90
90
  onSessionDeleted?.();
91
91
  }
@@ -97,7 +97,7 @@ export function useMixIdSession(options = {}) {
97
97
  }
98
98
  };
99
99
  const handleSessionExpired = () => {
100
- mixIdApi.clearConfig();
100
+ api.clearConfig();
101
101
  wsClient.disconnect();
102
102
  onSessionExpired?.();
103
103
  };
@@ -1,5 +1,5 @@
1
1
  import { useState, useEffect, useCallback } from 'react';
2
- import { mixIdApi } from '../api/mixIdApi';
2
+ import { api } from '../api/api';
3
3
  import { wsClient } from '../api/websocket';
4
4
  export function useMixIdStatus() {
5
5
  const [isConnected, setIsConnected] = useState(false);
@@ -7,7 +7,7 @@ export function useMixIdStatus() {
7
7
  const [hasConfig, setHasConfig] = useState(false);
8
8
  const checkStatus = useCallback(async () => {
9
9
  try {
10
- const config = mixIdApi.getConfig();
10
+ const config = api.getConfig();
11
11
  const hasConfigValue = !!(config && config.accessToken);
12
12
  setHasConfig(hasConfigValue);
13
13
  if (!hasConfigValue) {
@@ -24,7 +24,7 @@ export function useMixIdStatus() {
24
24
  else {
25
25
  // Check if we can use REST API (try to get sync status)
26
26
  try {
27
- await mixIdApi.getSyncStatus();
27
+ await api.getSyncStatus();
28
28
  setIsConnected(true);
29
29
  setSyncStatus('connected-rest');
30
30
  }
@@ -44,9 +44,9 @@ export function useMixIdStatus() {
44
44
  checkStatus();
45
45
  // Check periodically
46
46
  const interval = setInterval(checkStatus, 2000); // Check every 2 seconds
47
- // Listen to storage changes (for cross-tab updates)
47
+ // Listen to storage changes (для cross-tab обновлений, если приложение использует персистентное хранилище)
48
48
  const handleStorageChange = (e) => {
49
- if (e.key === 'mixId_config' || e.key === 'mixId_accessToken' || e.key === 'mixId_refreshToken') {
49
+ if (e.key === 'mixid_config' || e.key === 'mixId_config') {
50
50
  checkStatus();
51
51
  }
52
52
  };
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useRef, useCallback } from 'react';
2
- import { mixIdApi } from '../api/mixIdApi';
2
+ import { api } from '../api/api';
3
3
  import { wsClient } from '../api/websocket';
4
4
  import { offlineQueue } from '../api/offlineQueue';
5
5
  const SYNC_INTERVAL = 5 * 60 * 1000; // 5 minutes (fallback HTTP sync)
@@ -32,10 +32,10 @@ export function useMixIdSync(options = {}) {
32
32
  // Upload settings
33
33
  const uploadSettings = useCallback(async (settingsToUpload, version) => {
34
34
  try {
35
- const syncStatus = await mixIdApi.getSyncStatus();
35
+ const syncStatus = await api.getSyncStatus();
36
36
  if (!syncStatus.syncSettings)
37
37
  return;
38
- const result = await mixIdApi.uploadSettings(settingsToUpload);
38
+ const result = await api.uploadSettings(settingsToUpload);
39
39
  lastSettingsVersionRef.current = result.version;
40
40
  lastSettingsUpdateRef.current = Date.now();
41
41
  // Send via WebSocket for real-time sync
@@ -58,10 +58,10 @@ export function useMixIdSync(options = {}) {
58
58
  // Upload data
59
59
  const uploadData = useCallback(async (dataType, data) => {
60
60
  try {
61
- const syncStatus = await mixIdApi.getSyncStatus();
61
+ const syncStatus = await api.getSyncStatus();
62
62
  if (!syncStatus.syncData)
63
63
  return;
64
- await mixIdApi.uploadData(dataType, data);
64
+ await api.uploadData(dataType, data);
65
65
  // Send via WebSocket for real-time sync
66
66
  if (wsClient.isConnected()) {
67
67
  wsClient.send({
@@ -91,19 +91,19 @@ export function useMixIdSync(options = {}) {
91
91
  // Perform sync
92
92
  const performSync = useCallback(async () => {
93
93
  try {
94
- const config = mixIdApi.getConfig();
94
+ const config = api.getConfig();
95
95
  if (!config || !config.accessToken) {
96
96
  return;
97
97
  }
98
98
  // Get sync status
99
- const syncStatus = await mixIdApi.getSyncStatus();
99
+ const syncStatus = await api.getSyncStatus();
100
100
  // Check for updates
101
- const updates = await mixIdApi.checkUpdates(lastSettingsVersionRef.current, syncStatus.syncData && dataTypes.length > 0 ? dataTypes : undefined);
101
+ const updates = await api.checkUpdates(lastSettingsVersionRef.current, syncStatus.syncData && dataTypes.length > 0 ? dataTypes : undefined);
102
102
  // Download updates if available
103
103
  if (updates.hasUpdates) {
104
104
  if (updates.updates.settings && syncStatus.syncSettings && getLocalSettings && saveLocalSettings) {
105
105
  try {
106
- const remoteSettings = await mixIdApi.downloadSettings();
106
+ const remoteSettings = await api.downloadSettings();
107
107
  const localSettings = getLocalSettings();
108
108
  const merged = mergeWithConflictResolution(localSettings, remoteSettings.settings, remoteSettings.updatedAt);
109
109
  await saveLocalSettings(merged);
@@ -118,7 +118,7 @@ export function useMixIdSync(options = {}) {
118
118
  for (const dataType of dataTypes) {
119
119
  if (updates.updates.data[dataType] && getLocalData && saveLocalData) {
120
120
  try {
121
- const remoteData = await mixIdApi.downloadData(dataType);
121
+ const remoteData = await api.downloadData(dataType);
122
122
  const localData = await getLocalData(dataType);
123
123
  // Merge with conflict resolution
124
124
  const merged = { ...localData, ...remoteData.data };
@@ -171,7 +171,7 @@ export function useMixIdSync(options = {}) {
171
171
  ]);
172
172
  useEffect(() => {
173
173
  const setupSync = () => {
174
- const config = mixIdApi.getConfig();
174
+ const config = api.getConfig();
175
175
  if (!config || !config.accessToken) {
176
176
  // Disconnect WebSocket if config is cleared
177
177
  wsClient.disconnect();
@@ -188,7 +188,7 @@ export function useMixIdSync(options = {}) {
188
188
  };
189
189
  if (typeof window !== 'undefined') {
190
190
  window.addEventListener('mixid-config-changed', handleConfigChange);
191
- const config = mixIdApi.getConfig();
191
+ const config = api.getConfig();
192
192
  if (!config || !config.accessToken) {
193
193
  return () => {
194
194
  window.removeEventListener('mixid-config-changed', handleConfigChange);
@@ -225,7 +225,7 @@ export function useMixIdSync(options = {}) {
225
225
  syncIntervalRef.current = setInterval(performSync, SYNC_INTERVAL);
226
226
  // Set up heartbeat
227
227
  heartbeatIntervalRef.current = setInterval(() => {
228
- mixIdApi.heartbeat({
228
+ api.heartbeat({
229
229
  platform: typeof navigator !== 'undefined' ? navigator.platform : 'unknown',
230
230
  userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'unknown',
231
231
  }).catch(console.error);
@@ -1 +1 @@
1
- {"version":3,"file":"useNotifications.d.ts","sourceRoot":"","sources":["../../src/hooks/useNotifications.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;CAClB;AAID,wBAAgB,gBAAgB;;;iCAiDL,MAAM;;;EAiHhC"}
1
+ {"version":3,"file":"useNotifications.d.ts","sourceRoot":"","sources":["../../src/hooks/useNotifications.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;CAClB;AAID,wBAAgB,gBAAgB;;;iCA8CL,MAAM;;;EA6GhC"}
@@ -1,5 +1,5 @@
1
1
  import { useState, useEffect, useCallback } from 'react';
2
- import { mixIdApi } from '../api/mixIdApi';
2
+ import { api } from '../api/api';
3
3
  import { wsClient } from '../api/websocket';
4
4
  const NOTIFICATIONS_STORAGE_KEY = 'mixId_notifications';
5
5
  export function useNotifications() {
@@ -25,14 +25,12 @@ export function useNotifications() {
25
25
  }, []);
26
26
  const fetchNotifications = useCallback(async () => {
27
27
  try {
28
- const config = mixIdApi.getConfig();
28
+ const config = api.getConfig();
29
29
  if (!config || !config.accessToken) {
30
30
  return;
31
31
  }
32
- const apiBase = config.apiBase || (typeof import.meta !== 'undefined' && import.meta.env?.VITE_MIX_ID_API_BASE)
33
- ? (import.meta.env?.VITE_MIX_ID_API_BASE || 'https://data-center.zorin.cloud/api')
34
- : 'https://data-center.zorin.cloud/api';
35
- const response = await fetch(`${apiBase}/notifications`, {
32
+ const server = config.server || import.meta?.env?.VITE_MIX_ID_API_BASE || 'https://data-center.zorin.cloud/api';
33
+ const response = await fetch(`${server}/notifications`, {
36
34
  headers: {
37
35
  Authorization: `Bearer ${config.accessToken}`,
38
36
  },
@@ -48,7 +46,7 @@ export function useNotifications() {
48
46
  }, [saveNotifications]);
49
47
  const markAsRead = useCallback(async (notificationId) => {
50
48
  try {
51
- const config = mixIdApi.getConfig();
49
+ const config = api.getConfig();
52
50
  if (!config || !config.accessToken) {
53
51
  return;
54
52
  }
@@ -63,10 +61,8 @@ export function useNotifications() {
63
61
  });
64
62
  }
65
63
  // Also send via HTTP as fallback
66
- const apiBase = config.apiBase || (typeof import.meta !== 'undefined' && import.meta.env?.VITE_MIX_ID_API_BASE)
67
- ? (import.meta.env?.VITE_MIX_ID_API_BASE || 'https://data-center.zorin.cloud/api')
68
- : 'https://data-center.zorin.cloud/api';
69
- await fetch(`${apiBase}/notifications/${notificationId}/read`, {
64
+ const server = config.server || import.meta?.env?.VITE_MIX_ID_API_BASE || 'https://data-center.zorin.cloud/api';
65
+ await fetch(`${server}/notifications/${notificationId}/read`, {
70
66
  method: 'PUT',
71
67
  headers: {
72
68
  Authorization: `Bearer ${config.accessToken}`,
@@ -79,7 +75,7 @@ export function useNotifications() {
79
75
  }, [notifications, saveNotifications]);
80
76
  const markAllAsRead = useCallback(async () => {
81
77
  try {
82
- const config = mixIdApi.getConfig();
78
+ const config = api.getConfig();
83
79
  if (!config || !config.accessToken) {
84
80
  return;
85
81
  }
@@ -87,10 +83,8 @@ export function useNotifications() {
87
83
  const updated = notifications.map((n) => ({ ...n, read: true }));
88
84
  saveNotifications(updated);
89
85
  // Send via HTTP
90
- const apiBase = config.apiBase || (typeof import.meta !== 'undefined' && import.meta.env?.VITE_MIX_ID_API_BASE)
91
- ? (import.meta.env?.VITE_MIX_ID_API_BASE || 'https://data-center.zorin.cloud/api')
92
- : 'https://data-center.zorin.cloud/api';
93
- await fetch(`${apiBase}/notifications/read-all`, {
86
+ const server = config.server || import.meta?.env?.VITE_MIX_ID_API_BASE || 'https://data-center.zorin.cloud/api';
87
+ await fetch(`${server}/notifications/read-all`, {
94
88
  method: 'PUT',
95
89
  headers: {
96
90
  Authorization: `Bearer ${config.accessToken}`,
@@ -102,7 +96,7 @@ export function useNotifications() {
102
96
  }
103
97
  }, [notifications, saveNotifications]);
104
98
  useEffect(() => {
105
- const config = mixIdApi.getConfig();
99
+ const config = api.getConfig();
106
100
  if (!config || !config.accessToken) {
107
101
  // Clear notifications if MIX ID is not connected
108
102
  saveNotifications([]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@localzet/data-connector",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "MIX ID connector library for React/Electron applications with real-time sync, notifications, and session management",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1 +0,0 @@
1
- {"version":3,"file":"mixIdApi.d.ts","sourceRoot":"","sources":["../../src/api/mixIdApi.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,cAAM,QAAQ;IACZ,OAAO,CAAC,MAAM,CAA2B;IAEzC,SAAS,CAAC,MAAM,EAAE,WAAW;IAmB7B,SAAS,IAAI,WAAW,GAAG,IAAI;IA2B/B,WAAW;YAYG,OAAO;YAkDP,kBAAkB;IA0C1B,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,gBAAgB,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAmBvG,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QACtE,YAAY,EAAE,MAAM,CAAA;QACpB,aAAa,EAAE,MAAM,CAAA;QACrB,UAAU,EAAE,MAAM,CAAA;QAClB,UAAU,EAAE,MAAM,CAAA;KACnB,CAAC;IA4CI,aAAa,IAAI,OAAO,CAAC;QAC7B,YAAY,EAAE,OAAO,CAAA;QACrB,QAAQ,EAAE,OAAO,CAAA;QACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAC1B,CAAC;IAII,qBAAqB,CAAC,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAO9F,cAAc,CAAC,QAAQ,EAAE,GAAG,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAO7E,gBAAgB,IAAI,OAAO,CAAC;QAAE,QAAQ,EAAE,GAAG,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAIlF,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAkCtF,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAIxF,YAAY,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAC1E,OAAO,EAAE;YACP,QAAQ,CAAC,EAAE;gBAAE,OAAO,EAAE,MAAM,CAAC;gBAAC,SAAS,EAAE,MAAM,CAAA;aAAE,CAAA;YACjD,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;gBAAE,SAAS,EAAE,MAAM,CAAA;aAAE,CAAC,CAAA;SAC7C,CAAA;QACD,UAAU,EAAE,OAAO,CAAA;KACpB,CAAC;IAQI,SAAS,CAAC,UAAU,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAQ1D,WAAW,IAAI,OAAO,CAAC,KAAK,CAAC;QACjC,EAAE,EAAE,MAAM,CAAA;QACV,UAAU,EAAE,GAAG,CAAA;QACf,cAAc,EAAE,MAAM,CAAA;QACtB,SAAS,EAAE,MAAM,CAAA;KAClB,CAAC,CAAC;IAIG,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;CAKtE;AAED,eAAO,MAAM,QAAQ,UAAiB,CAAA"}