mosquito-transport 1.4.3 → 1.4.6

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 CHANGED
@@ -2,42 +2,36 @@
2
2
 
3
3
  MosquitoTransport is a powerful tool that enables developers to persist and synchronize data between their MongoDB database and frontend applications. It offers a centralized and self-hosted solution for managing server infrastructure and data, along with robust authentication, real-time data updates, scalability, and cross-platform compatibility.
4
4
 
5
- Under the hood, mosquito-transport uses Mongodb to store it data and [express](https://www.npmjs.com/package/express), [socket.io](https://www.npmjs.com/package/socket.io) for making request and [jwt](https://www.npmjs.com/package/jsonwebtoken) for signing authentication token, so make sure you have [mongodb](https://www.mongodb.com/docs/manual/installation/) installed before using this package.
5
+ Under the hood, mosquito-transport uses Mongodb to store it data, along with [express](https://www.npmjs.com/package/express), [socket.io](https://www.npmjs.com/package/socket.io) for making request and [jwt](https://www.npmjs.com/package/jsonwebtoken) for signing authentication token, so make sure you have [mongodb](https://www.mongodb.com/docs/manual/installation/) installed before using this package.
6
6
 
7
7
  ## Key features of mosquito-transport include:
8
8
 
9
- - ### Data Persistence and Synchronization 🔁:
9
+ - Data Persistence and Synchronization 🔁:
10
10
  - Seamlessly persist and synchronize data between MongoDB and frontend applications, ensuring consistency across all clients.
11
-
12
- - ### Self-Hosted Server 💾:
11
+ - Self-Hosted Server 💾:
13
12
  - Host your own server infrastructure, giving you full control over data storage, access, and management.
14
-
15
- - ### User Authentication and Authorization 🔐:
13
+ - User Authentication and Authorization 🔐:
16
14
  - Easily implement user authentication and authorization using JWT (JSON Web Tokens), providing secure access control to your application's resources.
17
-
18
- - ### End-to-End Encryption 🔗:
15
+ - End-to-End Encryption 🔗:
19
16
  - Optionally enforce end-to-end encryption by allowing only encrypted data to be transmitted between client and server, ensuring data privacy and security.
20
-
21
- - ### Real-Time Data Updates 🚨:
17
+ - Real-Time Data Updates 🚨:
22
18
  - Enable real-time updates to keep data synchronized across all clients in real-time, providing a seamless user experience.
23
-
24
- - ### Scalability and Performance 🚛:
19
+ - Scalability and Performance 🚛:
25
20
  - Benefit from auto-scaling and high performance, allowing your application to handle varying workloads with ease.
26
-
27
- - ### Cross-Platform Compatibility 📱:
21
+ - Cross-Platform Compatibility 📱:
28
22
  - Compatible with React Native and web applications, allowing you to build cross-platform solutions with ease.
29
23
 
30
24
 
31
25
  ## Installation
32
26
 
33
27
  ```sh
34
- npm install mosquito-transport
28
+ npm install mosquito-transport mongodb --save
35
29
  ```
36
30
 
37
31
  or using yarn
38
32
 
39
33
  ```sh
40
- yarn add mosquito-transport
34
+ yarn add mosquito-transport mongodb
41
35
  ```
42
36
 
43
37
  ## Usage
@@ -47,15 +41,12 @@ import MosquitoTransportServer from "mosquito-transport";
47
41
  import { MongoClient } from 'mongodb';
48
42
 
49
43
  // create a mongodb instance
50
- const dbInstance = new MongoClient('mongodb://127.0.0.1:27017', {
51
- useNewUrlParser: true,
52
- useUnifiedTopology: true
53
- });
44
+ const dbInstance = new MongoClient('mongodb://127.0.0.1:27017');
54
45
 
55
46
  dbInstance.connect().then(() => {
56
- console.log('connected to mongodb at');
47
+ console.log('connected to mongodb');
57
48
  }).catch(e => {
58
- console.error('failed to connected to mongodb at');
49
+ console.error('failed to connected to mongodb');
59
50
  });
60
51
 
61
52
  // setup your server
@@ -63,7 +54,8 @@ const serverApp = new MosquitoTransportServer({
63
54
  projectName: 'app_name',
64
55
  port: 4534, // defaults to 4291
65
56
  signerKey: 'random_90_hash_key_for_signing_jwt_tokens', // must be 90 length
66
- accessKey: 'this_is_my_private_password', // keep this private if you don't provide databaseRules or storageRules
57
+ accessKey: 'some_unique_string',
58
+ externalAddress: 'https://example.yourdomain.com',
67
59
  mongoInstances: {
68
60
  // this is where user info and tokens is stored
69
61
  admin: {
@@ -105,13 +97,72 @@ your server is now ready to be deploy on node.js! 🚀. Now install any mosquito
105
97
  ### SDKs And Hacks
106
98
  - [react-native-mosquito-transport](https://github.com/deflexable/react-native-mosquito-transport) for react native apps
107
99
  - [mosquito-transport-web](https://github.com/brainbehindx/mosquito-transport-js) for web platform
108
- - [mongodb-hack-middleware](https://github.com/deflexable/mongodb-middleware-utils) for random query hack and fulltext search hack
100
+ - [mongodb-hack-middleware](https://github.com/deflexable/mongodb-middleware-utils) for querying random document hack and fulltext search hack
109
101
 
110
102
  ## Additional Documentations
111
- - [Logging Level](#logging-levels)
112
- - [Database Rules](#database-rules)
113
- - [Storage Rules](#storage-rules)
114
- - [Authentication](#authentication)
103
+ - [MosquitoTransportServer Constructor](#MosquitoServerConfig)
104
+ - [projectName](#projectName)
105
+ - [signerKey](#signerKey)
106
+ - [port](#port)
107
+ - [storageRules](#storageRules)
108
+ - [databaseRules](#databaseRules)
109
+ - [accessTokenInterval](#accessTokenInterval)
110
+ - [refreshTokenExpiry](#refreshTokenExpiry)
111
+ - [accessKey](#accessKey)
112
+ - [mongoInstances](#mongoInstances)
113
+ - [externalAddress](#externalAddress)
114
+ - [hostname](#hostname)
115
+ - [enableSequentialUid](#enableSequentialUid)
116
+ - [mergeAuthAccount](#mergeAuthAccount)
117
+ - [sneakSignupAuth](#sneakSignupAuth)
118
+ - [uidLength](#uidLength)
119
+ - [enforceE2E](#enforceE2E)
120
+ - [e2eKeyPair](#e2eKeyPair)
121
+ - [logger](#logger)
122
+ - [dumpsterPath](#dumpsterPath)
123
+ - [preMiddlewares](#preMiddlewares)
124
+ - [transformMediaRoute](#transformMediaRoute)
125
+ - [transformMediaCleanupTimeout](#transformMediaCleanupTimeout)
126
+ <!-- - [googleAuthConfig](#googleAuthConfig)
127
+ - [appleAuthConfig](#appleAuthConfig)
128
+ - [facebookAuthConfig](#facebookAuthConfig)
129
+ - [githubAuthConfig](#githubAuthConfig)
130
+ - [twitterAuthConfig](#twitterAuthConfig)
131
+ - [fallbackAuthConfig](#fallbackAuthConfig) -->
132
+ - [staticContentProps](#staticContentProps)
133
+ - [staticContentMaxAge](#staticContentMaxAge)
134
+ - [staticContentCacheControl](#staticContentCacheControl)
135
+ - [corsOrigin](#corsOrigin)
136
+ - [maxRequestBufferSize](#maxRequestBufferSize)
137
+ - [maxUploadBufferSize](#maxUploadBufferSize)
138
+ - [MosquitoTransportServer Getters](#MosquitoTransportServer-Getters)
139
+ - [storagePath](#storagePath)
140
+ - [sampleE2E](#sampleE2E)
141
+ - [express](#express)
142
+ - [MosquitoTransportServer Methods](#MosquitoTransportServer-Methods)
143
+ - [getDatabase](#getDatabase)
144
+ - [listenDatabase](#listenDatabase)
145
+ - [listenStorage](#listenStorage)
146
+ - [listenHttpsRequest](#listenHttpsRequest)
147
+ - [listenNewUser](#listenNewUser)
148
+ - [listenDeletedUser](#listenDeletedUser)
149
+ - [verifyToken](#verifyToken)
150
+ - [validateToken](#validateToken)
151
+ - [invalidateToken](#invalidateToken)
152
+ - [getUserData](#getUserData)
153
+ - [updateUserProfile](#updateUserProfile)
154
+ - [updateUserClaims](#updateUserClaims)
155
+ - [updateUserEmailAddress](#updateUserEmailAddress)
156
+ - [updateUserPassword](#updateUserPassword)
157
+ - [updateUserEmailVerify](#updateUserEmailVerify)
158
+ - [signOutUser](#signOutUser)
159
+ - [disableUser](#disableUser)
160
+ - [uploadBuffer](#uploadBuffer)
161
+ - [deleteFile](#deleteFile)
162
+ - [deleteFolder](#deleteFolder)
163
+ - [inspectDocDisconnectionTask](#inspectDocDisconnectionTask)
164
+ - [linkToFile](#linkToFile)
165
+ - [Authentication Setup](#authentication-setup)
115
166
  - [Merge Auth Account](#google-auth-setup)
116
167
  - [Google Auth Setup](#google-auth-setup)
117
168
  - [Apple Auth Setup](#apple-auth-setup)
@@ -122,6 +173,488 @@ your server is now ready to be deploy on node.js! 🚀. Now install any mosquito
122
173
  - [Google Auth Setup](#google-auth-setup)
123
174
 
124
175
 
176
+ ## MosquitoServerConfig
177
+
178
+ ### projectName
179
+
180
+ the name for your mosquito-transport instance. this is required and used internally by both the backend and frontend client.
181
+
182
+ ### signerKey
183
+
184
+ a 90 character string which is used in signing jwt access and refresh token.
185
+
186
+ ### port
187
+
188
+ the port number you want mosquito-transport instance to be running on
189
+
190
+ ### storageRules
191
+
192
+ a function used for securing all file operations (`uploadFile`, `downloadFile`, `deleteFile`, `deleteFolder`) made by the frontend client.
193
+ <!-- TODO: show examples -->
194
+
195
+ ### databaseRules
196
+
197
+ a function used for securing all mongodb read and write operations made by the frontend client.
198
+ <!-- show examples -->
199
+
200
+ ### accessTokenInterval
201
+
202
+ numbers of milliseconds until generated access token expires. Defaults to `1 hour` (3600000).
203
+
204
+ ### refreshTokenExpiry
205
+
206
+ numbers of milliseconds until generated refresh token expires. Defaults to `1 month` (2419200000).
207
+
208
+ ### accessKey
209
+
210
+ a random string used by the frontend client for accessing internal resources.
211
+
212
+ ### mongoInstances
213
+
214
+ an object that maps names to your mongodb instance. if no `dbRef` were provided, the `default` mongodb instance will be used.
215
+
216
+ ```js
217
+ import MosquitoTransportServer from "mosquito-transport";
218
+ import { MongoClient } from 'mongodb';
219
+
220
+ // create a mongodb instance
221
+ const dbInstance = new MongoClient('mongodb://127.0.0.1:27017');
222
+
223
+ dbInstance.connect();
224
+
225
+ const remoteInstance = new MongoClient('mongodb://other-searver.com');
226
+
227
+ remoteInstance.connect();
228
+
229
+ const serverApp = new MosquitoTransportServer({
230
+ ...otherProps,
231
+ mongoInstances: {
232
+ // frontend client are prohitted from accessing this instance
233
+ admin: {
234
+ instance: dbInstance,
235
+ defaultName: 'ADMIN_DB_NAME'
236
+ },
237
+ // this will be the default db if no dbRef was provided by the frontend client
238
+ default: {
239
+ instance: dbInstance,
240
+ defaultName: 'DEFAULT_DB_NAME'
241
+ },
242
+ // additional instance
243
+ remoteBackup: {
244
+ instance: remoteInstance,
245
+ defaultName: 'WEB_BACKUP'
246
+ }
247
+ }
248
+ });
249
+
250
+ // then you can access this via frontend client
251
+
252
+ const webInstance = new MosquitoTransport({
253
+ projectUrl: 'http://localhost:4534/app_name',
254
+ accessKey: 'some_unique_string',
255
+ ...options
256
+ });
257
+
258
+ webInstance.getDatabase(
259
+ // if this is undefined, the server will use `defaultName` as the default name
260
+ 'database_name',
261
+ // the name of the mongoInstances map
262
+ 'remoteBackup'
263
+ ).collection('transactions').findOne({ date: { $gt: 1719291129937 } }).get();
264
+
265
+ // or access the default db
266
+
267
+ webInstance.getDatabase().collection('testing');
268
+
269
+ ```
270
+
271
+ ### externalAddress
272
+
273
+ this should be a valid http or https link. it is used internally while signing jwt and for prefixing storage `downloadUrl` when uploading a file by frontend client.
274
+
275
+ ### hostname
276
+
277
+ if no `externalAddress` was provided, `externalAddress` will be a construct as follows:
278
+
279
+ ```js
280
+ `http://${hostname || 'localhost'}:${port}`
281
+ ```
282
+
283
+ ### enableSequentialUid
284
+
285
+ true if you want new users to be assign a sequential `uid` like 0, 1, 2, 3, 4, 5, ...,
286
+
287
+ ### mergeAuthAccount
288
+
289
+ true if you want to threat the same email address from different auth provider as a single user.
290
+
291
+ ### sneakSignupAuth
292
+
293
+ a function use in preventing signup and adding metadata before signup
294
+
295
+ ```js
296
+ import MosquitoTransportServer from "mosquito-transport";
297
+
298
+ const blacklisted_country = ['RU', 'AF', 'NG'];
299
+
300
+ const serverApp = new MosquitoTransportServer({
301
+ ...otherProps,
302
+ sneakSignupAuth: ({ request, email, name, password, method }) => {
303
+ const geo = lookupIpAddress(request.ip);
304
+ if (!geo) throw 'Failed to lookup request location';
305
+
306
+ if (blacklisted_country.includes(geo.country))
307
+ throw 'This platform is not yet available in your location';
308
+
309
+ if (method === 'custom' && password.length < 5)
310
+ throw 'password is too short';
311
+
312
+ const uid = randomString(11),
313
+ lang = getCountryLang(geo?.country || 'US');
314
+
315
+ return {
316
+ metadata: {
317
+ country: geo.country,
318
+ city: geo.city,
319
+ location: geo.ll,
320
+ tz: geo?.timezone,
321
+ ip: request.ip,
322
+ locale: 'en'
323
+ },
324
+ uid
325
+ };
326
+ }
327
+ });
328
+ ```
329
+
330
+ ### uidLength
331
+
332
+ the length of generated user uid. default to `30`.
333
+
334
+ ### enforceE2E
335
+
336
+ true if you want to enforce end-to-end encryption for all request made by the server
337
+
338
+ ### e2eKeyPair
339
+
340
+ an array of string, `[public key, private key]`. You can get a sample as follows:
341
+
342
+ ```js
343
+ import MosquitoTransportServer from "mosquito-transport";
344
+
345
+ const serverApp = new MosquitoTransportServer({ ...options });
346
+
347
+ console.log('pair key', serverApp.sampleE2E);
348
+ ```
349
+ ### dumpsterPath
350
+
351
+ path to where mosquito-transport stores it files. Defaults to the current working directory.
352
+
353
+ ### preMiddlewares
354
+
355
+ a function to intercept express. This will be the first middleware executed by express.
356
+
357
+ ```js
358
+ import MosquitoTransportServer from "mosquito-transport";
359
+
360
+ const serverApp = new MosquitoTransportServer({
361
+ ...otherProps,
362
+ preMiddlewares: (req, res, next) => {
363
+ // do some enforcement checking here
364
+ next();
365
+ }
366
+ });
367
+ ```
368
+
369
+ ### transformMediaRoute
370
+
371
+ this option helps you to transform image and video files on the fly without having to write boilerplate code for this.
372
+ All you have to do is set `transformMediaRoute` to `*` as follows:
373
+
374
+ ```js
375
+ import MosquitoTransportServer from "mosquito-transport";
376
+
377
+ const serverApp = new MosquitoTransportServer({
378
+ ...otherProps,
379
+ transformMediaRoute: '*'
380
+ });
381
+ ```
382
+
383
+ now you can automatically transform images and video by appending some query parameter to the url of the image or video you're accessing.
384
+
385
+ #### Image Parameters
386
+ the following list the parameters available for image media
387
+
388
+ - `width` or `w`: a number that sets the width of the image
389
+ - `height` or `h`: a number that sets the height of the image
390
+ - `top` or `t`: a number that sets the top position of the image
391
+ - `left` or `l`: a number that sets the left position of the image
392
+ - `grayscale`or `gray`: set this to `1` or `true` if you want the image in grayscale
393
+ - `blur` or `b`: either set this to `true` to blur the image or a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where sigma = 1 + radius / 2.
394
+ - `flip`: set to `true` or `1` to flip the image about the vertical Y axis. The use of flip implies the removal of the EXIF Orientation tag, if any.
395
+ - `flop`: set to `true` or `1` to flop the image about the horizontal X axis. The use of flop implies the removal of the EXIF Orientation tag, if any.
396
+ - `format` or `o`: this set the output format of the image, can be any of `avif`, `dz`, `fits`, `gif`, `heif`, `input`, `jpeg`, `jpg`, `jp2`, `jxl`, `magick`, `openslide`, `pdf`, `png`, `ppm`, `raw`, `svg`, `tiff`, `tif`, `v` or `webp`
397
+ - `quality` or `q`: set the quality of the image from a scale of 0 - 1.
398
+ - `lossless` or `loss`: set to `1` or `true` to use lossless compression mode
399
+
400
+ ***Example***
401
+ the following transform the image at `http://localhost:5622/storage/users/richard/photo.png`:
402
+
403
+ ```js
404
+ // resize the image to 70 width and scale the height respectively
405
+ `http://localhost:5622/storage/users/richard/photo.png?w=70`
406
+
407
+ // apply grayscale to the image and set the quality to 0.3
408
+ `http://localhost:5622/storage/users/richard/photo.png?grayscale=true&q=0.3`
409
+ ```
410
+
411
+ #### Video Parameters
412
+ the following list the parameters available for video media
413
+
414
+ - `width` or `w`: a number that sets the width of the video.
415
+ - `height` or `h`: a number that sets the height of the video.
416
+ - `top` or `t`: a number that sets the top position of the video.
417
+ - `left` or `l`: a number that sets the left position of the video.
418
+ - `mute`: set to `1` or `true` to mute the video.
419
+ - `vbr`: set the bitrate of the video. Equivalent to `-v:a` command of ffmpeg.
420
+ - `abr`: set the bitrate of the audio. Equivalent to `-b:a` command of ffmpeg.
421
+ - `fps`: an integer to set the frame per seconds of the video. This parameter plays a significant role in reducing the output size and processing time of the video. Equivalent to `-r` command of ffmpeg.
422
+ - `grayscale`or `gray`: set this to `1` or `true` if you want the video in grayscale
423
+ - `flip`: set to `true` or `1` to flip the video about the vertical Y axis.
424
+ - `flop`: set to `true` or `1` to flop the video about the horizontal X axis.
425
+ - `quality` or `q`: set the quality of the video from a scale of 0 - 1.
426
+ - `lossless` or `loss`: set to `1` or `true` to use lossless compression mode
427
+ - `preset`: set the `-preset` of ffmpeg. Defaults to medium.
428
+ - `format` or `o`: this set the output format of the image, can be any of `avif`, `dz`, `fits`, `gif`, `heif`, `input`, `jpeg`, `jpg`, `jp2`, `jxl`, `magick`, `openslide`, `pdf`, `png`, `ppm`, `raw`, `svg`, `tiff`, `tif`, `v` or `webp`
429
+
430
+ ***Example***
431
+ the following transform the video at `http://localhost:5622/storage/video/lil-yatchy/range-rover-sport-truck.mp4`:
432
+
433
+ ```js
434
+ // resize the video to 200 height and scale the width respectively
435
+ `http://localhost:5622/storage/video/lil-yatchy/range-rover-sport-truck.mp4?height=200`
436
+
437
+ // apply grayscale to the video, set the quality to 0.7 and set the fps to 30
438
+ `http://localhost:5622/storage/video/lil-yatchy/range-rover-sport-truck.mp4?grayscale=true&q=0.7&fps=30`
439
+ ```
440
+ ***Additional Dependency***
441
+ Internally mosquito-transport uses `sharp` to transform images and `ffmpeg` to transform video, so make sure these library are installed before setting `transformMediaRoute: '*'`
442
+
443
+ ```sh
444
+ yarn add sharp
445
+ ```
446
+
447
+ ### transformMediaCleanupTimeout
448
+
449
+ This is the numbers of milliseconds to cache the transformed video media file before it is deleted. This is basically to avoid the overhead processing time next time the frontend client tries to access it. Defaults to 7 hours.
450
+
451
+ # logger
452
+
453
+ can either be a string or array containing any of the following:
454
+
455
+ - `all`: log all requests
456
+ - `auth`: log authentication requests
457
+ - `database`: log database requests
458
+ - `storage`: log storage requests
459
+ - `external-requests`: log api requests
460
+ - `served-content`: log serve content requests
461
+ - `database-snapshot`: log database snapshot events
462
+
463
+ ### staticContentProps
464
+
465
+ Static Content Props for storage file response. See [SendFileOptions](https://github.com/expressjs/expressjs/express-serve-static-core/index.d.ts)
466
+
467
+ ### staticContentMaxAge
468
+
469
+ Provide a max-age in milliseconds for http caching. This will only be applied to storage file response.
470
+
471
+ ### staticContentCacheControl
472
+
473
+ Enable or disable setting Cache-Control response header. This will only be applied to storage file response.
474
+
475
+ ### corsOrigin
476
+
477
+ set cors origin for all outgoing request
478
+
479
+ ### maxRequestBufferSize
480
+
481
+ the maximum size in bytes of each request payload. Default to 100MB
482
+
483
+ ### maxUploadBufferSize
484
+
485
+ the maximum size in byte of each uploading request payload. Default to 10GB
486
+
487
+ ## MosquitoTransportServer-Getters
488
+
489
+ ### storagePath
490
+
491
+ get the directory where storage files are saved
492
+
493
+ ### sampleE2E
494
+
495
+ quickly get an end-to-end encryption [pair key](#[e2eKeyPair]) for your server
496
+
497
+ ### express
498
+
499
+ get the internal express instance use
500
+
501
+ ## MosquitoTransportServer-Methods
502
+
503
+ ### getDatabase
504
+
505
+ returns the db instance of mongodb.
506
+
507
+ ```js
508
+ serverApp.getDatabase(
509
+ // if this is undefined, the server will use `defaultName` as the default name
510
+ 'database_name',
511
+ // the name of the mongoInstances map
512
+ 'remoteBackup'
513
+ ).collection('transactions').findOne({ date: { $gt: 1719291129937 } }).get();
514
+
515
+ // or access the default db
516
+
517
+ serverApp.getDatabase().collection('testing');
518
+ ```
519
+
520
+ ### listenDatabase
521
+
522
+ listen to insert, update and delete events from mongodb
523
+
524
+ ```js
525
+ serverApp.listenDatabase('transactions', async snapshot => {
526
+ console.log('transaction snapshot', snapshot);
527
+ });
528
+ ```
529
+
530
+ ### listenStorage
531
+
532
+ listen to storage event. these event are typically made by the frontend client.
533
+
534
+ ```js
535
+ serverApp.listenStorage(async event => {
536
+ console.log('storage event', event);
537
+ });
538
+ ```
539
+
540
+ ### listenHttpsRequest
541
+
542
+ listen to incoming request
543
+
544
+ ```js
545
+ // only allow authenticated user to access this endpoint
546
+ serverApp.listenHttpsRequest('check_user', async (req, res, user) => {
547
+ // user will always be present
548
+ res.status(200).send({ uid: user.uid });
549
+ }, {
550
+ enforceVerifiedUser: true,
551
+ enforceUser: true
552
+ });
553
+
554
+ // optionally allow un-authenticated user
555
+ serverApp.listenHttpsRequest('server_time', async (req, res, user) => {
556
+ // user may be present
557
+ if (user) {
558
+ res.status(200).send({ uid: user.uid });
559
+ } else {
560
+ res.status(403).send({ error: 'No user provided' });
561
+ }
562
+ }, {
563
+ validateUser: true
564
+ });
565
+
566
+ // disable end-to-end encrytion for this endpoint and user authentication
567
+ serverApp.listenHttpsRequest('server_time', async (req, res) => {
568
+ res.status(200).send({ currentData: Date.now() });
569
+ }, {
570
+ rawEntry: true
571
+ });
572
+ ```
573
+
574
+ ### listenNewUser
575
+
576
+ listen to new user
577
+
578
+ ```js
579
+ serverApp.listenNewUser(async user => {
580
+ console.log('new signup', user);
581
+ });
582
+ ```
583
+
584
+ ### listenDeletedUser
585
+
586
+ listen to deletedUser
587
+
588
+ ```js
589
+ serverApp.listenDeletedUser(uid => {
590
+ console.log('deleted user', uid);
591
+ });
592
+ ```
593
+
594
+ ### verifyToken
595
+
596
+ verify token to check if it was trully created using signerKey without checking against the expiry or local token reference
597
+
598
+ ### validateToken
599
+
600
+ verify token to check if it was trully created using signerKey and checking against the expiry and local token reference
601
+
602
+ ### invalidateToken
603
+
604
+ remove local reference of a token
605
+
606
+ ### getUserData
607
+
608
+ get the user data belonging to a user
609
+
610
+ ### updateUserProfile
611
+
612
+ update the profile data of a user
613
+
614
+ ### updateUserClaims
615
+
616
+ update the custom claim of a user
617
+
618
+ ### updateUserEmailAddress
619
+
620
+ update the email address of a user
621
+
622
+ ### updateUserPassword
623
+
624
+ update the user password of a user
625
+
626
+ ### updateUserEmailVerify
627
+
628
+ update the verify status of a user
629
+
630
+ ### signOutUser
631
+
632
+ purge all tokens references for a user and sign-out the user immediately
633
+
634
+ ### disableUser
635
+
636
+ disable a user
637
+
638
+ ### uploadBuffer
639
+
640
+ upload a file to the storage directory
641
+
642
+ ### deleteFile
643
+
644
+ delete file in the storage directory
645
+
646
+ ### deleteFolder
647
+
648
+ delete folder in the storage directory
649
+
650
+ ### linkToFile
651
+
652
+ convert a link to local file path.
653
+
654
+ ```js
655
+ serverApp.linkToFile('http://localhost:5622/storage/users/richard/photo.png');
656
+ ```
657
+
125
658
  <!-- ## Platform using MosquitoTransport in production
126
659
  - [Heavenya - christian events](https://heavenya.com)
127
660
  - [Inspire - christian audio](https://inspire.com)
package/TODO ADDED
@@ -0,0 +1,2 @@
1
+ - add method for creating user account
2
+ - add method for login user account
@@ -2,4 +2,5 @@ import SubscriptionListener from "subscription-listener";
2
2
 
3
3
  export const StorageListener = new SubscriptionListener();
4
4
  export const DisconnectionWriteTaskListener = new SubscriptionListener();
5
- export const UserCountReadyListener = new SubscriptionListener();;
5
+ export const UserCountReadyListener = new SubscriptionListener();
6
+ export const SignoutUserSignal = new SubscriptionListener();
@@ -53,7 +53,7 @@ export const deserializeE2E = (data, projectName) => {
53
53
  const [clientPubKey, clientNonce, clientData] = data.split('.'),
54
54
  [_, serverPrivateKey] = Scoped.InstancesData[projectName].E2E_BufferPair || [];
55
55
 
56
- if (serverPrivateKey) throw '"e2eKeyPair" is required for decrypting a e2e messages';
56
+ if (!serverPrivateKey) throw '"e2eKeyPair" is required for decrypting a e2e messages';
57
57
  const baseArray = box.open(
58
58
  Buffer.from(clientData, 'base64'),
59
59
  Buffer.from(clientNonce, 'base64'),
@@ -59,7 +59,6 @@ export const EngineRoutes = {
59
59
  _twitterSignin: '_twitterSignin',
60
60
  _githubSignin: '_githubSignin',
61
61
  _signOut: '_signOut',
62
- _invalidateToken: '_invalidateToken',
63
62
  _uploadFile: '_uploadFile',
64
63
  _deleteFile: '_deleteFile',
65
64
  _deleteFolder: '_deleteFolder',
package/lib/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Auth, Db, Document, MongoClient, SortDirection, UpdateDescription } from "mongodb";
1
+ import { Db, Document, MongoClient, SortDirection, UpdateDescription } from "mongodb";
2
2
  import express from "express";
3
3
  import { CorsOptions } from "cors";
4
4
  import { Sort } from "mongodb";
@@ -50,10 +50,10 @@ interface DatabaseRulesSnapshot {
50
50
  endpoint: '_readDocument' | '_queryCollection' | '_writeDocument' | '_writeMapDocument' | '_documentCount' | '_listenCollection' | '_listenDocument' | '_startDisconnectWriteTask' | '_cancelDisconnectWriteTask';
51
51
  prescription?: DatabaseRulesIOPrescription | DatabaseRulesBatchWritePrescription;
52
52
  dbName?: string;
53
- dbUrl?: string;
53
+ dbRef?: string;
54
54
  }
55
55
 
56
- type LogLevel = 'all' | 'disabled' | 'auth' | 'database' | 'storage' | 'external-requests' | 'served-content' | 'database-snapshot';
56
+ type LogLevel = 'all' | 'auth' | 'database' | 'storage' | 'external-requests' | 'served-content' | 'database-snapshot';
57
57
 
58
58
  interface GoogleAuthConfig {
59
59
  clientID?: string;
@@ -223,6 +223,8 @@ interface MSocketHandshake {
223
223
  };
224
224
  /**
225
225
  * the access token of the user that initiated this handshake
226
+ *
227
+ * N/B: make sure to always revalidate this when making sensitive request
226
228
  */
227
229
  userToken: string | undefined;
228
230
  }
@@ -251,7 +253,7 @@ interface TransformMediaOption {
251
253
 
252
254
  interface TransformMediaRoute {
253
255
  route: typeof RegExp | string;
254
- type?: string;
256
+ transformAs?: 'image' | 'video';
255
257
  transform: (options: TransformMediaOption) => Buffer | string | null | undefined;
256
258
  }
257
259
 
@@ -265,21 +267,155 @@ interface MongoInstancesMap {
265
267
  }
266
268
 
267
269
  interface MosquitoServerConfig {
270
+ /**
271
+ * the name for your mosquito-transport instance. this is required and used internally by both the backend and frontend client
272
+ */
268
273
  projectName: string;
274
+ /**
275
+ * a 90 character string which is used in signing jwt access and refresh token
276
+ */
269
277
  signerKey: string;
278
+
270
279
  storageRules: (snapshot?: StorageRulesSnapshot) => Promise<void> | undefined;
271
280
  databaseRules: (snapshot?: DatabaseRulesSnapshot) => Promise<void> | undefined;
272
281
  onSocketSnapshot?: (snapshot?: MSocketSnapshot, error?: MSocketError) => void;
282
+ /**
283
+ * the port number you want mosquito-transport instance to be running on
284
+ */
273
285
  port?: number;
286
+ /**
287
+ * true if you want new users to be assign a sequential `uid` like 0, 1, 2, 3, 4, 5, ...,
288
+ *
289
+ * Please note: this is an experimental feature
290
+ */
274
291
  enableSequentialUid?: boolean;
292
+ /**
293
+ * a random string used by the frontend client for accessing internal resources
294
+ */
275
295
  accessKey: string;
296
+ /**
297
+ * can either be a string or array containing any of the following:
298
+ *
299
+ * - `all`: log all requests
300
+ * - `auth`: log authentication requests
301
+ * - `database`: log database requests
302
+ * - `storage`: log storage requests
303
+ * - `external-requests`: log api requests
304
+ * - `served-content`: log serve content requests
305
+ * - `database-snapshot`: log database snapshot events
306
+ */
276
307
  logger?: LogLevel | LogLevel[];
308
+ /**
309
+ * this should be a valid http or https link. it is used internally while signing jwt and for prefixing storage `downloadUrl` when uploading a file by frontend client
310
+ */
277
311
  externalAddress?: string;
312
+ /**
313
+ * if no `externalAddress` was provided, `externalAddress` will be a construct as follows:
314
+ *
315
+ * ```js
316
+ * `http://${hostname || 'localhost'}:${port}`
317
+ * ```
318
+ */
278
319
  hostname?: string;
320
+ /**
321
+ * an object that maps names to your mongodb instance. if no `dbRef` were provided, the `default` mongodb instance will be used.
322
+ *
323
+ * ```js
324
+ * import MosquitoTransportServer from "mosquito-transport";
325
+ * import { MongoClient } from 'mongodb';
326
+ *
327
+ * // create a mongodb instance
328
+ * const dbInstance = new MongoClient('mongodb://127.0.0.1:27017');
329
+ * dbInstance.connect();
330
+ *
331
+ * const remoteInstance = new MongoClient('mongodb://other-searver.com');
332
+ * remoteInstance.connect();
333
+ *
334
+ * const serverApp = new MosquitoTransportServer({
335
+ * ...otherProps,
336
+ * mongoInstances: {
337
+ * // frontend client are prohitted from accessing this instance
338
+ * admin: {
339
+ * instance: dbInstance,
340
+ * defaultName: 'ADMIN_DB_NAME'
341
+ * },
342
+ * // this will be the default db if no dbRef was provided by the frontend client
343
+ * default: {
344
+ * instance: dbInstance,
345
+ * defaultName: 'DEFAULT_DB_NAME'
346
+ * },
347
+ * // additional instance
348
+ * remoteBackup: {
349
+ * instance: remoteInstance,
350
+ * defaultName: 'WEB_BACKUP'
351
+ * }
352
+ * }
353
+ * });
354
+ *
355
+ * // then you can access this via frontend client
356
+ *
357
+ * const webInstance = new MosquitoTransport({
358
+ * projectUrl: 'http://localhost:4534/app_name',
359
+ * accessKey: 'some_unique_string',
360
+ * ...options
361
+ * });
362
+ *
363
+ * webInstance.getDatabase(
364
+ * // if this is undefined, the server will use `defaultName` as the default name
365
+ * 'database_name',
366
+ * // the name of the mongoInstances map
367
+ * 'remoteBackup'
368
+ * ).collection('transactions').findOne({ date: { $gt: 1719291129937 } }).get();
369
+ *
370
+ * // or access the default db
371
+ *
372
+ * webInstance.getDatabase().collection('testing');
373
+ * ```
374
+ */
279
375
  mongoInstances: MongoInstancesMap;
376
+ /**
377
+ * true if you want to threat the same email address from different auth provider as a single user
378
+ */
280
379
  mergeAuthAccount?: boolean;
281
380
  transformMediaRoute?: '*' | TransformMediaRoute[];
282
- transformMediaCleanupTimeout?: string;
381
+ /**
382
+ * This is the numbers of milliseconds to cache the transformed video media file before it is deleted. This is basically to avoid the overhead processing time next time the frontend client tries to access it. Defaults to 7 hours.
383
+ */
384
+ transformMediaCleanupTimeout?: number;
385
+ /**
386
+ *
387
+ * a function use in preventing signup and adding metadata before signup
388
+ * ```js
389
+ * import MosquitoTransportServer from "mosquito-transport";
390
+ * const blacklisted_country = ['RU', 'AF', 'NG'];
391
+ *
392
+ * const serverApp = new MosquitoTransportServer({
393
+ * ...otherProps,
394
+ * sneakSignupAuth: ({ request, email, name, password, method }) => {
395
+ * const geo = lookupIpAddress(request.ip);
396
+ * if (!geo) throw 'Failed to lookup request location';
397
+ * if (blacklisted_country.includes(geo.country))
398
+ * throw 'This platform is not yet available in your location';
399
+ *
400
+ * if (method === 'custom' && password.length < 5)
401
+ * throw 'password is too short';
402
+ * const uid = randomString(11),
403
+ * lang = getCountryLang(geo?.country || 'US');
404
+ * return {
405
+ * metadata: {
406
+ * country: geo.country,
407
+ * city: geo.city,
408
+ * location: geo.ll,
409
+ * tz: geo?.timezone,
410
+ * ip: request.ip,
411
+ * locale: 'en'
412
+ * },
413
+ * uid
414
+ * };
415
+ * }
416
+ * });
417
+ * ```
418
+ */
283
419
  sneakSignupAuth?: (config: SneakSignupAuthConfig) => SneakSignupAuthResult;
284
420
  googleAuthConfig?: GoogleAuthConfig;
285
421
  appleAuthConfig?: AppleAuthConfig;
@@ -297,9 +433,13 @@ interface MosquitoServerConfig {
297
433
  accessTokenInterval?: number;
298
434
  refreshTokenExpiry?: number;
299
435
  dumpsterPath?: string;
436
+ /**
437
+ * require an e2e public and private key like:
438
+ * `['public key', 'private key']`
439
+ */
300
440
  e2eKeyPair?: string[] | undefined;
301
441
  enforceE2E?: boolean;
302
- preMiddlewares?: Function[] | Function;
442
+ preMiddlewares?: express.Handler | express.Handler[];
303
443
  }
304
444
 
305
445
  interface UserProfile {
@@ -378,7 +518,7 @@ interface DisconnectTaskInspector extends SimpleError {
378
518
  task?: ({
379
519
  commands: WriteCommand;
380
520
  dbName?: string;
381
- dbUrl?: string;
521
+ dbRef?: string;
382
522
  })
383
523
  }
384
524
 
@@ -394,10 +534,29 @@ interface RawBodyRequest extends express.Request {
394
534
  rawBody: Buffer;
395
535
  }
396
536
 
397
- export default class MosquitoDbServer {
537
+ export default class MosquitoTransportServer {
398
538
  constructor(config: MosquitoServerConfig);
399
539
 
400
- getDatabase(dbName?: string, dbUrl?: string): Db;
540
+ /**
541
+ * the directory where storage files are saved
542
+ */
543
+ get storagePath(): string;
544
+
545
+ /**
546
+ * quickly get an end-to-end encryption pair key for your server
547
+ * @returns [public_string, private_string]
548
+ */
549
+ get sampleE2E(): string[];
550
+
551
+ get express(): express.Application;
552
+
553
+ getDatabase(dbName?: string, dbRef?: string): Db;
554
+
555
+ /**
556
+ * purge all tokens references for a user and sign-out the user immediately
557
+ * @param uid uid of the user you are signing out
558
+ */
559
+ signOutUser(uid: string): Promise<void>;
401
560
 
402
561
  /**
403
562
  * verify token to check if it was trully created using signerKey without checking against the expiry or local token reference
@@ -422,6 +581,10 @@ export default class MosquitoDbServer {
422
581
  * @param isRefreshToken - set this to true if token is a refresh token
423
582
  */
424
583
  invalidateToken(token: string, isRefreshToken?: boolean): Promise<void | boolean>;
584
+
585
+ /**
586
+ * listen to incoming request
587
+ */
425
588
  listenHttpsRequest(route: string, callback?: (request: RawBodyRequest, response: express.Response, auth?: JWTAuthData | null) => void, options?: MosquitoDbHttpOptions): void;
426
589
  listenDatabase(collection: string, callback?: (data: DatabaseListenerCallbackData) => void, options?: DatabaseListenerOption): void;
427
590
  listenStorage(callback?: (snapshot: StorageSnapshot) => void): () => void;
package/lib/index.js CHANGED
@@ -14,11 +14,11 @@ import { validateFacebookAuthConfig } from "./products/auth/facebookAuth.js";
14
14
  import { validateGithubAuthConfig } from "./products/auth/githubAuth.js";
15
15
  import { validateTwitterAuthConfig } from "./products/auth/twitterAuth.js";
16
16
  import { validateFallbackAuthConfig } from "./products/auth/fallbackAuth.js";
17
- import { DisconnectionWriteTaskListener, StorageListener, UserCountReadyListener } from "./helpers/listeners.js";
17
+ import { DisconnectionWriteTaskListener, SignoutUserSignal, StorageListener, UserCountReadyListener } from "./helpers/listeners.js";
18
18
  import EnginePath from "./helpers/EnginePath.js";
19
19
  import { Server } from "socket.io";
20
20
  import http from 'http';
21
- import { mkdir, readFile, unlink, writeFile, rm as rmdir } from "fs/promises";
21
+ import { mkdir, readFile, unlink, writeFile, rm } from "fs/promises";
22
22
  import { cleanUserToken } from "./products/auth/customAuth.js";
23
23
  import { invalidateToken } from "./products/auth/customAuth.js";
24
24
  import cors from 'cors';
@@ -26,6 +26,9 @@ import { parse, stringify } from 'json-buffer';
26
26
  import { exec } from "child_process";
27
27
  import { createRequire } from 'node:module';
28
28
  import { MongoClient } from "mongodb";
29
+ import naclPkg from 'tweetnacl';
30
+
31
+ const { sign: e2eSign } = naclPkg;
29
32
 
30
33
  const _require = createRequire(import.meta.url);
31
34
 
@@ -73,9 +76,8 @@ const serveStorage = ({
73
76
  }
74
77
 
75
78
  const routeTransformer = mediaRoute === '*' ||
76
- (mediaRoute || []).find(({ route, type }) =>
77
- (route instanceof RegExp ? route.test(cleanRoute) : cleanRoute.startsWith(route)) &&
78
- (type === undefined || routeExtension === type)
79
+ (mediaRoute || []).find(({ route }) =>
80
+ (route instanceof RegExp ? route.test(cleanRoute) : cleanRoute.startsWith(route))
79
81
  ),
80
82
  linkRef = requestURL(req),
81
83
  filePath = STORAGE_URL_TO_FILE(linkRef.href, projectName),
@@ -102,6 +104,7 @@ const serveStorage = ({
102
104
  [['loss', 'lossless'], (v) => v === '1' || v === 'true' || undefined],
103
105
  [['vbr'], v => v],
104
106
  [['abr'], v => v],
107
+ [['fps'], v => isNaN(v * 1) ? undefined : v * 1],
105
108
  [['preset'], v => v]
106
109
  ].forEach(([paths, ext]) => {
107
110
  const v = paths.map(v => ext(linkRef.searchParams.get(v) || undefined)).filter(v =>
@@ -121,11 +124,14 @@ const serveStorage = ({
121
124
  if (routeTransformer?.transform) {
122
125
  rib = await routeTransformer.transform({ request: req, localBuffer });
123
126
  if (res.headersSent) return;
124
- } else if ((mediaType === 'image' || mediaType === 'video') && Object.keys(partern).length) {
127
+ } else if (
128
+ (mediaType === 'image' || mediaType === 'video' || routeTransformer?.transformAs) &&
129
+ Object.keys(partern).length
130
+ ) {
125
131
  // console.log('transforming partern:', partern);
126
- const { width, height, grayscale, blur, fit, top, left, flip, flop, format, quality, lossless, mute, vbr, abr, preset } = partern;
132
+ const { width, height, grayscale, blur, fit, top, left, flip, flop, format, quality, lossless, mute, vbr, abr, preset, fps } = partern;
127
133
 
128
- if (mediaType === 'image') {
134
+ if (mediaType === 'image' || routeTransformer?.transformAs === 'image') {
129
135
  const SharpLib = _require('sharp');
130
136
  let sharpInstance = SharpLib(localBuffer);
131
137
 
@@ -176,7 +182,7 @@ const serveStorage = ({
176
182
  inputFile: filePath
177
183
  };
178
184
 
179
- const ffmpegCommad = `ffmpeg -i "${filePath}"${mute ? ' -an' : ''}${com.length ? ' -vf "' + com.join(', ') + '"' : ''}${mute ? '' : ' -c:a copy'}${crf}${vbr ? ' -b:v ' + vbr : ''}${abr ? ' -b:a' + abr : ''} -preset ${preset || 'medium'} "${outPath}"`;
185
+ const ffmpegCommad = `ffmpeg -i "${filePath}"${mute ? ' -an' : ''}${com.length ? ' -vf "' + com.join(', ') + '"' : ''}${mute ? '' : ' -c:a copy'}${crf}${vbr ? ' -b:v ' + vbr : ''}${abr ? ' -b:a' + abr : ''}${fps ? ' -r' + fps : ''} -preset ${preset || 'medium'} "${outPath}"`;
180
186
 
181
187
  exec(ffmpegCommad, async (err) => {
182
188
  if (!Scoped.cacheTranformVideoTimer[outPath]) {
@@ -193,7 +199,7 @@ const serveStorage = ({
193
199
  clearTimeout(Scoped.cacheTranformVideoTimer[outPath].timer);
194
200
  delete Scoped.cacheTranformVideoTimer[outPath];
195
201
  await niceTry(() => unlink(outPath));
196
- }, transformMediaCleanupTimeout || (one_hour / 2));
202
+ }, transformMediaCleanupTimeout || (one_hour * 7));
197
203
  resolve(outPath);
198
204
  Scoped.cacheTranformVideoTimer[outPath].processList?.map?.(([done]) => done(outPath));
199
205
  delete Scoped.cacheTranformVideoTimer[outPath].processing;
@@ -356,7 +362,18 @@ const useMosquitoServer = (app, config) => {
356
362
  authLiveRoutes({ ...config }).map(e => e(socket, scope));
357
363
  databaseLiveRoutes({ ...config }).map(e => e(socket, scope));
358
364
 
359
- if (initAuthHandshake?._m_internal || !onSocketSnapshot) return;
365
+ if (initAuthHandshake?._m_internal || !onSocketSnapshot) {
366
+ if (initAuthHandshake?._from_base) {
367
+ const signoutSignal = SignoutUserSignal.listenTo('d', () => {
368
+ socket.emit('_signal_signout');
369
+ });
370
+
371
+ socket.on('disconnect', () => {
372
+ signoutSignal();
373
+ });
374
+ }
375
+ return;
376
+ }
360
377
  try {
361
378
  const { e2e, ugly, accessKey: ak } = initAuthHandshake;
362
379
 
@@ -555,7 +572,7 @@ export default class MosquitoTransportServer {
555
572
 
556
573
  (async () => {
557
574
  try {
558
- await rmdir(`${STORAGE_PREFIX_PATH(this.projectName)}/.vid_freezer`, {
575
+ await rm(`${STORAGE_PREFIX_PATH(this.projectName)}/.vid_freezer`, {
559
576
  recursive: true,
560
577
  force: true
561
578
  });
@@ -567,6 +584,28 @@ export default class MosquitoTransportServer {
567
584
  get storagePath() {
568
585
  return STORAGE_PATH(this.projectName);
569
586
  }
587
+
588
+ get sampleE2E() {
589
+ const keyPair = e2eSign.keyPair();
590
+ return [
591
+ keyPair.publicKey,
592
+ keyPair.secretKey
593
+ ].map(v => Buffer.from(v).toString('base64'));
594
+ }
595
+
596
+ get express() {
597
+ return Scoped.expressInstances[`${this.port}`];
598
+ }
599
+
600
+ signOutUser = async (uid) => {
601
+ const db = getDB(this.projectName, ADMIN_DB_NAME, ADMIN_DB_URL);
602
+ await Promise.all([
603
+ db.collection(EnginePath.refreshTokenStore).deleteMany({ uid }),
604
+ db.collection(EnginePath.tokenStore).deleteMany({ uid })
605
+ ]);
606
+ SignoutUserSignal.dispatch('d', uid);
607
+ }
608
+
570
609
  verifyToken = (token, isRefreshToken) => verifyJWT(token, this.projectName, isRefreshToken);
571
610
  validateToken = (token, isRefreshToken) => validateJWT(token, this.projectName, isRefreshToken);
572
611
  invalidateToken = (token, isRefreshToken) => invalidateToken(token, this.projectName, isRefreshToken);
@@ -720,7 +759,7 @@ export default class MosquitoTransportServer {
720
759
  path = `${STORAGE_PATH(this.projectName)}/${path}`;
721
760
 
722
761
  removeVideoFreezer(path, true);
723
- await rmdir(path, {
762
+ await rm(path, {
724
763
  recursive: true,
725
764
  force: true
726
765
  });
@@ -865,7 +904,7 @@ export default class MosquitoTransportServer {
865
904
  }
866
905
 
867
906
  const projectNameWrongChar = ['/', '\\', '.', '$', '%', '#', '!', '*', '?'];
868
- const loggerOptions = ['all', 'disabled', 'auth', 'database', 'storage', 'outside-requests', 'content'];
907
+ const loggerOptions = ['all', 'auth', 'database', 'storage', 'external-requests', 'served-content', 'database-snapshot'];
869
908
 
870
909
  const validateResizableMediaRoute = (v) => {
871
910
  if (v !== '*') {
@@ -873,10 +912,10 @@ const validateResizableMediaRoute = (v) => {
873
912
  v.forEach((v, i) => {
874
913
  if (!(v?.route instanceof RegExp) && typeof v?.route !== 'string')
875
914
  throw `"transformMediaRoute" array at index ${i} expected "route" to be either RegularExpression or string but got ${v?.route}`;
876
- if (v?.type !== undefined && typeof v?.type !== 'string')
877
- throw `"transformMediaRoute" array at index ${i} expected "route" to be string`;
878
915
  if (v?.transform !== undefined && typeof v?.transform !== 'function')
879
916
  throw `"transformMediaRoute" array at index ${i} expected "transform" to be a function`;
917
+ if (v?.transformAs !== undefined && v.transformAs !== 'image' && v.transformAs !== 'video')
918
+ throw `"transformAs" must be either "image" or "video" but got "${v.transformAs}"`;
880
919
  });
881
920
  }
882
921
  }
@@ -203,7 +203,8 @@ export const refreshToken = async ({ token, refToken }, projectName) => {
203
203
  tokenID: newTokenID
204
204
  };
205
205
 
206
- if (disabled) throw simplifyError('account_disabled', 'You cannot refresh token for this account because it has been disabled');
206
+ if (disabled)
207
+ throw simplifyError('account_disabled', 'You cannot refresh token for this account because it has been disabled');
207
208
 
208
209
  const [tokenx] = await Promise.all([
209
210
  signJWT({ ...tokenData }, projectName),
@@ -18,8 +18,7 @@ const {
18
18
  _facebookSignin,
19
19
  _twitterSignin,
20
20
  _githubSignin,
21
- _signOut,
22
- _invalidateToken
21
+ _signOut
23
22
  } = EngineRoutes;
24
23
 
25
24
  const authRoute = [
@@ -32,8 +31,7 @@ const authRoute = [
32
31
  // _twitterSignin,
33
32
  // _githubSignin,
34
33
  // _appleSignin,
35
- _signOut,
36
- _invalidateToken
34
+ _signOut
37
35
  ];
38
36
 
39
37
  export const authRoutes = ({
@@ -116,10 +114,6 @@ export const authRoutes = ({
116
114
  ]);
117
115
  res.status(200).send(makeResult({ status: 'success', result: r2 }));
118
116
  break;
119
- case _invalidateToken:
120
- const r3 = await invalidateToken(token, projectName);
121
- res.status(200).send(makeResult({ status: 'success', result: r3 }));
122
- break;
123
117
  case _refreshAuthToken:
124
118
  const r4 = await refreshToken({ token, refToken: r_token }, projectName);
125
119
  res.status(200).send(makeResult({ status: 'success', result: r4 }));
@@ -3,14 +3,14 @@ import { Scoped } from "../../helpers/variables.js";
3
3
 
4
4
  export const getDB = (projectName, name, url = DEFAULT_DB) => {
5
5
  if (!projectName) throw 'expected projectName in getDb()';
6
- if (url === 'admin' || url === 'default') throw `invalid database url: "${url}"`;
6
+ if (url === 'admin' || url === 'default') throw `reserved keyword dbRef: "${url}"`;
7
7
 
8
8
  const dbUrl = url === ADMIN_DB_URL ? 'admin' : url === DEFAULT_DB ? 'default' : url,
9
9
  { defaultName: dbName, instance } = Scoped.InstancesData[projectName].mongoInstances[dbUrl] || {};
10
10
 
11
11
  if (name === ADMIN_DB_NAME) name = dbName;
12
- if (!instance) throw `no MongoClient was found for database with url "${dbUrl}"`;
13
- if (!name && !dbName) throw `no dbName found for database with url "${dbUrl}"`;
12
+ if (!instance) throw `no MongoClient was found for database with dbRef "${dbUrl}"`;
13
+ if (!name && !dbName) throw `no dbName found for database with dbRef "${dbUrl}"`;
14
14
 
15
15
  return instance.db(name || dbName);
16
16
  };
@@ -249,8 +249,8 @@ export const emitDatabase = (path, callback, projectName, dbName, dbUrl, options
249
249
 
250
250
  const col = getDB(projectName, dbName, dbUrl).collection(path),
251
251
  stream = col.watch(pipeline, {
252
- fullDocument: includeAfterData ? 'required' : undefined,
253
- fullDocumentBeforeChange: includeBeforeData ? 'required' : undefined
252
+ fullDocument: includeAfterData ? 'whenAvailable' : undefined,
253
+ fullDocumentBeforeChange: includeBeforeData ? 'whenAvailable' : undefined
254
254
  });
255
255
 
256
256
  stream.on('change', l => {
@@ -325,6 +325,8 @@ const validateDbBody = (body, route) => {
325
325
  if (!b.scope) throw simplifyError('required_field', `scope is required field at index ${i}`);
326
326
  });
327
327
  } else throw simplifyError('invalid_field_type', `"value" must be an array`);
328
+ } else if (k === 'stepping') {
329
+ if (typeof v !== 'boolean') throw simplifyError('invalid_value', `Invalid value supplied to stepping, expected a boolean but got ${v}`);
328
330
  } else throw simplifyError('invalid_field', `Unknown field "${k}"`);
329
331
  });
330
332
  } else if (route === '_readDocument' || route === '_listenDocument') {
@@ -4,9 +4,8 @@ import { Scoped } from "../../helpers/variables";
4
4
  import { validateJWT } from "../auth/tokenizer";
5
5
  import fs from 'fs';
6
6
  import { EngineRoutes, STORAGE_PATH, STORAGE_ROUTE } from "../../helpers/values";
7
- import { mkdirp } from 'mkdirp';
8
7
  import { StorageListener } from "../../helpers/listeners";
9
- import { unlink } from "fs/promises";
8
+ import { mkdir, unlink } from "fs/promises";
10
9
 
11
10
  const { _uploadFile, _deleteFile, _deleteFolder } = EngineRoutes;
12
11
 
@@ -56,7 +55,9 @@ export const storageRoutes = ({ projectName, externalAddress, logger }) => [
56
55
  destErr = validateDestination(destination);
57
56
 
58
57
  if (destErr) throw simplifyError('invalid_destination', destErr);
59
- await mkdirp(tipDir);
58
+ try {
59
+ await mkdir(tipDir, { recursive: true, force: true });
60
+ } catch (error) { }
60
61
  let buf;
61
62
 
62
63
  const sendEvent = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mosquito-transport",
3
- "version": "1.4.3",
3
+ "version": "1.4.6",
4
4
  "description": "MosquitoTransport is a powerful tool that helps persist and synchronize data between your MongoDB database and frontend applications",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
@@ -8,12 +8,7 @@
8
8
  "lib": "lib"
9
9
  },
10
10
  "scripts": {
11
- "test": "echo \"Error: no test specified\" && exit 1",
12
- "mongo-stop": "brew services stop mongodb-community",
13
- "mongo-run": "brew services run mongodb-community",
14
- "mongo-list": "brew services list",
15
- "build": "rm -rf distx && babel lib --out-dir distx --copy-files",
16
- "start": "nodemon --exec babel-node --experimental-modules --es-module-specifier-resolution=node lib/index.js"
11
+ "test": "echo \"Error: no test specified\" && exit 1"
17
12
  },
18
13
  "repository": {
19
14
  "type": "git",
@@ -37,7 +32,6 @@
37
32
  },
38
33
  "homepage": "https://github.com/deflexable/mosquito-transport#readme",
39
34
  "dependencies": {
40
- "@types/express": "^4.17.20",
41
35
  "compression": "^1.7.4",
42
36
  "cors": "^2.8.5",
43
37
  "express": "^4.18.2",
@@ -45,8 +39,6 @@
45
39
  "json-buffer": "^3.0.1",
46
40
  "jsonwebtoken": "^9.0.0",
47
41
  "lodash": "^4.17.21",
48
- "mkdirp": "^3.0.1",
49
- "mongodb": "^5.3.0",
50
42
  "node-fetch": "^3.3.1",
51
43
  "set-large-timeout": "^1.0.1",
52
44
  "socket.io": "^4.6.1",
@@ -54,14 +46,8 @@
54
46
  "tweetnacl": "^1.0.3"
55
47
  },
56
48
  "devDependencies": {
57
- "@babel/cli": "^7.21.5",
58
- "@babel/core": "^7.21.5",
59
- "@babel/node": "^7.20.7",
60
- "@babel/preset-env": "^7.21.5",
61
- "@babel/runtime": "^7.21.5",
49
+ "@types/express": "^4.17.20",
50
+ "@types/mongodb": "^4.0.7",
62
51
  "eslint": "^8.23.1"
63
- },
64
- "engines": {
65
- "node": ">=14"
66
52
  }
67
- }
53
+ }
package/rollup.config.js DELETED
@@ -1,34 +0,0 @@
1
- import babel from '@rollup/plugin-babel';
2
- import resolve from '@rollup/plugin-node-resolve';
3
- import { terser } from 'rollup-plugin-terser';
4
-
5
- export default {
6
- input: './lib/index.js',
7
- plugins: [
8
- resolve(),
9
- babel({
10
- babelHelpers: 'bundled',
11
- presets: [
12
- ['@babel/preset-env', { targets: { node: 'current' }, modules: false }],
13
- ],
14
- }),
15
- // terser(),
16
- ],
17
- output: [
18
- {
19
- dir: 'dist/esm',
20
- format: 'es',
21
- assetFileNames: '[name].[ext]'
22
- },
23
- {
24
- dir: 'dist/cjs',
25
- format: 'cjs',
26
- assetFileNames: '[name].[ext]'
27
- },
28
- // {
29
- // file: 'dist/esm/index.min.js',
30
- // format: 'es',
31
- // },
32
- ],
33
- external: ['mongodb', 'express', 'url', 'path', 'compression', 'socket.io','json-buffer', ''], // Add other external dependencies
34
- };