0
# Service Tokens
1
2
Dependency injection tokens for service-based architecture, enabling loose coupling and extensibility in the plugin system.
3
4
## Capabilities
5
6
### IConnectionLost Token
7
8
Service token and interface for handling server connection failures with customizable dialog behavior.
9
10
```typescript { .api }
11
/**
12
* Connection lost handler function type
13
* @param manager - Service manager instance
14
* @param err - Network error that occurred
15
* @param translator - Optional translator for internationalization
16
* @returns Promise resolving when handling is complete
17
*/
18
type IConnectionLost = (
19
manager: ServiceManager.IManager,
20
err: ServerConnection.NetworkError,
21
translator?: ITranslator
22
) => Promise<void>;
23
24
/**
25
* Service token for connection lost handling
26
*/
27
const IConnectionLost: Token<IConnectionLost>;
28
```
29
30
**Usage Examples:**
31
32
```typescript
33
import { IConnectionLost } from "@jupyterlab/application";
34
import { JupyterFrontEndPlugin } from "@jupyterlab/application";
35
36
// Using connection lost service in a plugin
37
const connectionPlugin: JupyterFrontEndPlugin<void> = {
38
id: 'my-connection-plugin',
39
autoStart: true,
40
requires: [IConnectionLost],
41
activate: (app, connectionLost: IConnectionLost) => {
42
// Monitor service manager for connection issues
43
app.serviceManager.connectionFailure.connect(async (sender, err) => {
44
await connectionLost(app.serviceManager, err);
45
});
46
}
47
};
48
49
// Providing custom connection lost handler
50
const customConnectionLostPlugin: JupyterFrontEndPlugin<IConnectionLost> = {
51
id: 'custom-connection-lost',
52
provides: IConnectionLost,
53
activate: (app) => {
54
return async (manager, err, translator) => {
55
// Custom connection lost handling
56
console.error('Connection lost:', err.message);
57
58
// Show custom dialog or notification
59
const dialog = document.createElement('div');
60
dialog.textContent = 'Connection to server lost. Attempting to reconnect...';
61
document.body.appendChild(dialog);
62
63
// Attempt reconnection
64
try {
65
await manager.ready;
66
dialog.remove();
67
} catch (reconnectErr) {
68
dialog.textContent = 'Failed to reconnect. Please refresh the page.';
69
}
70
};
71
}
72
};
73
```
74
75
### ILabStatus Token
76
77
Service token and interface for managing application busy and dirty states with reactive signals.
78
79
```typescript { .api }
80
/**
81
* Application status management interface
82
*/
83
interface ILabStatus {
84
/** Signal emitted when application busy state changes */
85
readonly busySignal: ISignal<JupyterFrontEnd<any, any>, boolean>;
86
87
/** Signal emitted when application dirty state changes */
88
readonly dirtySignal: ISignal<JupyterFrontEnd<any, any>, boolean>;
89
90
/** Whether the application is currently busy */
91
readonly isBusy: boolean;
92
93
/** Whether the application has unsaved changes */
94
readonly isDirty: boolean;
95
96
/**
97
* Set the application state to busy
98
* @returns A disposable used to clear the busy state for the caller
99
*/
100
setBusy(): IDisposable;
101
102
/**
103
* Set the application state to dirty
104
* @returns A disposable used to clear the dirty state for the caller
105
*/
106
setDirty(): IDisposable;
107
}
108
109
/**
110
* Service token for application status management
111
*/
112
const ILabStatus: Token<ILabStatus>;
113
```
114
115
**Usage Examples:**
116
117
```typescript
118
import { ILabStatus } from "@jupyterlab/application";
119
import { JupyterFrontEndPlugin } from "@jupyterlab/application";
120
121
// Using status service in a plugin
122
const statusPlugin: JupyterFrontEndPlugin<void> = {
123
id: 'my-status-plugin',
124
autoStart: true,
125
requires: [ILabStatus],
126
activate: (app, status: ILabStatus) => {
127
// Listen to status changes
128
status.busySignal.connect((sender, isBusy) => {
129
console.log('Application busy state:', isBusy);
130
// Update UI (e.g., show/hide spinner)
131
document.body.classList.toggle('busy', isBusy);
132
});
133
134
status.dirtySignal.connect((sender, isDirty) => {
135
console.log('Application dirty state:', isDirty);
136
// Update UI (e.g., show unsaved indicator)
137
document.title = isDirty ? '• My App' : 'My App';
138
});
139
140
// Set busy state during long operations
141
const performLongOperation = async () => {
142
const busyDisposable = status.setBusy();
143
try {
144
await someAsyncOperation();
145
} finally {
146
busyDisposable.dispose(); // Clear busy state
147
}
148
};
149
150
// Set dirty state when content changes
151
const handleContentChange = () => {
152
const dirtyDisposable = status.setDirty();
153
// Keep the disposable until content is saved
154
return dirtyDisposable;
155
};
156
}
157
};
158
```
159
160
### IRouter Token
161
162
Service token for URL routing functionality (covered in detail in URL Routing documentation).
163
164
```typescript { .api }
165
/**
166
* Service token for URL router
167
*/
168
const IRouter: Token<IRouter>;
169
170
/**
171
* URL routing service interface
172
*/
173
interface IRouter {
174
readonly base: string;
175
readonly commands: CommandRegistry;
176
readonly current: IRouter.ILocation;
177
readonly routed: ISignal<IRouter, IRouter.ILocation>;
178
readonly stop: Token<void>;
179
navigate(path: string, options?: IRouter.INavOptions): void;
180
register(options: IRouter.IRegisterOptions): IDisposable;
181
reload(): void;
182
route(url: string): void;
183
}
184
```
185
186
### ILayoutRestorer Token
187
188
Service token for layout restoration functionality (covered in detail in Layout Restoration documentation).
189
190
```typescript { .api }
191
/**
192
* Service token for layout restoration
193
*/
194
const ILayoutRestorer: Token<ILayoutRestorer>;
195
196
/**
197
* Layout restoration service interface
198
*/
199
interface ILayoutRestorer extends IRestorer {
200
readonly restored: Promise<void>;
201
add(widget: Widget, name: string): void;
202
restore<T extends Widget>(
203
tracker: WidgetTracker<T>,
204
options: IRestorer.IOptions<T>
205
): Promise<any>;
206
}
207
```
208
209
### IMimeDocumentTracker Token
210
211
Service token for tracking MIME document widgets across the application.
212
213
```typescript { .api }
214
/**
215
* MIME document widget tracker interface
216
*/
217
interface IMimeDocumentTracker extends IWidgetTracker<MimeDocument> {
218
// Inherits all IWidgetTracker methods for tracking MimeDocument widgets
219
}
220
221
/**
222
* Service token for MIME document tracker
223
*/
224
const IMimeDocumentTracker: Token<IMimeDocumentTracker>;
225
```
226
227
**Usage Examples:**
228
229
```typescript
230
import { IMimeDocumentTracker } from "@jupyterlab/application";
231
import { JupyterFrontEndPlugin } from "@jupyterlab/application";
232
233
const mimeTrackerPlugin: JupyterFrontEndPlugin<void> = {
234
id: 'my-mime-tracker-plugin',
235
autoStart: true,
236
requires: [IMimeDocumentTracker],
237
activate: (app, tracker: IMimeDocumentTracker) => {
238
// Listen for new MIME documents
239
tracker.widgetAdded.connect((sender, widget) => {
240
console.log('New MIME document added:', widget.context.path);
241
});
242
243
// Access current MIME document
244
const current = tracker.currentWidget;
245
if (current) {
246
console.log('Current MIME document:', current.title.label);
247
}
248
249
// Find specific MIME documents
250
const htmlDocs = tracker.filter(widget =>
251
widget.context.path.endsWith('.html')
252
);
253
console.log(`Found ${htmlDocs.length} HTML documents`);
254
}
255
};
256
```
257
258
### ITreePathUpdater Token
259
260
Service token for updating tree path information in the application.
261
262
```typescript { .api }
263
/**
264
* Tree path updater function type
265
* @param treePath - New tree path to set
266
*/
267
type ITreePathUpdater = (treePath: string) => void;
268
269
/**
270
* Service token for tree path updating
271
*/
272
const ITreePathUpdater: Token<ITreePathUpdater>;
273
```
274
275
**Usage Examples:**
276
277
```typescript
278
import { ITreePathUpdater } from "@jupyterlab/application";
279
import { JupyterFrontEndPlugin } from "@jupyterlab/application";
280
281
const pathUpdaterPlugin: JupyterFrontEndPlugin<void> = {
282
id: 'my-path-updater-plugin',
283
autoStart: true,
284
requires: [ITreePathUpdater],
285
activate: (app, updatePath: ITreePathUpdater) => {
286
// Update path when navigating
287
const navigateToPath = (newPath: string) => {
288
updatePath(newPath);
289
console.log('Updated tree path to:', newPath);
290
};
291
292
// Example usage
293
navigateToPath('/notebooks/analysis.ipynb');
294
navigateToPath('/data/dataset.csv');
295
}
296
};
297
```
298
299
### Application-Specific Tokens
300
301
Additional service tokens from the JupyterFrontEnd namespace.
302
303
```typescript { .api }
304
/**
305
* Service token for application paths configuration
306
*/
307
const IPaths: Token<JupyterFrontEnd.IPaths>;
308
309
/**
310
* Service token for tree resolver functionality
311
*/
312
const ITreeResolver: Token<JupyterFrontEnd.ITreeResolver>;
313
```
314
315
## Plugin Integration Patterns
316
317
Common patterns for using service tokens in plugin development.
318
319
### Basic Service Usage
320
321
```typescript
322
// Single service dependency
323
const basicPlugin: JupyterFrontEndPlugin<void> = {
324
id: 'basic-service-plugin',
325
autoStart: true,
326
requires: [ILabStatus],
327
activate: (app, status: ILabStatus) => {
328
// Use the service
329
const disposable = status.setBusy();
330
// ... do work
331
disposable.dispose();
332
}
333
};
334
```
335
336
### Multiple Service Dependencies
337
338
```typescript
339
// Multiple service dependencies
340
const multiServicePlugin: JupyterFrontEndPlugin<void> = {
341
id: 'multi-service-plugin',
342
autoStart: true,
343
requires: [ILabStatus, IRouter, ILayoutRestorer],
344
activate: (app, status: ILabStatus, router: IRouter, restorer: ILayoutRestorer) => {
345
// Use multiple services together
346
router.routed.connect(() => {
347
const busyDisposable = status.setBusy();
348
349
// Restore layout after routing
350
restorer.restored.then(() => {
351
busyDisposable.dispose();
352
});
353
});
354
}
355
};
356
```
357
358
### Optional Service Dependencies
359
360
```typescript
361
// Optional service dependencies
362
const optionalServicePlugin: JupyterFrontEndPlugin<void> = {
363
id: 'optional-service-plugin',
364
autoStart: true,
365
requires: [ILabStatus],
366
optional: [IRouter],
367
activate: (app, status: ILabStatus, router: IRouter | null) => {
368
// Required service is always available
369
status.setBusy();
370
371
// Optional service might be null
372
if (router) {
373
router.navigate('/optional-feature');
374
} else {
375
console.log('Router not available, using fallback');
376
}
377
}
378
};
379
```
380
381
### Providing Custom Services
382
383
```typescript
384
// Providing a service implementation
385
const serviceProviderPlugin: JupyterFrontEndPlugin<IConnectionLost> = {
386
id: 'custom-connection-lost-provider',
387
provides: IConnectionLost,
388
activate: (app): IConnectionLost => {
389
return async (manager, err, translator) => {
390
// Custom implementation
391
console.error('Custom connection lost handler:', err);
392
};
393
}
394
};
395
396
// Overriding default service
397
const overrideServicePlugin: JupyterFrontEndPlugin<ILabStatus> = {
398
id: 'custom-status-provider',
399
provides: ILabStatus,
400
activate: (app): ILabStatus => {
401
// Return custom implementation
402
return new CustomLabStatus(app);
403
}
404
};
405
```
406
407
### Service Composition
408
409
```typescript
410
// Composing services to create higher-level functionality
411
const compositeServicePlugin: JupyterFrontEndPlugin<IMyCompositeService> = {
412
id: 'composite-service',
413
provides: IMyCompositeService,
414
requires: [ILabStatus, IRouter, ILayoutRestorer],
415
activate: (app, status, router, restorer): IMyCompositeService => {
416
return new MyCompositeService(status, router, restorer);
417
}
418
};
419
420
interface IMyCompositeService {
421
navigateWithBusyState(path: string): Promise<void>;
422
saveAndNavigate(path: string): Promise<void>;
423
}
424
425
class MyCompositeService implements IMyCompositeService {
426
constructor(
427
private status: ILabStatus,
428
private router: IRouter,
429
private restorer: ILayoutRestorer
430
) {}
431
432
async navigateWithBusyState(path: string): Promise<void> {
433
const busyDisposable = this.status.setBusy();
434
try {
435
this.router.navigate(path);
436
await this.restorer.restored;
437
} finally {
438
busyDisposable.dispose();
439
}
440
}
441
442
async saveAndNavigate(path: string): Promise<void> {
443
const dirtyDisposable = this.status.setDirty();
444
try {
445
// Save current state
446
await this.saveCurrentState();
447
dirtyDisposable.dispose();
448
449
// Navigate to new path
450
await this.navigateWithBusyState(path);
451
} catch (error) {
452
// Keep dirty state if save failed
453
throw error;
454
}
455
}
456
457
private async saveCurrentState(): Promise<void> {
458
// Implementation for saving state
459
}
460
}
461
```
462
463
## Service Token Best Practices
464
465
### Token Definition
466
467
```typescript
468
// Good: Descriptive token with documentation
469
const IMyService: Token<IMyService> = new Token<IMyService>(
470
'@myapp/myservice:IMyService',
471
'A service for managing custom functionality in my application.'
472
);
473
474
// Good: Interface with clear contracts
475
interface IMyService {
476
/** Initialize the service */
477
initialize(): Promise<void>;
478
479
/** Clean up resources */
480
dispose(): void;
481
}
482
```
483
484
### Error Handling
485
486
```typescript
487
// Service with proper error handling
488
const robustServicePlugin: JupyterFrontEndPlugin<void> = {
489
id: 'robust-service-plugin',
490
requires: [ILabStatus],
491
activate: (app, status: ILabStatus) => {
492
try {
493
// Service initialization
494
const disposable = status.setBusy();
495
496
// Handle service errors gracefully
497
status.busySignal.connect((sender, isBusy) => {
498
if (isBusy) {
499
// Set up error recovery
500
setTimeout(() => {
501
if (status.isBusy) {
502
console.warn('Operation taking longer than expected');
503
}
504
}, 5000);
505
}
506
});
507
508
} catch (error) {
509
console.error('Failed to initialize service:', error);
510
// Graceful degradation
511
}
512
}
513
};
514
```