@bopen-io/messagebox-server 1.1.5

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.
Files changed (69) hide show
  1. package/README.md +372 -0
  2. package/knexfile.d.ts +5 -0
  3. package/knexfile.ts +29 -0
  4. package/out/knexfile.js +25 -0
  5. package/out/knexfile.js.map +1 -0
  6. package/out/src/app.js +160 -0
  7. package/out/src/app.js.map +1 -0
  8. package/out/src/config/firebase.js +150 -0
  9. package/out/src/config/firebase.js.map +1 -0
  10. package/out/src/index.js +295 -0
  11. package/out/src/index.js.map +1 -0
  12. package/out/src/migrations/2022-12-28-001-initial-migration.js +28 -0
  13. package/out/src/migrations/2022-12-28-001-initial-migration.js.map +1 -0
  14. package/out/src/migrations/2023-01-17-messages-update.js +11 -0
  15. package/out/src/migrations/2023-01-17-messages-update.js.map +1 -0
  16. package/out/src/migrations/2024-03-05-001-messageID-upgrade.js +21 -0
  17. package/out/src/migrations/2024-03-05-001-messageID-upgrade.js.map +1 -0
  18. package/out/src/migrations/2025-01-31-001-notification-permissions.js +59 -0
  19. package/out/src/migrations/2025-01-31-001-notification-permissions.js.map +1 -0
  20. package/out/src/migrations/2025-01-31-002-device-registrations.js +24 -0
  21. package/out/src/migrations/2025-01-31-002-device-registrations.js.map +1 -0
  22. package/out/src/routes/__tests/acknowledgeMessage.test.js +129 -0
  23. package/out/src/routes/__tests/acknowledgeMessage.test.js.map +1 -0
  24. package/out/src/routes/__tests/listMessages.test.js +211 -0
  25. package/out/src/routes/__tests/listMessages.test.js.map +1 -0
  26. package/out/src/routes/__tests/sendMessage.test.js +307 -0
  27. package/out/src/routes/__tests/sendMessage.test.js.map +1 -0
  28. package/out/src/routes/acknowledgeMessage.js +144 -0
  29. package/out/src/routes/acknowledgeMessage.js.map +1 -0
  30. package/out/src/routes/index.js +17 -0
  31. package/out/src/routes/index.js.map +1 -0
  32. package/out/src/routes/listDevices.js +74 -0
  33. package/out/src/routes/listDevices.js.map +1 -0
  34. package/out/src/routes/listMessages.js +186 -0
  35. package/out/src/routes/listMessages.js.map +1 -0
  36. package/out/src/routes/permissions/getPermission.js +149 -0
  37. package/out/src/routes/permissions/getPermission.js.map +1 -0
  38. package/out/src/routes/permissions/getQuote.js +165 -0
  39. package/out/src/routes/permissions/getQuote.js.map +1 -0
  40. package/out/src/routes/permissions/index.js +12 -0
  41. package/out/src/routes/permissions/index.js.map +1 -0
  42. package/out/src/routes/permissions/listPermissions.js +190 -0
  43. package/out/src/routes/permissions/listPermissions.js.map +1 -0
  44. package/out/src/routes/permissions/setPermission.js +137 -0
  45. package/out/src/routes/permissions/setPermission.js.map +1 -0
  46. package/out/src/routes/registerDevice.js +150 -0
  47. package/out/src/routes/registerDevice.js.map +1 -0
  48. package/out/src/routes/sendMessage.js +451 -0
  49. package/out/src/routes/sendMessage.js.map +1 -0
  50. package/out/src/swagger.js +49 -0
  51. package/out/src/swagger.js.map +1 -0
  52. package/out/src/types/messagePermissions.js +2 -0
  53. package/out/src/types/messagePermissions.js.map +1 -0
  54. package/out/src/types/notifications.js +2 -0
  55. package/out/src/types/notifications.js.map +1 -0
  56. package/out/src/utils/logger.js +24 -0
  57. package/out/src/utils/logger.js.map +1 -0
  58. package/out/src/utils/messagePermissions.js +126 -0
  59. package/out/src/utils/messagePermissions.js.map +1 -0
  60. package/out/src/utils/sendFCMNotification.js +121 -0
  61. package/out/src/utils/sendFCMNotification.js.map +1 -0
  62. package/out/tsconfig.tsbuildinfo +1 -0
  63. package/package.json +88 -0
  64. package/src/migrations/2022-12-28-001-initial-migration.ts +31 -0
  65. package/src/migrations/2023-01-17-messages-update.ts +13 -0
  66. package/src/migrations/2024-03-05-001-messageID-upgrade.ts +25 -0
  67. package/src/migrations/2025-01-31-001-notification-permissions.ts +68 -0
  68. package/src/migrations/2025-01-31-002-device-registrations.ts +28 -0
  69. package/src/templates/documentation.ejs +62 -0
@@ -0,0 +1,49 @@
1
+ import swaggerJsdoc from 'swagger-jsdoc';
2
+ import swaggerUi from 'swagger-ui-express';
3
+ export function setupSwagger(app) {
4
+ const options = {
5
+ definition: {
6
+ openapi: '3.0.0',
7
+ info: {
8
+ title: 'MessageBox Server API',
9
+ version: '1.0.0',
10
+ description: 'API documentation for the MessageBox Server, including message delivery, retrieval, acknowledgment, and overlay routing.'
11
+ },
12
+ servers: [
13
+ {
14
+ url: 'http://localhost:5001',
15
+ description: 'Local Development Server'
16
+ },
17
+ {
18
+ url: 'https://messagebox.babbage.systems',
19
+ description: 'Production MessageBox Server'
20
+ }
21
+ ],
22
+ components: {
23
+ securitySchemes: {
24
+ BearerAuth: {
25
+ type: 'http',
26
+ scheme: 'bearer',
27
+ bearerFormat: 'JWT',
28
+ description: 'Provide your signed identity key JWT in the `Authorization` header as: `Bearer <token>`'
29
+ }
30
+ }
31
+ },
32
+ security: [
33
+ {
34
+ BearerAuth: []
35
+ }
36
+ ]
37
+ },
38
+ apis: ['./src/routes/*.ts']
39
+ };
40
+ const swaggerSpec = swaggerJsdoc(options);
41
+ // Swagger UI at /docs
42
+ app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
43
+ // Serve raw OpenAPI spec at /openapi.json
44
+ app.get('/openapi.json', (_req, res) => {
45
+ res.setHeader('Content-Type', 'application/json');
46
+ res.send(swaggerSpec);
47
+ });
48
+ }
49
+ //# sourceMappingURL=swagger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"swagger.js","sourceRoot":"","sources":["../../src/swagger.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAA;AACxC,OAAO,SAAS,MAAM,oBAAoB,CAAA;AAG1C,MAAM,UAAU,YAAY,CAAE,GAAY;IACxC,MAAM,OAAO,GAAG;QACd,UAAU,EAAE;YACV,OAAO,EAAE,OAAO;YAChB,IAAI,EAAE;gBACJ,KAAK,EAAE,uBAAuB;gBAC9B,OAAO,EAAE,OAAO;gBAChB,WAAW,EAAE,0HAA0H;aACxI;YACD,OAAO,EAAE;gBACP;oBACE,GAAG,EAAE,uBAAuB;oBAC5B,WAAW,EAAE,0BAA0B;iBACxC;gBACD;oBACE,GAAG,EAAE,oCAAoC;oBACzC,WAAW,EAAE,8BAA8B;iBAC5C;aACF;YACD,UAAU,EAAE;gBACV,eAAe,EAAE;oBACf,UAAU,EAAE;wBACV,IAAI,EAAE,MAAM;wBACZ,MAAM,EAAE,QAAQ;wBAChB,YAAY,EAAE,KAAK;wBACnB,WAAW,EAAE,yFAAyF;qBACvG;iBACF;aACF;YACD,QAAQ,EAAE;gBACR;oBACE,UAAU,EAAE,EAAE;iBACf;aACF;SACF;QACD,IAAI,EAAE,CAAC,mBAAmB,CAAC;KAC5B,CAAA;IAED,MAAM,WAAW,GAAG,YAAY,CAAC,OAAO,CAAC,CAAA;IAEzC,sBAAsB;IACtB,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAA;IAE/D,0CAA0C;IAC1C,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QACrC,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAA;QACjD,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IACvB,CAAC,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=messagePermissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messagePermissions.js","sourceRoot":"","sources":["../../../src/types/messagePermissions.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=notifications.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../../src/types/notifications.ts"],"names":[],"mappings":""}
@@ -0,0 +1,24 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-extraneous-class
2
+ export class Logger {
3
+ static isEnabled = false;
4
+ static enable() {
5
+ this.isEnabled = true;
6
+ }
7
+ static disable() {
8
+ this.isEnabled = false;
9
+ }
10
+ static log(...args) {
11
+ if (this.isEnabled) {
12
+ console.log(...args);
13
+ }
14
+ }
15
+ static warn(...args) {
16
+ if (this.isEnabled) {
17
+ console.warn(...args);
18
+ }
19
+ }
20
+ static error(...args) {
21
+ console.error(...args);
22
+ }
23
+ }
24
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,MAAM,OAAO,MAAM;IACT,MAAM,CAAC,SAAS,GAAG,KAAK,CAAA;IAEhC,MAAM,CAAC,MAAM;QACX,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;IACvB,CAAC;IAED,MAAM,CAAC,OAAO;QACZ,IAAI,CAAC,SAAS,GAAG,KAAK,CAAA;IACxB,CAAC;IAED,MAAM,CAAC,GAAG,CAAC,GAAG,IAAe;QAC3B,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAA;QACtB,CAAC;IACH,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,GAAG,IAAe;QAC5B,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;QACvB,CAAC;IACH,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,GAAG,IAAe;QAC7B,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAA;IACxB,CAAC"}
@@ -0,0 +1,126 @@
1
+ import knexConfig from '../../knexfile.js';
2
+ import * as knexLib from 'knex';
3
+ import { Logger } from './logger.js';
4
+ // Determine the environment (default to development)
5
+ const { NODE_ENV = 'development' } = process.env;
6
+ /**
7
+ * Knex instance connected based on environment (development, production, or staging).
8
+ */
9
+ const knex = knexLib.default?.(NODE_ENV === 'production' || NODE_ENV === 'staging'
10
+ ? knexConfig.production
11
+ : knexConfig.development) ?? knexLib(NODE_ENV === 'production' || NODE_ENV === 'staging'
12
+ ? knexConfig.production
13
+ : knexConfig.development);
14
+ /**
15
+ * Get server delivery fee for a message box type
16
+ */
17
+ export async function getServerDeliveryFee(messageBox) {
18
+ try {
19
+ const serverFee = await knex('server_fees')
20
+ .where({ message_box: messageBox })
21
+ .select('delivery_fee')
22
+ .first();
23
+ return serverFee?.delivery_fee ?? 0;
24
+ }
25
+ catch (error) {
26
+ Logger.error('[ERROR] Error getting server delivery fee:', error);
27
+ return 0;
28
+ }
29
+ }
30
+ /**
31
+ * Get recipient fee for a sender/messageBox combination with hierarchical fallback
32
+ */
33
+ export async function getRecipientFee(recipient, sender, messageBox) {
34
+ try {
35
+ // Debug parameter types
36
+ Logger.log(`[DEBUG] getRecipientFee params - recipient: ${typeof recipient} (${JSON.stringify(recipient)}), sender: ${typeof sender} (${JSON.stringify(sender)}), messageBox: ${typeof messageBox} (${JSON.stringify(messageBox)})`);
37
+ // First try sender-specific permission
38
+ if (sender != null) {
39
+ const senderSpecific = await knex('message_permissions')
40
+ .where({
41
+ recipient: String(recipient),
42
+ sender: String(sender),
43
+ message_box: String(messageBox)
44
+ })
45
+ .select('recipient_fee')
46
+ .first();
47
+ if (senderSpecific != null) {
48
+ return senderSpecific.recipient_fee;
49
+ }
50
+ }
51
+ // Fallback to box-wide default
52
+ const boxWideDefault = await knex('message_permissions')
53
+ .where({
54
+ recipient: String(recipient),
55
+ sender: null, // Box-wide default
56
+ message_box: String(messageBox)
57
+ })
58
+ .select('recipient_fee')
59
+ .first();
60
+ if (boxWideDefault != null) {
61
+ return boxWideDefault.recipient_fee;
62
+ }
63
+ // Auto-create box-wide default if none exists
64
+ const defaultFee = getSmartDefaultFee(String(messageBox));
65
+ await knex('message_permissions').insert({
66
+ recipient: String(recipient),
67
+ sender: null,
68
+ message_box: String(messageBox),
69
+ recipient_fee: defaultFee,
70
+ created_at: new Date(),
71
+ updated_at: new Date()
72
+ });
73
+ Logger.log(`[DEBUG] Created box-wide default permission for ${recipient}/${messageBox} with fee ${defaultFee}`);
74
+ return defaultFee;
75
+ }
76
+ catch (error) {
77
+ Logger.error('[ERROR] Error getting recipient fee:', error);
78
+ return 0; // Block on error
79
+ }
80
+ }
81
+ /**
82
+ * Get smart default fee based on message box type
83
+ */
84
+ function getSmartDefaultFee(messageBox) {
85
+ // Notifications are premium service
86
+ if (messageBox === 'notifications') {
87
+ return 10; // 10 satoshis
88
+ }
89
+ // Other message boxes are always allowed by default
90
+ return 0;
91
+ }
92
+ /**
93
+ * Set message permission for a sender/recipient/messageBox combination
94
+ */
95
+ export async function setMessagePermission(recipient, sender, messageBox, recipientFee) {
96
+ try {
97
+ const now = new Date();
98
+ // Use upsert (insert or update)
99
+ await knex('message_permissions')
100
+ .insert({
101
+ recipient,
102
+ sender,
103
+ message_box: messageBox,
104
+ recipient_fee: recipientFee,
105
+ created_at: now,
106
+ updated_at: now
107
+ })
108
+ .onConflict(['recipient', 'sender', 'message_box'])
109
+ .merge({
110
+ recipient_fee: recipientFee,
111
+ updated_at: now
112
+ });
113
+ return true;
114
+ }
115
+ catch (error) {
116
+ Logger.error('[ERROR] Error setting message permission:', error);
117
+ return false;
118
+ }
119
+ }
120
+ /**
121
+ * Check if FCM delivery should be used for this message box
122
+ */
123
+ export function shouldUseFCMDelivery(messageBox) {
124
+ return messageBox === 'notifications';
125
+ }
126
+ //# sourceMappingURL=messagePermissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messagePermissions.js","sourceRoot":"","sources":["../../../src/utils/messagePermissions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ import { getFirebaseMessaging } from '../config/firebase.js';
2
+ import { Logger } from './logger.js';
3
+ import knexConfig from '../../knexfile.js';
4
+ import * as knexLib from 'knex';
5
+ // Determine the environment (default to development)
6
+ const { NODE_ENV = 'development' } = process.env;
7
+ /**
8
+ * Knex instance connected based on environment (development, production, or staging).
9
+ */
10
+ const knex = knexLib.default?.(NODE_ENV === 'production' || NODE_ENV === 'staging'
11
+ ? knexConfig.production
12
+ : knexConfig.development) ?? knexLib(NODE_ENV === 'production' || NODE_ENV === 'staging'
13
+ ? knexConfig.production
14
+ : knexConfig.development);
15
+ /**
16
+ * Send FCM push notification to all registered devices for a recipient
17
+ * Looks up FCM tokens from device_registrations table and sends to all active devices
18
+ */
19
+ export async function sendFCMNotification(recipient, payload) {
20
+ try {
21
+ Logger.log(`[DEBUG] Attempting to send FCM notification to ${recipient}`);
22
+ Logger.log('[DEBUG] Payload:', payload);
23
+ // Look up all active FCM tokens for this recipient
24
+ const deviceRegistrations = await knex('device_registrations')
25
+ .where({
26
+ identity_key: recipient,
27
+ active: true
28
+ })
29
+ .select('fcm_token', 'platform', 'device_id');
30
+ if (deviceRegistrations.length === 0) {
31
+ Logger.log(`[DEBUG] No active FCM tokens found for recipient ${recipient}`);
32
+ return { success: false, error: 'No registered devices found for recipient' };
33
+ }
34
+ Logger.log(`[DEBUG] Found ${deviceRegistrations.length} active device(s) for ${recipient}`);
35
+ // Send notification to all registered devices
36
+ const sendPromises = deviceRegistrations.map(async (device) => {
37
+ try {
38
+ Logger.log(`[DEBUG] Sending to ${device.platform ?? 'unknown'} device: ${device.device_id ?? 'unknown'}`);
39
+ const messaging = getFirebaseMessaging();
40
+ if (messaging == null) {
41
+ return { success: false, token: device.fcm_token, error: 'Firebase Messaging not initialized (ENABLE_FIREBASE != true)' };
42
+ }
43
+ await messaging.send({
44
+ token: device.fcm_token,
45
+ notification: {
46
+ title: payload.title,
47
+ body: payload.messageId
48
+ },
49
+ // Android configuration for headless service
50
+ android: {
51
+ priority: 'high',
52
+ data: {
53
+ messageId: payload.messageId,
54
+ originator: payload.originator || 'unknown'
55
+ }
56
+ },
57
+ // iOS configuration for mutable content and Notification Service Extension
58
+ apns: {
59
+ headers: {
60
+ 'apns-push-type': 'alert', // required for iOS 13+
61
+ 'apns-priority': '10', // deliver immediately
62
+ // optional: 'apns-topic': '<your app bundle id>' // FCM fills this automatically
63
+ },
64
+ payload: {
65
+ aps: {
66
+ 'mutable-content': 1,
67
+ alert: {
68
+ title: payload.title,
69
+ body: payload.messageId,
70
+ },
71
+ // do NOT set 'content-available': 1 unless you also want background fetch
72
+ },
73
+ // custom keys your NSE can read:
74
+ messageId: payload.messageId,
75
+ originator: payload.originator ?? 'unknown',
76
+ },
77
+ },
78
+ });
79
+ // Update last_used timestamp on successful send
80
+ await knex('device_registrations')
81
+ .where('fcm_token', device.fcm_token)
82
+ .update({
83
+ last_used: new Date(),
84
+ updated_at: new Date()
85
+ });
86
+ return { success: true, token: device.fcm_token };
87
+ }
88
+ catch (error) {
89
+ Logger.error(`[FCM ERROR] Failed to send to token ${device.fcm_token.slice(-10)}:`, error);
90
+ // Mark token as inactive if it's invalid
91
+ if (error instanceof Error && (error.message.includes('registration-token-not-registered') ||
92
+ error.message.includes('invalid-registration-token'))) {
93
+ Logger.log(`[DEBUG] Marking invalid token as inactive: ...${device.fcm_token.slice(-10)}`);
94
+ await knex('device_registrations')
95
+ .where('fcm_token', device.fcm_token)
96
+ .update({
97
+ active: false,
98
+ updated_at: new Date()
99
+ });
100
+ }
101
+ return { success: false, token: device.fcm_token, error: error instanceof Error ? error.message : String(error) };
102
+ }
103
+ });
104
+ const results = await Promise.all(sendPromises);
105
+ const successCount = results.filter(r => r.success).length;
106
+ const failureCount = results.length - successCount;
107
+ Logger.log(`[DEBUG] FCM notification results: ${successCount} successful, ${failureCount} failed`);
108
+ // Consider it successful if at least one device received the notification
109
+ if (successCount > 0) {
110
+ return { success: true };
111
+ }
112
+ else {
113
+ return { success: false, error: `Failed to send to all ${results.length} registered devices` };
114
+ }
115
+ }
116
+ catch (error) {
117
+ Logger.error('[FCM ERROR] Failed to send FCM notification:', error);
118
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
119
+ }
120
+ }
121
+ //# sourceMappingURL=sendFCMNotification.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sendFCMNotification.js","sourceRoot":"","sources":["../../../src/utils/sendFCMNotification.ts"],"names":[],"mappings":"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"}