thinkncollab-shell 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +26 -0
- package/bin/thinknsh.js +68 -0
- package/package.json +42 -0
- package/src/core/shell.js +974 -0
- package/src/core/websocket.js +385 -0
- package/src/events/notifications.js +198 -0
- package/src/utils/logger.js +169 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Manager for ThinkNCollab Shell
|
|
3
|
+
* Handles all real-time communication
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { io } = require('socket.io-client');
|
|
7
|
+
const EventEmitter = require('events');
|
|
8
|
+
|
|
9
|
+
class WebSocketManager extends EventEmitter {
|
|
10
|
+
constructor(config = {}) {
|
|
11
|
+
super();
|
|
12
|
+
|
|
13
|
+
this.config = {
|
|
14
|
+
serverUrl: config.serverUrl || 'https://api.thinkncollab.com',
|
|
15
|
+
reconnectAttempts: config.reconnectAttempts || 5,
|
|
16
|
+
reconnectDelay: config.reconnectDelay || 1000,
|
|
17
|
+
autoConnect: config.autoConnect || false,
|
|
18
|
+
...config
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
this.socket = null;
|
|
22
|
+
this.connected = false;
|
|
23
|
+
this.userId = null;
|
|
24
|
+
this.roomId = null;
|
|
25
|
+
this.reconnectCount = 0;
|
|
26
|
+
this.pendingMessages = [];
|
|
27
|
+
this.messageQueue = [];
|
|
28
|
+
|
|
29
|
+
// Bind methods
|
|
30
|
+
this.connect = this.connect.bind(this);
|
|
31
|
+
this.disconnect = this.disconnect.bind(this);
|
|
32
|
+
this.joinRoom = this.joinRoom.bind(this);
|
|
33
|
+
this.leaveRoom = this.leaveRoom.bind(this);
|
|
34
|
+
this.sendMessage = this.sendMessage.bind(this);
|
|
35
|
+
this.sendNotification = this.sendNotification.bind(this);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Connect to WebSocket server
|
|
40
|
+
*/
|
|
41
|
+
connect(authToken = null) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
try {
|
|
44
|
+
const options = {
|
|
45
|
+
transports: ['websocket', 'polling'],
|
|
46
|
+
reconnection: true,
|
|
47
|
+
reconnectionAttempts: this.config.reconnectAttempts,
|
|
48
|
+
reconnectionDelay: this.config.reconnectDelay,
|
|
49
|
+
query: {}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
if (authToken) {
|
|
53
|
+
options.query.token = authToken;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.socket = io(this.config.serverUrl, options);
|
|
57
|
+
|
|
58
|
+
// Connection established
|
|
59
|
+
this.socket.on('connect', () => {
|
|
60
|
+
this.connected = true;
|
|
61
|
+
this.reconnectCount = 0;
|
|
62
|
+
this.userId = this.socket.id;
|
|
63
|
+
|
|
64
|
+
// Process any pending messages
|
|
65
|
+
this.processPendingMessages();
|
|
66
|
+
|
|
67
|
+
this.emit('connected', { socketId: this.socket.id });
|
|
68
|
+
resolve({ success: true, socketId: this.socket.id });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Connection error
|
|
72
|
+
this.socket.on('connect_error', (error) => {
|
|
73
|
+
this.emit('error', { type: 'connection', error: error.message });
|
|
74
|
+
|
|
75
|
+
if (this.reconnectCount >= this.config.reconnectAttempts) {
|
|
76
|
+
reject(new Error(`Failed to connect after ${this.config.reconnectAttempts} attempts`));
|
|
77
|
+
}
|
|
78
|
+
this.reconnectCount++;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Disconnection
|
|
82
|
+
this.socket.on('disconnect', (reason) => {
|
|
83
|
+
this.connected = false;
|
|
84
|
+
this.emit('disconnected', { reason });
|
|
85
|
+
|
|
86
|
+
if (reason === 'io server disconnect') {
|
|
87
|
+
// Server disconnected, don't reconnect
|
|
88
|
+
this.socket = null;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Reconnection
|
|
93
|
+
this.socket.on('reconnect', (attemptNumber) => {
|
|
94
|
+
this.connected = true;
|
|
95
|
+
this.emit('reconnected', { attemptNumber });
|
|
96
|
+
|
|
97
|
+
// Rejoin room if we were in one
|
|
98
|
+
if (this.roomId) {
|
|
99
|
+
this.joinRoom(this.roomId);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Custom event handlers
|
|
104
|
+
this.setupEventHandlers();
|
|
105
|
+
|
|
106
|
+
} catch (error) {
|
|
107
|
+
reject(error);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Setup custom event handlers
|
|
114
|
+
*/
|
|
115
|
+
setupEventHandlers() {
|
|
116
|
+
// Room events
|
|
117
|
+
this.socket.on('room:joined', (data) => {
|
|
118
|
+
this.roomId = data.roomId;
|
|
119
|
+
this.emit('roomJoined', data);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
this.socket.on('room:left', (data) => {
|
|
123
|
+
this.roomId = null;
|
|
124
|
+
this.emit('roomLeft', data);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
this.socket.on('room:participants', (data) => {
|
|
128
|
+
this.emit('roomParticipants', data);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Message events
|
|
132
|
+
this.socket.on('message:new', (data) => {
|
|
133
|
+
this.emit('message', data);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
this.socket.on('message:history', (data) => {
|
|
137
|
+
this.emit('messageHistory', data);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// User events
|
|
141
|
+
this.socket.on('user:joined', (data) => {
|
|
142
|
+
this.emit('userJoined', data);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
this.socket.on('user:left', (data) => {
|
|
146
|
+
this.emit('userLeft', data);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
this.socket.on('user:typing', (data) => {
|
|
150
|
+
this.emit('userTyping', data);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Notification events
|
|
154
|
+
this.socket.on('notification', (data) => {
|
|
155
|
+
this.emit('notification', data);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Terminal sharing events
|
|
159
|
+
this.socket.on('terminal:output', (data) => {
|
|
160
|
+
this.emit('terminalOutput', data);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
this.socket.on('terminal:command', (data) => {
|
|
164
|
+
this.emit('terminalCommand', data);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Error events
|
|
168
|
+
this.socket.on('error', (data) => {
|
|
169
|
+
this.emit('serverError', data);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Disconnect from WebSocket server
|
|
175
|
+
*/
|
|
176
|
+
disconnect() {
|
|
177
|
+
if (this.socket) {
|
|
178
|
+
this.socket.disconnect();
|
|
179
|
+
this.socket = null;
|
|
180
|
+
this.connected = false;
|
|
181
|
+
this.userId = null;
|
|
182
|
+
this.roomId = null;
|
|
183
|
+
this.emit('disconnected', { reason: 'manual' });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Join a collaboration room
|
|
189
|
+
*/
|
|
190
|
+
joinRoom(roomId, password = null) {
|
|
191
|
+
return new Promise((resolve, reject) => {
|
|
192
|
+
if (!this.connected || !this.socket) {
|
|
193
|
+
reject(new Error('WebSocket not connected'));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const payload = { roomId };
|
|
198
|
+
if (password) payload.password = password;
|
|
199
|
+
|
|
200
|
+
this.socket.emit('room:join', payload);
|
|
201
|
+
|
|
202
|
+
// Wait for response
|
|
203
|
+
const timeout = setTimeout(() => {
|
|
204
|
+
reject(new Error('Join room timeout'));
|
|
205
|
+
}, 5000);
|
|
206
|
+
|
|
207
|
+
this.socket.once('room:joined', (data) => {
|
|
208
|
+
clearTimeout(timeout);
|
|
209
|
+
this.roomId = data.roomId;
|
|
210
|
+
resolve(data);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
this.socket.once('room:error', (data) => {
|
|
214
|
+
clearTimeout(timeout);
|
|
215
|
+
reject(new Error(data.message));
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Leave current room
|
|
222
|
+
*/
|
|
223
|
+
leaveRoom() {
|
|
224
|
+
if (!this.connected || !this.socket || !this.roomId) {
|
|
225
|
+
return Promise.reject(new Error('Not in a room'));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return new Promise((resolve) => {
|
|
229
|
+
this.socket.emit('room:leave', { roomId: this.roomId });
|
|
230
|
+
this.roomId = null;
|
|
231
|
+
resolve({ success: true });
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Send message to current room
|
|
237
|
+
*/
|
|
238
|
+
sendMessage(message, type = 'text') {
|
|
239
|
+
if (!this.connected || !this.socket) {
|
|
240
|
+
// Queue message for later
|
|
241
|
+
this.messageQueue.push({ message, type });
|
|
242
|
+
return Promise.resolve({ queued: true });
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!this.roomId) {
|
|
246
|
+
return Promise.reject(new Error('Not in a room'));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return new Promise((resolve) => {
|
|
250
|
+
const payload = {
|
|
251
|
+
roomId: this.roomId,
|
|
252
|
+
message,
|
|
253
|
+
type,
|
|
254
|
+
timestamp: new Date().toISOString()
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
this.socket.emit('message:send', payload);
|
|
258
|
+
resolve({ success: true, id: `msg_${Date.now()}` });
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Send notification to room/users
|
|
264
|
+
*/
|
|
265
|
+
sendNotification(notification, target = 'room') {
|
|
266
|
+
if (!this.connected || !this.socket) {
|
|
267
|
+
return Promise.reject(new Error('WebSocket not connected'));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const payload = {
|
|
271
|
+
type: notification.type || 'info',
|
|
272
|
+
title: notification.title,
|
|
273
|
+
message: notification.message,
|
|
274
|
+
data: notification.data,
|
|
275
|
+
target: target === 'room' ? this.roomId : target,
|
|
276
|
+
timestamp: new Date().toISOString()
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
this.socket.emit('notification:send', payload);
|
|
280
|
+
|
|
281
|
+
return Promise.resolve({ success: true });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Share terminal output with room
|
|
286
|
+
*/
|
|
287
|
+
shareTerminalOutput(command, output) {
|
|
288
|
+
if (!this.connected || !this.socket || !this.roomId) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
this.socket.emit('terminal:share', {
|
|
293
|
+
roomId: this.roomId,
|
|
294
|
+
command,
|
|
295
|
+
output,
|
|
296
|
+
timestamp: new Date().toISOString()
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Send typing indicator
|
|
302
|
+
*/
|
|
303
|
+
sendTyping(isTyping = true) {
|
|
304
|
+
if (!this.connected || !this.socket || !this.roomId) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
this.socket.emit('user:typing', {
|
|
309
|
+
roomId: this.roomId,
|
|
310
|
+
isTyping
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Get room participants
|
|
316
|
+
*/
|
|
317
|
+
getParticipants() {
|
|
318
|
+
if (!this.connected || !this.socket || !this.roomId) {
|
|
319
|
+
return Promise.reject(new Error('Not connected to a room'));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return new Promise((resolve) => {
|
|
323
|
+
this.socket.emit('room:getParticipants', { roomId: this.roomId });
|
|
324
|
+
|
|
325
|
+
this.socket.once('room:participants', (data) => {
|
|
326
|
+
resolve(data.participants);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Get message history
|
|
333
|
+
*/
|
|
334
|
+
getMessageHistory(limit = 50) {
|
|
335
|
+
if (!this.connected || !this.socket || !this.roomId) {
|
|
336
|
+
return Promise.reject(new Error('Not connected to a room'));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return new Promise((resolve) => {
|
|
340
|
+
this.socket.emit('message:getHistory', {
|
|
341
|
+
roomId: this.roomId,
|
|
342
|
+
limit
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
this.socket.once('message:history', (data) => {
|
|
346
|
+
resolve(data.messages);
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Process pending messages after reconnection
|
|
353
|
+
*/
|
|
354
|
+
processPendingMessages() {
|
|
355
|
+
if (this.messageQueue.length > 0) {
|
|
356
|
+
this.messageQueue.forEach(item => {
|
|
357
|
+
this.sendMessage(item.message, item.type);
|
|
358
|
+
});
|
|
359
|
+
this.messageQueue = [];
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Check connection status
|
|
365
|
+
*/
|
|
366
|
+
isConnected() {
|
|
367
|
+
return this.connected && this.socket && this.socket.connected;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Get current room info
|
|
372
|
+
*/
|
|
373
|
+
getCurrentRoom() {
|
|
374
|
+
return this.roomId;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Get socket ID
|
|
379
|
+
*/
|
|
380
|
+
getSocketId() {
|
|
381
|
+
return this.userId;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
module.exports = WebSocketManager;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notification System for ThinkNCollab Shell
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const EventEmitter = require('events');
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
|
|
8
|
+
class NotificationManager extends EventEmitter {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
super();
|
|
11
|
+
|
|
12
|
+
this.options = {
|
|
13
|
+
sound: options.sound || false,
|
|
14
|
+
desktop: options.desktop || false,
|
|
15
|
+
...options
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
this.notifications = [];
|
|
19
|
+
this.maxNotifications = 100;
|
|
20
|
+
this.unreadCount = 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Show a notification
|
|
25
|
+
*/
|
|
26
|
+
show(notification) {
|
|
27
|
+
const notif = {
|
|
28
|
+
id: `notif_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
29
|
+
type: notification.type || 'info',
|
|
30
|
+
title: notification.title || 'Notification',
|
|
31
|
+
message: notification.message,
|
|
32
|
+
timestamp: new Date(),
|
|
33
|
+
read: false,
|
|
34
|
+
data: notification.data || {}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
this.notifications.unshift(notif);
|
|
38
|
+
this.unreadCount++;
|
|
39
|
+
|
|
40
|
+
// Trim old notifications
|
|
41
|
+
if (this.notifications.length > this.maxNotifications) {
|
|
42
|
+
this.notifications.pop();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Emit event
|
|
46
|
+
this.emit('notification', notif);
|
|
47
|
+
|
|
48
|
+
// Show in console
|
|
49
|
+
this.showInConsole(notif);
|
|
50
|
+
|
|
51
|
+
// Desktop notification
|
|
52
|
+
if (this.options.desktop) {
|
|
53
|
+
this.showDesktopNotification(notif);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Sound
|
|
57
|
+
if (this.options.sound) {
|
|
58
|
+
this.playSound();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return notif;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Show notification in console
|
|
66
|
+
*/
|
|
67
|
+
showInConsole(notification) {
|
|
68
|
+
let color = chalk.blue;
|
|
69
|
+
let symbol = 'ℹ';
|
|
70
|
+
|
|
71
|
+
switch (notification.type) {
|
|
72
|
+
case 'success':
|
|
73
|
+
color = chalk.green;
|
|
74
|
+
symbol = '✓';
|
|
75
|
+
break;
|
|
76
|
+
case 'warning':
|
|
77
|
+
color = chalk.yellow;
|
|
78
|
+
symbol = '⚠';
|
|
79
|
+
break;
|
|
80
|
+
case 'error':
|
|
81
|
+
color = chalk.red;
|
|
82
|
+
symbol = '✗';
|
|
83
|
+
break;
|
|
84
|
+
case 'info':
|
|
85
|
+
default:
|
|
86
|
+
color = chalk.blue;
|
|
87
|
+
symbol = 'ℹ';
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log(color(`${symbol} ${notification.title}: ${notification.message}`));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Show desktop notification (if supported)
|
|
96
|
+
*/
|
|
97
|
+
showDesktopNotification(notification) {
|
|
98
|
+
// Check if running in Electron
|
|
99
|
+
if (process.type === 'renderer' || process.type === 'browser') {
|
|
100
|
+
try {
|
|
101
|
+
const { Notification } = require('electron');
|
|
102
|
+
new Notification({
|
|
103
|
+
title: notification.title,
|
|
104
|
+
body: notification.message,
|
|
105
|
+
silent: !this.options.sound
|
|
106
|
+
}).show();
|
|
107
|
+
} catch (error) {
|
|
108
|
+
// Electron not available
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Play notification sound
|
|
115
|
+
*/
|
|
116
|
+
playSound() {
|
|
117
|
+
// TODO: Implement sound playing
|
|
118
|
+
// This would use 'sound-play' or similar package
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Mark notification as read
|
|
123
|
+
*/
|
|
124
|
+
markAsRead(id) {
|
|
125
|
+
const notif = this.notifications.find(n => n.id === id);
|
|
126
|
+
if (notif && !notif.read) {
|
|
127
|
+
notif.read = true;
|
|
128
|
+
this.unreadCount--;
|
|
129
|
+
this.emit('read', notif);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Mark all as read
|
|
135
|
+
*/
|
|
136
|
+
markAllAsRead() {
|
|
137
|
+
this.notifications.forEach(n => {
|
|
138
|
+
if (!n.read) {
|
|
139
|
+
n.read = true;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
this.unreadCount = 0;
|
|
143
|
+
this.emit('allRead');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get unread notifications
|
|
148
|
+
*/
|
|
149
|
+
getUnread() {
|
|
150
|
+
return this.notifications.filter(n => !n.read);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get all notifications
|
|
155
|
+
*/
|
|
156
|
+
getAll() {
|
|
157
|
+
return this.notifications;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Clear all notifications
|
|
162
|
+
*/
|
|
163
|
+
clear() {
|
|
164
|
+
this.notifications = [];
|
|
165
|
+
this.unreadCount = 0;
|
|
166
|
+
this.emit('cleared');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Create success notification
|
|
171
|
+
*/
|
|
172
|
+
success(title, message, data = {}) {
|
|
173
|
+
return this.show({ type: 'success', title, message, data });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Create error notification
|
|
178
|
+
*/
|
|
179
|
+
error(title, message, data = {}) {
|
|
180
|
+
return this.show({ type: 'error', title, message, data });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Create warning notification
|
|
185
|
+
*/
|
|
186
|
+
warning(title, message, data = {}) {
|
|
187
|
+
return this.show({ type: 'warning', title, message, data });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Create info notification
|
|
192
|
+
*/
|
|
193
|
+
info(title, message, data = {}) {
|
|
194
|
+
return this.show({ type: 'info', title, message, data });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
module.exports = NotificationManager;
|