@bopen-io/messagebox-server 1.2.2 → 1.2.4

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 (143) hide show
  1. package/out/knexfile.js +25 -0
  2. package/out/knexfile.js.map +1 -0
  3. package/out/src/__tests/compose.test.js +25 -0
  4. package/out/src/__tests/compose.test.js.map +1 -0
  5. package/out/src/app.js +162 -0
  6. package/out/src/app.js.map +1 -0
  7. package/out/src/compose.js +239 -0
  8. package/out/src/compose.js.map +1 -0
  9. package/out/src/config/firebase.js +150 -0
  10. package/out/src/config/firebase.js.map +1 -0
  11. package/out/src/context.js +23 -0
  12. package/out/src/context.js.map +1 -0
  13. package/out/src/index.js +311 -0
  14. package/out/src/index.js.map +1 -0
  15. package/out/src/migrations/2022-12-28-001-initial-migration.js +28 -0
  16. package/out/src/migrations/2022-12-28-001-initial-migration.js.map +1 -0
  17. package/out/src/migrations/2023-01-17-messages-update.js +11 -0
  18. package/out/src/migrations/2023-01-17-messages-update.js.map +1 -0
  19. package/out/src/migrations/2024-03-05-001-messageID-upgrade.js +21 -0
  20. package/out/src/migrations/2024-03-05-001-messageID-upgrade.js.map +1 -0
  21. package/out/src/migrations/2025-01-31-001-notification-permissions.js +59 -0
  22. package/out/src/migrations/2025-01-31-001-notification-permissions.js.map +1 -0
  23. package/out/src/migrations/2025-01-31-002-device-registrations.js +24 -0
  24. package/out/src/migrations/2025-01-31-002-device-registrations.js.map +1 -0
  25. package/out/src/routes/__tests/acknowledgeMessage.test.js +135 -0
  26. package/out/src/routes/__tests/acknowledgeMessage.test.js.map +1 -0
  27. package/out/src/routes/__tests/listMessages.test.js +217 -0
  28. package/out/src/routes/__tests/listMessages.test.js.map +1 -0
  29. package/out/src/routes/__tests/sendMessage.test.js +313 -0
  30. package/out/src/routes/__tests/sendMessage.test.js.map +1 -0
  31. package/out/src/routes/acknowledgeMessage.js +133 -0
  32. package/out/src/routes/acknowledgeMessage.js.map +1 -0
  33. package/out/src/routes/index.js +17 -0
  34. package/out/src/routes/index.js.map +1 -0
  35. package/out/src/routes/listDevices.js +63 -0
  36. package/out/src/routes/listDevices.js.map +1 -0
  37. package/out/src/routes/listMessages.js +176 -0
  38. package/out/src/routes/listMessages.js.map +1 -0
  39. package/out/src/routes/permissions/getPermission.js +138 -0
  40. package/out/src/routes/permissions/getPermission.js.map +1 -0
  41. package/out/src/routes/permissions/getQuote.js +165 -0
  42. package/out/src/routes/permissions/getQuote.js.map +1 -0
  43. package/out/src/routes/permissions/index.js +12 -0
  44. package/out/src/routes/permissions/index.js.map +1 -0
  45. package/out/src/routes/permissions/listPermissions.js +179 -0
  46. package/out/src/routes/permissions/listPermissions.js.map +1 -0
  47. package/out/src/routes/permissions/setPermission.js +137 -0
  48. package/out/src/routes/permissions/setPermission.js.map +1 -0
  49. package/out/src/routes/registerDevice.js +139 -0
  50. package/out/src/routes/registerDevice.js.map +1 -0
  51. package/out/src/routes/sendMessage.js +438 -0
  52. package/out/src/routes/sendMessage.js.map +1 -0
  53. package/out/src/runtimeDeps.js +15 -0
  54. package/out/src/runtimeDeps.js.map +1 -0
  55. package/out/src/swagger.js +48 -0
  56. package/out/src/swagger.js.map +1 -0
  57. package/out/src/telemetry.js +134 -0
  58. package/out/src/telemetry.js.map +1 -0
  59. package/out/src/types/messagePermissions.js +2 -0
  60. package/out/src/types/messagePermissions.js.map +1 -0
  61. package/out/src/types/notifications.js +2 -0
  62. package/out/src/types/notifications.js.map +1 -0
  63. package/out/src/utils/logger.js +90 -0
  64. package/out/src/utils/logger.js.map +1 -0
  65. package/out/src/utils/messagePermissions.js +115 -0
  66. package/out/src/utils/messagePermissions.js.map +1 -0
  67. package/out/src/utils/sendFCMNotification.js +110 -0
  68. package/out/src/utils/sendFCMNotification.js.map +1 -0
  69. package/out/tsconfig.tsbuildinfo +1 -0
  70. package/out/types/jest.config.d.ts +4 -0
  71. package/out/types/jest.config.d.ts.map +1 -0
  72. package/out/types/jest.config.integration.d.ts +5 -0
  73. package/out/types/jest.config.integration.d.ts.map +1 -0
  74. package/out/types/knexfile.d.ts +6 -0
  75. package/out/types/knexfile.d.ts.map +1 -0
  76. package/out/types/src/__tests/compose.test.d.ts +2 -0
  77. package/out/types/src/__tests/compose.test.d.ts.map +1 -0
  78. package/out/types/src/app.d.ts +63 -0
  79. package/out/types/src/app.d.ts.map +1 -0
  80. package/out/types/src/compose.d.ts +22 -0
  81. package/out/types/src/compose.d.ts.map +1 -0
  82. package/out/types/src/config/firebase.d.ts +34 -0
  83. package/out/types/src/config/firebase.d.ts.map +1 -0
  84. package/out/types/src/context.d.ts +23 -0
  85. package/out/types/src/context.d.ts.map +1 -0
  86. package/out/types/src/index.d.ts +43 -0
  87. package/out/types/src/index.d.ts.map +1 -0
  88. package/out/types/src/migrations/2022-12-28-001-initial-migration.d.ts +4 -0
  89. package/out/types/src/migrations/2022-12-28-001-initial-migration.d.ts.map +1 -0
  90. package/out/types/src/migrations/2023-01-17-messages-update.d.ts +4 -0
  91. package/out/types/src/migrations/2023-01-17-messages-update.d.ts.map +1 -0
  92. package/out/types/src/migrations/2024-03-05-001-messageID-upgrade.d.ts +4 -0
  93. package/out/types/src/migrations/2024-03-05-001-messageID-upgrade.d.ts.map +1 -0
  94. package/out/types/src/migrations/2025-01-31-001-notification-permissions.d.ts +4 -0
  95. package/out/types/src/migrations/2025-01-31-001-notification-permissions.d.ts.map +1 -0
  96. package/out/types/src/migrations/2025-01-31-002-device-registrations.d.ts +4 -0
  97. package/out/types/src/migrations/2025-01-31-002-device-registrations.d.ts.map +1 -0
  98. package/out/types/src/routes/__tests/acknowledgeMessage.test.d.ts +2 -0
  99. package/out/types/src/routes/__tests/acknowledgeMessage.test.d.ts.map +1 -0
  100. package/out/types/src/routes/__tests/listMessages.test.d.ts +2 -0
  101. package/out/types/src/routes/__tests/listMessages.test.d.ts.map +1 -0
  102. package/out/types/src/routes/__tests/sendMessage.test.d.ts +2 -0
  103. package/out/types/src/routes/__tests/sendMessage.test.d.ts.map +1 -0
  104. package/out/types/src/routes/acknowledgeMessage.d.ts +100 -0
  105. package/out/types/src/routes/acknowledgeMessage.d.ts.map +1 -0
  106. package/out/types/src/routes/index.d.ts +11 -0
  107. package/out/types/src/routes/index.d.ts.map +1 -0
  108. package/out/types/src/routes/listDevices.d.ts +19 -0
  109. package/out/types/src/routes/listDevices.d.ts.map +1 -0
  110. package/out/types/src/routes/listMessages.d.ts +124 -0
  111. package/out/types/src/routes/listMessages.d.ts.map +1 -0
  112. package/out/types/src/routes/permissions/getPermission.d.ts +46 -0
  113. package/out/types/src/routes/permissions/getPermission.d.ts.map +1 -0
  114. package/out/types/src/routes/permissions/getQuote.d.ts +50 -0
  115. package/out/types/src/routes/permissions/getQuote.d.ts.map +1 -0
  116. package/out/types/src/routes/permissions/index.d.ts +18 -0
  117. package/out/types/src/routes/permissions/index.d.ts.map +1 -0
  118. package/out/types/src/routes/permissions/listPermissions.d.ts +106 -0
  119. package/out/types/src/routes/permissions/listPermissions.d.ts.map +1 -0
  120. package/out/types/src/routes/permissions/setPermission.d.ts +53 -0
  121. package/out/types/src/routes/permissions/setPermission.d.ts.map +1 -0
  122. package/out/types/src/routes/registerDevice.d.ts +67 -0
  123. package/out/types/src/routes/registerDevice.d.ts.map +1 -0
  124. package/out/types/src/routes/sendMessage.d.ts +140 -0
  125. package/out/types/src/routes/sendMessage.d.ts.map +1 -0
  126. package/out/types/src/runtimeDeps.d.ts +17 -0
  127. package/out/types/src/runtimeDeps.d.ts.map +1 -0
  128. package/out/types/src/swagger.d.ts +3 -0
  129. package/out/types/src/swagger.d.ts.map +1 -0
  130. package/out/types/src/telemetry.d.ts +2 -0
  131. package/out/types/src/telemetry.d.ts.map +1 -0
  132. package/out/types/src/types/messagePermissions.d.ts +15 -0
  133. package/out/types/src/types/messagePermissions.d.ts.map +1 -0
  134. package/out/types/src/types/notifications.d.ts +20 -0
  135. package/out/types/src/types/notifications.d.ts.map +1 -0
  136. package/out/types/src/utils/logger.d.ts +17 -0
  137. package/out/types/src/utils/logger.d.ts.map +1 -0
  138. package/out/types/src/utils/messagePermissions.d.ts +29 -0
  139. package/out/types/src/utils/messagePermissions.d.ts.map +1 -0
  140. package/out/types/src/utils/sendFCMNotification.d.ts +22 -0
  141. package/out/types/src/utils/sendFCMNotification.d.ts.map +1 -0
  142. package/package.json +1 -1
  143. package/src/utils/logger.ts +6 -2
@@ -0,0 +1,134 @@
1
+ /**
2
+ * OpenTelemetry bootstrap (ESM) — preloaded before app code via
3
+ * `node --import ./out/src/telemetry.js`.
4
+ *
5
+ * Emits traces, metrics and logs. All wiring is driven by OTEL_* env vars; when
6
+ * OTEL_EXPORTER_OTLP_ENDPOINT is unset we fall back to console exporters so the
7
+ * process always boots (dev-safe). Runtime (heap/GC/event-loop) metrics are
8
+ * enabled to support memory-leak diagnosis.
9
+ *
10
+ * ESM note: we deliberately do NOT register the import-in-the-middle loader
11
+ * hook (@opentelemetry/instrumentation/hook.mjs). That hook rebuilds the named
12
+ * exports of CJS packages imported as ESM and drops some of them (e.g.
13
+ * @bsv/sdk's PushDrop), which crashes the app at import time. The libraries we
14
+ * actually instrument (http, express, mongodb, mysql2, pino) are pulled in
15
+ * through CJS dependency chains (overlay-express, wallet-toolbox, authsocket)
16
+ * and are still patched by require-in-the-middle, so coverage is retained.
17
+ *
18
+ * See docs/superpowers/specs/2026-06-22-infra-opentelemetry-design.md.
19
+ */
20
+ import { createRequire } from 'node:module';
21
+ import { join } from 'node:path';
22
+ import { NodeSDK } from '@opentelemetry/sdk-node';
23
+ import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
24
+ import { RuntimeNodeInstrumentation } from '@opentelemetry/instrumentation-runtime-node';
25
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
26
+ import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
27
+ import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
28
+ import { ConsoleSpanExporter, BatchSpanProcessor, SimpleSpanProcessor, } from '@opentelemetry/sdk-trace-base';
29
+ import { PeriodicExportingMetricReader, ConsoleMetricExporter, } from '@opentelemetry/sdk-metrics';
30
+ import { BatchLogRecordProcessor, SimpleLogRecordProcessor, ConsoleLogRecordExporter, } from '@opentelemetry/sdk-logs';
31
+ import { resourceFromAttributes } from '@opentelemetry/resources';
32
+ import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, } from '@opentelemetry/semantic-conventions';
33
+ import { logs, SeverityNumber } from '@opentelemetry/api-logs';
34
+ import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
35
+ // Resolve the component's package.json relative to the working directory (the
36
+ // app root in every Dockerfile and local run) — robust regardless of build layout.
37
+ const require = createRequire(import.meta.url);
38
+ const pkg = require(join(process.cwd(), 'package.json'));
39
+ const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
40
+ const useOtlp = typeof otlpEndpoint === 'string' && otlpEndpoint.length > 0;
41
+ const env = process.env.DEPLOY_ENV ?? process.env.NODE_ENV ?? 'development';
42
+ if (process.env.OTEL_DIAG === 'true') {
43
+ diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);
44
+ }
45
+ const resource = resourceFromAttributes({
46
+ [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME ?? pkg.name,
47
+ [ATTR_SERVICE_VERSION]: pkg.version,
48
+ 'deployment.environment': env,
49
+ });
50
+ const traceExporter = useOtlp
51
+ ? new OTLPTraceExporter()
52
+ : new ConsoleSpanExporter();
53
+ const metricExporter = useOtlp
54
+ ? new OTLPMetricExporter()
55
+ : new ConsoleMetricExporter();
56
+ const logExporter = useOtlp
57
+ ? new OTLPLogExporter()
58
+ : new ConsoleLogRecordExporter();
59
+ const logRecordProcessor = useOtlp
60
+ ? new BatchLogRecordProcessor(logExporter)
61
+ : new SimpleLogRecordProcessor(logExporter);
62
+ const sdk = new NodeSDK({
63
+ resource,
64
+ spanProcessors: [
65
+ useOtlp
66
+ ? new BatchSpanProcessor(traceExporter)
67
+ : new SimpleSpanProcessor(traceExporter),
68
+ ],
69
+ metricReader: new PeriodicExportingMetricReader({
70
+ exporter: metricExporter,
71
+ exportIntervalMillis: Number(process.env.OTEL_METRIC_EXPORT_INTERVAL ?? 60000),
72
+ }),
73
+ logRecordProcessors: [logRecordProcessor],
74
+ instrumentations: [
75
+ getNodeAutoInstrumentations({
76
+ '@opentelemetry/instrumentation-fs': { enabled: false },
77
+ }),
78
+ new RuntimeNodeInstrumentation(),
79
+ ],
80
+ });
81
+ sdk.start();
82
+ // Capture clean console refs before patching, for telemetry's own messages.
83
+ const rawInfo = console.info.bind(console);
84
+ const rawError = console.error.bind(console);
85
+ // Bridge stray console.* calls into OTel logs so nothing is lost while code is
86
+ // migrated to the structured (pino) logger.
87
+ const logger = logs.getLogger(pkg.name, pkg.version);
88
+ const SEVERITY = {
89
+ debug: SeverityNumber.DEBUG,
90
+ info: SeverityNumber.INFO,
91
+ log: SeverityNumber.INFO,
92
+ warn: SeverityNumber.WARN,
93
+ error: SeverityNumber.ERROR,
94
+ };
95
+ for (const method of ['debug', 'info', 'log', 'warn', 'error']) {
96
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
97
+ const original = console[method].bind(console);
98
+ console[method] = (...args) => {
99
+ try {
100
+ logger.emit({
101
+ severityNumber: SEVERITY[method],
102
+ severityText: method.toUpperCase(),
103
+ body: args
104
+ .map((a) => typeof a === 'string' ? a : (() => {
105
+ try {
106
+ return JSON.stringify(a);
107
+ }
108
+ catch {
109
+ return String(a);
110
+ }
111
+ })())
112
+ .join(' '),
113
+ });
114
+ }
115
+ catch {
116
+ /* never let telemetry break logging */
117
+ }
118
+ original(...args);
119
+ };
120
+ }
121
+ // Flush telemetry on shutdown. Only force-exit if the app registered no handler
122
+ // of its own for this signal — otherwise we let the app's shutdown drive exit.
123
+ const shutdown = (signal) => {
124
+ sdk.shutdown()
125
+ .then(() => rawInfo(`[otel] flushed (${signal})`))
126
+ .catch((err) => rawError('[otel] shutdown error', err))
127
+ .finally(() => {
128
+ if (process.listeners(signal).length <= 1)
129
+ process.exit(0);
130
+ });
131
+ };
132
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
133
+ process.on('SIGINT', () => shutdown('SIGINT'));
134
+ //# sourceMappingURL=telemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../src/telemetry.ts"],"names":[],"mappings":"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"}
@@ -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,90 @@
1
+ import pino from 'pino';
2
+ import { createRequire } from 'node:module';
3
+ const require = createRequire(import.meta.url);
4
+ let pkg;
5
+ try {
6
+ pkg = require('../../package.json');
7
+ }
8
+ catch {
9
+ pkg = { name: '@bsv/messagebox-server', version: '0.0.0' };
10
+ }
11
+ // Structured pino logger. @opentelemetry/instrumentation-pino (loaded by
12
+ // telemetry.ts) injects trace_id/span_id into every record, and records are
13
+ // shipped to the OTLP logs endpoint. Stable base fields: service, env.
14
+ const pinoLogger = pino({
15
+ name: pkg.name,
16
+ level: process.env.LOG_LEVEL ?? 'info',
17
+ base: {
18
+ service: process.env.OTEL_SERVICE_NAME ?? pkg.name,
19
+ env: process.env.DEPLOY_ENV ?? process.env.NODE_ENV ?? 'development',
20
+ },
21
+ // Scrub PII / credentials before records leave the process. on-chain data
22
+ // (identity_key, txid) is public and intentionally NOT redacted.
23
+ redact: {
24
+ paths: [
25
+ 'phone', 'phoneNumber', 'identifier',
26
+ 'presentation_key', 'presentationKey', 'payload', 'store',
27
+ 'password', 'pass', 'secret', 'privateKey', 'private_key',
28
+ 'authorization', 'token', 'access_token',
29
+ '*.phone', '*.phoneNumber', '*.identifier', '*.authorization',
30
+ ],
31
+ censor: '[redacted]',
32
+ },
33
+ formatters: {
34
+ level: (label) => ({ level: label }),
35
+ },
36
+ });
37
+ // Export the raw pino logger for call sites that want structured fields:
38
+ // log.info({ operation: 'send', room_id }, 'message delivered')
39
+ export const log = pinoLogger;
40
+ // Split variadic args into a message string + structured detail object so legacy
41
+ // Logger.log('text', value) calls still produce useful structured records.
42
+ function emit(level, args) {
43
+ const strings = [];
44
+ const details = [];
45
+ for (const a of args) {
46
+ if (typeof a === 'string' || typeof a === 'number' || typeof a === 'boolean') {
47
+ strings.push(String(a));
48
+ }
49
+ else {
50
+ details.push(a);
51
+ }
52
+ }
53
+ const msg = strings.join(' ');
54
+ if (details.length > 0) {
55
+ pinoLogger[level]({ detail: details.length === 1 ? details[0] : details }, msg);
56
+ }
57
+ else {
58
+ pinoLogger[level](msg);
59
+ }
60
+ }
61
+ /**
62
+ * Backwards-compatible static facade over the structured logger. Existing
63
+ * Logger.log/warn/error call sites keep working but now emit structured,
64
+ * trace-correlated records. `enable`/`disable` toggle info/warn verbosity;
65
+ * errors are always emitted.
66
+ */
67
+ // eslint-disable-next-line @typescript-eslint/no-extraneous-class
68
+ export class Logger {
69
+ static isEnabled = false;
70
+ static enable() {
71
+ this.isEnabled = true;
72
+ }
73
+ static disable() {
74
+ this.isEnabled = false;
75
+ }
76
+ static log(...args) {
77
+ if (this.isEnabled) {
78
+ emit('info', args);
79
+ }
80
+ }
81
+ static warn(...args) {
82
+ if (this.isEnabled) {
83
+ emit('warn', args);
84
+ }
85
+ }
86
+ static error(...args) {
87
+ emit('error', args);
88
+ }
89
+ }
90
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../../src/utils/logger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,115 @@
1
+ import { Logger } from './logger.js';
2
+ import { runtimeDeps } from '../runtimeDeps.js';
3
+ /**
4
+ * Get server delivery fee for a message box type
5
+ */
6
+ export async function getServerDeliveryFee(messageBox) {
7
+ try {
8
+ const serverFee = await runtimeDeps.knex('server_fees')
9
+ .where({ message_box: messageBox })
10
+ .select('delivery_fee')
11
+ .first();
12
+ return serverFee?.delivery_fee ?? 0;
13
+ }
14
+ catch (error) {
15
+ Logger.error('[ERROR] Error getting server delivery fee:', error);
16
+ return 0;
17
+ }
18
+ }
19
+ /**
20
+ * Get recipient fee for a sender/messageBox combination with hierarchical fallback
21
+ */
22
+ export async function getRecipientFee(recipient, sender, messageBox) {
23
+ try {
24
+ // Debug parameter types
25
+ Logger.log(`[DEBUG] getRecipientFee params - recipient: ${typeof recipient} (${JSON.stringify(recipient)}), sender: ${typeof sender} (${JSON.stringify(sender)}), messageBox: ${typeof messageBox} (${JSON.stringify(messageBox)})`);
26
+ // First try sender-specific permission
27
+ if (sender != null) {
28
+ const senderSpecific = await runtimeDeps.knex('message_permissions')
29
+ .where({
30
+ recipient: String(recipient),
31
+ sender: String(sender),
32
+ message_box: String(messageBox)
33
+ })
34
+ .select('recipient_fee')
35
+ .first();
36
+ if (senderSpecific != null) {
37
+ return senderSpecific.recipient_fee;
38
+ }
39
+ }
40
+ // Fallback to box-wide default
41
+ const boxWideDefault = await runtimeDeps.knex('message_permissions')
42
+ .where({
43
+ recipient: String(recipient),
44
+ sender: null, // Box-wide default
45
+ message_box: String(messageBox)
46
+ })
47
+ .select('recipient_fee')
48
+ .first();
49
+ if (boxWideDefault != null) {
50
+ return boxWideDefault.recipient_fee;
51
+ }
52
+ // Auto-create box-wide default if none exists
53
+ const defaultFee = getSmartDefaultFee(String(messageBox));
54
+ await runtimeDeps.knex('message_permissions').insert({
55
+ recipient: String(recipient),
56
+ sender: null,
57
+ message_box: String(messageBox),
58
+ recipient_fee: defaultFee,
59
+ created_at: new Date(),
60
+ updated_at: new Date()
61
+ });
62
+ Logger.log(`[DEBUG] Created box-wide default permission for ${recipient}/${messageBox} with fee ${defaultFee}`);
63
+ return defaultFee;
64
+ }
65
+ catch (error) {
66
+ Logger.error('[ERROR] Error getting recipient fee:', error);
67
+ return 0; // Block on error
68
+ }
69
+ }
70
+ /**
71
+ * Get smart default fee based on message box type
72
+ */
73
+ function getSmartDefaultFee(messageBox) {
74
+ // Notifications are premium service
75
+ if (messageBox === 'notifications') {
76
+ return 10; // 10 satoshis
77
+ }
78
+ // Other message boxes are always allowed by default
79
+ return 0;
80
+ }
81
+ /**
82
+ * Set message permission for a sender/recipient/messageBox combination
83
+ */
84
+ export async function setMessagePermission(recipient, sender, messageBox, recipientFee) {
85
+ try {
86
+ const now = new Date();
87
+ // Use upsert (insert or update)
88
+ await runtimeDeps.knex('message_permissions')
89
+ .insert({
90
+ recipient,
91
+ sender,
92
+ message_box: messageBox,
93
+ recipient_fee: recipientFee,
94
+ created_at: now,
95
+ updated_at: now
96
+ })
97
+ .onConflict(['recipient', 'sender', 'message_box'])
98
+ .merge({
99
+ recipient_fee: recipientFee,
100
+ updated_at: now
101
+ });
102
+ return true;
103
+ }
104
+ catch (error) {
105
+ Logger.error('[ERROR] Error setting message permission:', error);
106
+ return false;
107
+ }
108
+ }
109
+ /**
110
+ * Check if FCM delivery should be used for this message box
111
+ */
112
+ export function shouldUseFCMDelivery(messageBox) {
113
+ return messageBox === 'notifications';
114
+ }
115
+ //# 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,110 @@
1
+ import { getFirebaseMessaging } from '../config/firebase.js';
2
+ import { Logger } from './logger.js';
3
+ import { runtimeDeps } from '../runtimeDeps.js';
4
+ /**
5
+ * Send FCM push notification to all registered devices for a recipient
6
+ * Looks up FCM tokens from device_registrations table and sends to all active devices
7
+ */
8
+ export async function sendFCMNotification(recipient, payload) {
9
+ try {
10
+ Logger.log(`[DEBUG] Attempting to send FCM notification to ${recipient}`);
11
+ Logger.log('[DEBUG] Payload:', payload);
12
+ // Look up all active FCM tokens for this recipient
13
+ const deviceRegistrations = await runtimeDeps.knex('device_registrations')
14
+ .where({
15
+ identity_key: recipient,
16
+ active: true
17
+ })
18
+ .select('fcm_token', 'platform', 'device_id');
19
+ if (deviceRegistrations.length === 0) {
20
+ Logger.log(`[DEBUG] No active FCM tokens found for recipient ${recipient}`);
21
+ return { success: false, error: 'No registered devices found for recipient' };
22
+ }
23
+ Logger.log(`[DEBUG] Found ${deviceRegistrations.length} active device(s) for ${recipient}`);
24
+ // Send notification to all registered devices
25
+ const sendPromises = deviceRegistrations.map(async (device) => {
26
+ try {
27
+ Logger.log(`[DEBUG] Sending to ${device.platform ?? 'unknown'} device: ${device.device_id ?? 'unknown'}`);
28
+ const messaging = getFirebaseMessaging();
29
+ if (messaging == null) {
30
+ return { success: false, token: device.fcm_token, error: 'Firebase Messaging not initialized (ENABLE_FIREBASE != true)' };
31
+ }
32
+ await messaging.send({
33
+ token: device.fcm_token,
34
+ notification: {
35
+ title: payload.title,
36
+ body: payload.messageId
37
+ },
38
+ // Android configuration for headless service
39
+ android: {
40
+ priority: 'high',
41
+ data: {
42
+ messageId: payload.messageId,
43
+ originator: payload.originator || 'unknown'
44
+ }
45
+ },
46
+ // iOS configuration for mutable content and Notification Service Extension
47
+ apns: {
48
+ headers: {
49
+ 'apns-push-type': 'alert', // required for iOS 13+
50
+ 'apns-priority': '10', // deliver immediately
51
+ // optional: 'apns-topic': '<your app bundle id>' // FCM fills this automatically
52
+ },
53
+ payload: {
54
+ aps: {
55
+ 'mutable-content': 1,
56
+ alert: {
57
+ title: payload.title,
58
+ body: payload.messageId,
59
+ },
60
+ // do NOT set 'content-available': 1 unless you also want background fetch
61
+ },
62
+ // custom keys your NSE can read:
63
+ messageId: payload.messageId,
64
+ originator: payload.originator ?? 'unknown',
65
+ },
66
+ },
67
+ });
68
+ // Update last_used timestamp on successful send
69
+ await runtimeDeps.knex('device_registrations')
70
+ .where('fcm_token', device.fcm_token)
71
+ .update({
72
+ last_used: new Date(),
73
+ updated_at: new Date()
74
+ });
75
+ return { success: true, token: device.fcm_token };
76
+ }
77
+ catch (error) {
78
+ Logger.error(`[FCM ERROR] Failed to send to token ${device.fcm_token.slice(-10)}:`, error);
79
+ // Mark token as inactive if it's invalid
80
+ if (error instanceof Error && (error.message.includes('registration-token-not-registered') ||
81
+ error.message.includes('invalid-registration-token'))) {
82
+ Logger.log(`[DEBUG] Marking invalid token as inactive: ...${device.fcm_token.slice(-10)}`);
83
+ await runtimeDeps.knex('device_registrations')
84
+ .where('fcm_token', device.fcm_token)
85
+ .update({
86
+ active: false,
87
+ updated_at: new Date()
88
+ });
89
+ }
90
+ return { success: false, token: device.fcm_token, error: error instanceof Error ? error.message : String(error) };
91
+ }
92
+ });
93
+ const results = await Promise.all(sendPromises);
94
+ const successCount = results.filter(r => r.success).length;
95
+ const failureCount = results.length - successCount;
96
+ Logger.log(`[DEBUG] FCM notification results: ${successCount} successful, ${failureCount} failed`);
97
+ // Consider it successful if at least one device received the notification
98
+ if (successCount > 0) {
99
+ return { success: true };
100
+ }
101
+ else {
102
+ return { success: false, error: `Failed to send to all ${results.length} registered devices` };
103
+ }
104
+ }
105
+ catch (error) {
106
+ Logger.error('[FCM ERROR] Failed to send FCM notification:', error);
107
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
108
+ }
109
+ }
110
+ //# 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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"}