1
1
import { net } from 'electron' ;
2
- import Store from 'electron-store' ;
3
2
4
3
interface SecureStore {
5
4
credentials ?: {
@@ -8,11 +7,20 @@ interface SecureStore {
8
7
} ;
9
8
}
10
9
11
- // Secure encrypted storage for credentials
12
- const store = new Store < SecureStore > ( {
13
- name : 'datalayer-secure' ,
14
- encryptionKey : 'datalayer-electron-app' , // In production, use a more secure key
15
- } ) ;
10
+ // Store instance will be initialized lazily
11
+ let store : any = null ;
12
+
13
+ // Initialize store asynchronously
14
+ async function getStore ( ) {
15
+ if ( ! store ) {
16
+ const { default : Store } = await import ( 'electron-store' ) ;
17
+ store = new Store < SecureStore > ( {
18
+ name : 'datalayer-secure' ,
19
+ encryptionKey : 'datalayer-electron-app' , // In production, use a more secure key
20
+ } ) ;
21
+ }
22
+ return store ;
23
+ }
16
24
17
25
// Whitelist of allowed domains for API requests
18
26
const ALLOWED_DOMAINS = [
@@ -25,8 +33,13 @@ class DatalayerAPIService {
25
33
private token : string = '' ;
26
34
27
35
constructor ( ) {
28
- // Load stored credentials on startup
29
- const stored = store . get ( 'credentials' ) ;
36
+ // Load stored credentials on startup (async)
37
+ this . loadCredentials ( ) ;
38
+ }
39
+
40
+ private async loadCredentials ( ) {
41
+ const storeInstance = await getStore ( ) ;
42
+ const stored = storeInstance . get ( 'credentials' ) ;
30
43
if ( stored ) {
31
44
this . baseUrl = stored . runUrl ;
32
45
this . token = stored . token ;
@@ -69,7 +82,8 @@ class DatalayerAPIService {
69
82
this . baseUrl = runUrl ;
70
83
this . token = token ;
71
84
72
- store . set ( 'credentials' , { runUrl, token } ) ;
85
+ const storeInstance = await getStore ( ) ;
86
+ storeInstance . set ( 'credentials' , { runUrl, token } ) ;
73
87
74
88
// Test the credentials by fetching environments
75
89
const testResponse = await this . getEnvironments ( ) ;
@@ -90,7 +104,8 @@ class DatalayerAPIService {
90
104
async logout ( ) : Promise < { success : boolean } > {
91
105
this . token = '' ;
92
106
this . baseUrl = 'https://prod1.datalayer.run' ;
93
- store . delete ( 'credentials' ) ;
107
+ const storeInstance = await getStore ( ) ;
108
+ storeInstance . delete ( 'credentials' ) ;
94
109
return { success : true } ;
95
110
}
96
111
@@ -242,6 +257,211 @@ class DatalayerAPIService {
242
257
}
243
258
}
244
259
260
+ /**
261
+ * Get user spaces
262
+ */
263
+ async getUserSpaces ( ) : Promise < {
264
+ success : boolean ;
265
+ data ?: any [ ] ;
266
+ error ?: string ;
267
+ } > {
268
+ try {
269
+ const response = await this . request ( '/api/spacer/v1/spaces/users/me' ) ;
270
+ return { success : true , data : response . spaces || [ ] } ;
271
+ } catch ( error ) {
272
+ console . error ( 'Failed to fetch user spaces:' , error ) ;
273
+ return {
274
+ success : false ,
275
+ error :
276
+ error instanceof Error ? error . message : 'Failed to fetch spaces' ,
277
+ } ;
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Get all items in a space
283
+ */
284
+ async getSpaceItems ( spaceId : string ) : Promise < {
285
+ success : boolean ;
286
+ data ?: any [ ] ;
287
+ error ?: string ;
288
+ } > {
289
+ try {
290
+ const response = await this . request (
291
+ `/api/spacer/v1/spaces/${ spaceId } /items`
292
+ ) ;
293
+ // The response has items directly or nested in the response
294
+ return { success : true , data : response . items || response || [ ] } ;
295
+ } catch ( error ) {
296
+ console . error ( 'Failed to fetch space items:' , error ) ;
297
+ return {
298
+ success : false ,
299
+ error :
300
+ error instanceof Error ? error . message : 'Failed to fetch items' ,
301
+ } ;
302
+ }
303
+ }
304
+
305
+ /**
306
+ * List notebooks in a space
307
+ */
308
+ async listNotebooks ( spaceId ?: string ) : Promise < {
309
+ success : boolean ;
310
+ data ?: any [ ] ;
311
+ spaceInfo ?: any ;
312
+ error ?: string ;
313
+ } > {
314
+ try {
315
+ console . log ( 'listNotebooks: Starting notebook fetch...' ) ;
316
+ console . log ( 'listNotebooks: Current token:' , this . token ? 'Set' : 'Not set' ) ;
317
+ console . log ( 'listNotebooks: Current baseUrl:' , this . baseUrl ) ;
318
+
319
+ // Check if we're authenticated
320
+ if ( ! this . token ) {
321
+ console . log ( 'listNotebooks: No token available, returning mock data' ) ;
322
+ // Return mock data for demo
323
+ return {
324
+ success : true ,
325
+ data : [
326
+ {
327
+ uid : 'mock-1' ,
328
+ name_t : 'Sample Data Analysis.ipynb' ,
329
+ creation_ts_dt : new Date ( Date . now ( ) - 86400000 ) . toISOString ( ) ,
330
+ last_update_ts_dt : new Date ( ) . toISOString ( ) ,
331
+ type : 'notebook' ,
332
+ } ,
333
+ {
334
+ uid : 'mock-2' ,
335
+ name_t : 'Machine Learning Tutorial.ipynb' ,
336
+ creation_ts_dt : new Date ( Date . now ( ) - 172800000 ) . toISOString ( ) ,
337
+ last_update_ts_dt : new Date ( Date . now ( ) - 3600000 ) . toISOString ( ) ,
338
+ type : 'notebook' ,
339
+ } ,
340
+ {
341
+ uid : 'mock-3' ,
342
+ name_t : 'Data Visualization.ipynb' ,
343
+ creation_ts_dt : new Date ( Date . now ( ) - 259200000 ) . toISOString ( ) ,
344
+ last_update_ts_dt : new Date ( Date . now ( ) - 7200000 ) . toISOString ( ) ,
345
+ type : 'notebook' ,
346
+ } ,
347
+ ] ,
348
+ error : 'Using mock data - not authenticated'
349
+ } ;
350
+ }
351
+
352
+ // First get user spaces if no spaceId provided
353
+ let selectedSpace : any ;
354
+
355
+ if ( ! spaceId ) {
356
+ console . log ( 'listNotebooks: No spaceId provided, fetching user spaces...' ) ;
357
+ const spacesResponse = await this . getUserSpaces ( ) ;
358
+ console . log ( 'listNotebooks: Spaces response:' , JSON . stringify ( spacesResponse , null , 2 ) ) ;
359
+
360
+ if ( spacesResponse . success && spacesResponse . data && spacesResponse . data . length > 0 ) {
361
+ // Find default space or one called "library"
362
+ selectedSpace = spacesResponse . data . find (
363
+ ( space : any ) =>
364
+ space . handle === 'library' ||
365
+ space . name === 'Library' ||
366
+ space . is_default === true
367
+ ) || spacesResponse . data [ 0 ] ;
368
+
369
+ console . log ( 'listNotebooks: Selected space:' , selectedSpace ) ;
370
+
371
+ if ( selectedSpace ) {
372
+ spaceId = selectedSpace . id || selectedSpace . uid ;
373
+ }
374
+ } else {
375
+ console . log ( 'listNotebooks: No spaces found, using mock data' ) ;
376
+ // Return mock data if no spaces
377
+ return {
378
+ success : true ,
379
+ data : [
380
+ {
381
+ uid : 'mock-1' ,
382
+ name_t : 'Welcome to Datalayer.ipynb' ,
383
+ creation_ts_dt : new Date ( ) . toISOString ( ) ,
384
+ last_update_ts_dt : new Date ( ) . toISOString ( ) ,
385
+ type : 'notebook' ,
386
+ } ,
387
+ ] ,
388
+ error : spacesResponse . error || 'No spaces available'
389
+ } ;
390
+ }
391
+ }
392
+
393
+ if ( ! spaceId ) {
394
+ console . log ( 'listNotebooks: No space available, returning mock data' ) ;
395
+ return {
396
+ success : true ,
397
+ data : [
398
+ {
399
+ uid : 'mock-1' ,
400
+ name_t : 'Getting Started.ipynb' ,
401
+ creation_ts_dt : new Date ( ) . toISOString ( ) ,
402
+ last_update_ts_dt : new Date ( ) . toISOString ( ) ,
403
+ type : 'notebook' ,
404
+ } ,
405
+ ] ,
406
+ error : 'No spaces available'
407
+ } ;
408
+ }
409
+
410
+ console . log ( `listNotebooks: Fetching items from space ${ spaceId } ...` ) ;
411
+
412
+ // Fetch all items from the space
413
+ const itemsResponse = await this . getSpaceItems ( spaceId ) ;
414
+ console . log ( 'listNotebooks: Items response:' , JSON . stringify ( itemsResponse , null , 2 ) ) ;
415
+
416
+ if ( itemsResponse . success && itemsResponse . data && itemsResponse . data . length > 0 ) {
417
+ // Filter only notebook items - the field is type_s in the actual response
418
+ const notebooks = itemsResponse . data . filter (
419
+ ( item : any ) => item . type === 'notebook' || item . type_s === 'notebook' || item . item_type === 'notebook'
420
+ ) ;
421
+
422
+ console . log ( `listNotebooks: Found ${ notebooks . length } notebooks out of ${ itemsResponse . data . length } items` ) ;
423
+
424
+ if ( notebooks . length > 0 ) {
425
+ return {
426
+ success : true ,
427
+ data : notebooks ,
428
+ spaceInfo : selectedSpace
429
+ } ;
430
+ }
431
+ }
432
+
433
+ // Fallback to specific notebook endpoint
434
+ console . log ( 'listNotebooks: Trying fallback notebook endpoint...' ) ;
435
+ const response = await this . request (
436
+ `/api/spacer/v1/spaces/${ spaceId } /items/types/notebook`
437
+ ) ;
438
+
439
+ console . log ( 'listNotebooks: Fallback response:' , JSON . stringify ( response , null , 2 ) ) ;
440
+
441
+ return {
442
+ success : true ,
443
+ data : response . items || response || [ ] ,
444
+ spaceInfo : selectedSpace
445
+ } ;
446
+ } catch ( error ) {
447
+ console . error ( 'listNotebooks: Error fetching notebooks:' , error ) ;
448
+ // Return mock data on error
449
+ return {
450
+ success : true ,
451
+ data : [
452
+ {
453
+ uid : 'error-mock-1' ,
454
+ name_t : 'Example Notebook.ipynb' ,
455
+ creation_ts_dt : new Date ( ) . toISOString ( ) ,
456
+ last_update_ts_dt : new Date ( ) . toISOString ( ) ,
457
+ type : 'notebook' ,
458
+ } ,
459
+ ] ,
460
+ error : error instanceof Error ? error . message : 'Failed to fetch notebooks' ,
461
+ } ;
462
+ }
463
+ }
464
+
245
465
/**
246
466
* Generic API request handler for other endpoints
247
467
*/
0 commit comments