@bopen-io/messagebox-server 1.1.5 → 1.2.0
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/.dockerignore +11 -0
- package/.env.example +11 -0
- package/.eslintignore +2 -0
- package/.gcloudignore +17 -0
- package/.gitattributes +2 -0
- package/.github/workflows/build-and-push.yml +51 -0
- package/.github/workflows/deploy.yaml.example +64 -0
- package/.github/workflows/image.yaml +66 -0
- package/.vscode/launch.json +67 -0
- package/AGENTS.md +92 -0
- package/BASELINE.md +55 -0
- package/DEPLOYING.md +120 -0
- package/Dockerfile +63 -0
- package/docker-compose.yml +64 -0
- package/jest.config.integration.ts +26 -0
- package/jest.config.mjs +42 -0
- package/jest.config.ts +35 -0
- package/jest.setup.mjs +18 -0
- package/nginx.conf +36 -0
- package/package.json +65 -51
- package/scripts/build.js +19 -0
- package/scripts/mkenv.sh +31 -0
- package/scripts/start.sh +16 -0
- package/src/__tests/compose.test.ts +30 -0
- package/src/app.ts +210 -0
- package/src/compose.ts +286 -0
- package/src/config/firebase.ts +183 -0
- package/src/context.ts +49 -0
- package/src/index.ts +375 -0
- package/src/routes/__tests/acknowledgeMessage.test.ts +158 -0
- package/src/routes/__tests/listMessages.test.ts +240 -0
- package/src/routes/__tests/sendMessage.test.ts +362 -0
- package/src/routes/acknowledgeMessage.ts +153 -0
- package/src/routes/index.ts +17 -0
- package/src/routes/listDevices.ts +80 -0
- package/src/routes/listMessages.ts +195 -0
- package/src/routes/permissions/getPermission.ts +152 -0
- package/src/routes/permissions/getQuote.ts +192 -0
- package/src/routes/permissions/index.ts +12 -0
- package/src/routes/permissions/listPermissions.ts +202 -0
- package/src/routes/permissions/setPermission.ts +155 -0
- package/src/routes/registerDevice.ts +155 -0
- package/src/routes/sendMessage.ts +531 -0
- package/src/runtimeDeps.ts +29 -0
- package/src/swagger.ts +54 -0
- package/src/telemetry.ts +158 -0
- package/src/types/messagePermissions.ts +18 -0
- package/src/types/mock-knex.d.ts +18 -0
- package/src/types/notifications.ts +27 -0
- package/src/utils/logger.ts +92 -0
- package/src/utils/messagePermissions.ts +148 -0
- package/src/utils/sendFCMNotification.ts +143 -0
- package/tsconfig.json +36 -0
- package/tsconfig.types.json +11 -0
- package/out/knexfile.js +0 -25
- package/out/knexfile.js.map +0 -1
- package/out/src/app.js +0 -160
- package/out/src/app.js.map +0 -1
- package/out/src/config/firebase.js +0 -150
- package/out/src/config/firebase.js.map +0 -1
- package/out/src/index.js +0 -295
- package/out/src/index.js.map +0 -1
- package/out/src/migrations/2022-12-28-001-initial-migration.js +0 -28
- package/out/src/migrations/2022-12-28-001-initial-migration.js.map +0 -1
- package/out/src/migrations/2023-01-17-messages-update.js +0 -11
- package/out/src/migrations/2023-01-17-messages-update.js.map +0 -1
- package/out/src/migrations/2024-03-05-001-messageID-upgrade.js +0 -21
- package/out/src/migrations/2024-03-05-001-messageID-upgrade.js.map +0 -1
- package/out/src/migrations/2025-01-31-001-notification-permissions.js +0 -59
- package/out/src/migrations/2025-01-31-001-notification-permissions.js.map +0 -1
- package/out/src/migrations/2025-01-31-002-device-registrations.js +0 -24
- package/out/src/migrations/2025-01-31-002-device-registrations.js.map +0 -1
- package/out/src/routes/__tests/acknowledgeMessage.test.js +0 -129
- package/out/src/routes/__tests/acknowledgeMessage.test.js.map +0 -1
- package/out/src/routes/__tests/listMessages.test.js +0 -211
- package/out/src/routes/__tests/listMessages.test.js.map +0 -1
- package/out/src/routes/__tests/sendMessage.test.js +0 -307
- package/out/src/routes/__tests/sendMessage.test.js.map +0 -1
- package/out/src/routes/acknowledgeMessage.js +0 -144
- package/out/src/routes/acknowledgeMessage.js.map +0 -1
- package/out/src/routes/index.js +0 -17
- package/out/src/routes/index.js.map +0 -1
- package/out/src/routes/listDevices.js +0 -74
- package/out/src/routes/listDevices.js.map +0 -1
- package/out/src/routes/listMessages.js +0 -186
- package/out/src/routes/listMessages.js.map +0 -1
- package/out/src/routes/permissions/getPermission.js +0 -149
- package/out/src/routes/permissions/getPermission.js.map +0 -1
- package/out/src/routes/permissions/getQuote.js +0 -165
- package/out/src/routes/permissions/getQuote.js.map +0 -1
- package/out/src/routes/permissions/index.js +0 -12
- package/out/src/routes/permissions/index.js.map +0 -1
- package/out/src/routes/permissions/listPermissions.js +0 -190
- package/out/src/routes/permissions/listPermissions.js.map +0 -1
- package/out/src/routes/permissions/setPermission.js +0 -137
- package/out/src/routes/permissions/setPermission.js.map +0 -1
- package/out/src/routes/registerDevice.js +0 -150
- package/out/src/routes/registerDevice.js.map +0 -1
- package/out/src/routes/sendMessage.js +0 -451
- package/out/src/routes/sendMessage.js.map +0 -1
- package/out/src/swagger.js +0 -49
- package/out/src/swagger.js.map +0 -1
- package/out/src/types/messagePermissions.js +0 -2
- package/out/src/types/messagePermissions.js.map +0 -1
- package/out/src/types/notifications.js +0 -2
- package/out/src/types/notifications.js.map +0 -1
- package/out/src/utils/logger.js +0 -24
- package/out/src/utils/logger.js.map +0 -1
- package/out/src/utils/messagePermissions.js +0 -126
- package/out/src/utils/messagePermissions.js.map +0 -1
- package/out/src/utils/sendFCMNotification.js +0 -121
- package/out/src/utils/sendFCMNotification.js.map +0 -1
- package/out/tsconfig.tsbuildinfo +0 -1
package/tsconfig.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": [
|
|
4
|
+
"dom",
|
|
5
|
+
"ESNext"
|
|
6
|
+
],
|
|
7
|
+
"module": "NodeNext",
|
|
8
|
+
"target": "esnext",
|
|
9
|
+
"moduleResolution": "NodeNext",
|
|
10
|
+
"moduleDetection": "force",
|
|
11
|
+
"rootDir": "./",
|
|
12
|
+
"types": ["node", "jest"],
|
|
13
|
+
"outDir": "./out",
|
|
14
|
+
"strict": true,
|
|
15
|
+
"isolatedModules": true,
|
|
16
|
+
"strictNullChecks": true,
|
|
17
|
+
"skipLibCheck": true,
|
|
18
|
+
"forceConsistentCasingInFileNames": true,
|
|
19
|
+
"incremental": true,
|
|
20
|
+
"sourceMap": true,
|
|
21
|
+
"emitDecoratorMetadata": true,
|
|
22
|
+
"experimentalDecorators": true,
|
|
23
|
+
"resolveJsonModule": true,
|
|
24
|
+
"esModuleInterop": true
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"src/index.ts",
|
|
28
|
+
],
|
|
29
|
+
"include": [
|
|
30
|
+
"src"
|
|
31
|
+
],
|
|
32
|
+
"exclude": [
|
|
33
|
+
"out",
|
|
34
|
+
"node_modules"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "./",
|
|
5
|
+
"outDir": "./out/types",
|
|
6
|
+
"emitDeclarationOnly": true,
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"typeRoots": ["./src/utils/types", "./node_modules/@types"]
|
|
10
|
+
}
|
|
11
|
+
}
|
package/out/knexfile.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import dotenv from 'dotenv';
|
|
2
|
-
dotenv.config();
|
|
3
|
-
const connectionConfig = process.env.KNEX_DB_CONNECTION != null && process.env.KNEX_DB_CONNECTION.trim() !== ''
|
|
4
|
-
? JSON.parse(process.env.KNEX_DB_CONNECTION)
|
|
5
|
-
: undefined;
|
|
6
|
-
const config = {
|
|
7
|
-
client: process.env.KNEX_DB_CLIENT ?? 'mysql2',
|
|
8
|
-
connection: connectionConfig,
|
|
9
|
-
useNullAsDefault: true,
|
|
10
|
-
migrations: {
|
|
11
|
-
directory: './out/src/migrations'
|
|
12
|
-
},
|
|
13
|
-
pool: {
|
|
14
|
-
min: 0,
|
|
15
|
-
max: 7,
|
|
16
|
-
idleTimeoutMillis: 15000
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
const knexfile = {
|
|
20
|
-
development: config,
|
|
21
|
-
staging: config,
|
|
22
|
-
production: config
|
|
23
|
-
};
|
|
24
|
-
export default knexfile;
|
|
25
|
-
//# sourceMappingURL=knexfile.js.map
|
package/out/knexfile.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"knexfile.js","sourceRoot":"","sources":["../knexfile.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAA;AAE3B,MAAM,CAAC,MAAM,EAAE,CAAA;AAEf,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,EAAE,KAAK,EAAE;IAC7G,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAC5C,CAAC,CAAC,SAAS,CAAA;AAEb,MAAM,MAAM,GAAgB;IAC1B,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,QAAQ;IAC9C,UAAU,EAAE,gBAAgB;IAC5B,gBAAgB,EAAE,IAAI;IACtB,UAAU,EAAE;QACV,SAAS,EAAE,sBAAsB;KAClC;IACD,IAAI,EAAE;QACJ,GAAG,EAAE,CAAC;QACN,GAAG,EAAE,CAAC;QACN,iBAAiB,EAAE,KAAK;KACzB;CACF,CAAA;AAED,MAAM,QAAQ,GAAmC;IAC/C,WAAW,EAAE,MAAM;IACnB,OAAO,EAAE,MAAM;IACf,UAAU,EAAE,MAAM;CACnB,CAAA;AAED,eAAe,QAAQ,CAAA"}
|
package/out/src/app.js
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file app.ts
|
|
3
|
-
* @description
|
|
4
|
-
* Initializes the MessageBoxServer Express app.
|
|
5
|
-
*
|
|
6
|
-
* Responsibilities:
|
|
7
|
-
* - Parses environment variables and loads config
|
|
8
|
-
* - Sets up Knex for DB access
|
|
9
|
-
* - Initializes WalletClient from the BSV SDK
|
|
10
|
-
* - Mounts Express routes (pre-auth and post-auth)
|
|
11
|
-
* - Applies auth middleware using wallet identity
|
|
12
|
-
*
|
|
13
|
-
* This file exports:
|
|
14
|
-
* - `app`: the configured Express instance
|
|
15
|
-
* - `walletReady`: a promise that resolves once the wallet is ready
|
|
16
|
-
* - `getWallet()`: async accessor for the WalletClient
|
|
17
|
-
* - `useRoutes()`: middleware + route initialization
|
|
18
|
-
* - `appReady`: promise that completes once all setup is done
|
|
19
|
-
*/
|
|
20
|
-
import * as dotenv from 'dotenv';
|
|
21
|
-
import express from 'express';
|
|
22
|
-
import bodyParser from 'body-parser';
|
|
23
|
-
import { preAuth, postAuth } from './routes/index.js';
|
|
24
|
-
import { Logger } from './utils/logger.js';
|
|
25
|
-
import sendMessageRoute from './routes/sendMessage.js';
|
|
26
|
-
import { Setup } from '@bsv/wallet-toolbox';
|
|
27
|
-
import knexLib from 'knex';
|
|
28
|
-
import knexConfig from '../knexfile.js';
|
|
29
|
-
import { createAuthMiddleware } from '@bsv/auth-express-middleware';
|
|
30
|
-
import { createPaymentMiddleware } from '@bsv/payment-express-middleware';
|
|
31
|
-
import { setupSwagger } from './swagger.js';
|
|
32
|
-
import * as crypto from 'crypto';
|
|
33
|
-
global.self = { crypto };
|
|
34
|
-
dotenv.config();
|
|
35
|
-
// Create the Express app instance
|
|
36
|
-
export const app = express();
|
|
37
|
-
// Load environment variables
|
|
38
|
-
const { NODE_ENV = 'development', ROUTING_PREFIX = '', SERVER_PRIVATE_KEY, WALLET_STORAGE_URL, BSV_NETWORK = 'mainnet' } = process.env;
|
|
39
|
-
// Enable logger in dev mode or if explicitly enabled
|
|
40
|
-
if (NODE_ENV === 'development' || process.env.LOGGING_ENABLED === 'true') {
|
|
41
|
-
Logger.enable();
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Knex instance connected based on environment (development, production, or staging).
|
|
45
|
-
*/
|
|
46
|
-
export const knex = knexLib.default?.(NODE_ENV === 'production' || NODE_ENV === 'staging'
|
|
47
|
-
? knexConfig.production
|
|
48
|
-
: knexConfig.development) ?? knexLib(NODE_ENV === 'production' || NODE_ENV === 'staging'
|
|
49
|
-
? knexConfig.production
|
|
50
|
-
: knexConfig.development);
|
|
51
|
-
// Wallet initialization logic
|
|
52
|
-
let _wallet;
|
|
53
|
-
let _resolveReady;
|
|
54
|
-
export const walletReady = new Promise((resolve) => {
|
|
55
|
-
_resolveReady = resolve;
|
|
56
|
-
});
|
|
57
|
-
/**
|
|
58
|
-
* @function initializeWallet
|
|
59
|
-
* @description Initializes the WalletClient with a root identity key and storage backend.
|
|
60
|
-
*
|
|
61
|
-
* Loads configuration from the environment and connects to the wallet service.
|
|
62
|
-
*
|
|
63
|
-
* @returns {Promise<void>} Resolves when the wallet is initialized.
|
|
64
|
-
* @throws If SERVER_PRIVATE_KEY is missing or invalid.
|
|
65
|
-
*/
|
|
66
|
-
export async function initializeWallet() {
|
|
67
|
-
if (SERVER_PRIVATE_KEY == null || SERVER_PRIVATE_KEY.trim() === '') {
|
|
68
|
-
throw new Error('SERVER_PRIVATE_KEY is not defined in environment variables.');
|
|
69
|
-
}
|
|
70
|
-
_wallet = await Setup.createWalletClientNoEnv({
|
|
71
|
-
chain: BSV_NETWORK === 'testnet' ? 'test' : 'main',
|
|
72
|
-
rootKeyHex: SERVER_PRIVATE_KEY,
|
|
73
|
-
storageUrl: WALLET_STORAGE_URL
|
|
74
|
-
});
|
|
75
|
-
_resolveReady();
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* @function getWallet
|
|
79
|
-
* @description Waits for the WalletClient to be ready and returns the instance.
|
|
80
|
-
*
|
|
81
|
-
* @returns {Promise<WalletInterface>} The initialized wallet client
|
|
82
|
-
* @throws {Error} If called before the wallet is initialized
|
|
83
|
-
*/
|
|
84
|
-
export async function getWallet() {
|
|
85
|
-
await walletReady;
|
|
86
|
-
if (_wallet == null) {
|
|
87
|
-
throw new Error('Wallet has not been initialized yet.');
|
|
88
|
-
}
|
|
89
|
-
return _wallet;
|
|
90
|
-
}
|
|
91
|
-
// Run on app startup to prep wallet and activate routes
|
|
92
|
-
export const appReady = (async () => {
|
|
93
|
-
await initializeWallet();
|
|
94
|
-
await useRoutes();
|
|
95
|
-
})();
|
|
96
|
-
/**
|
|
97
|
-
* @function useRoutes
|
|
98
|
-
* @description Registers all routes and middleware on the Express app instance.
|
|
99
|
-
*
|
|
100
|
-
* Steps:
|
|
101
|
-
* - Applies JSON body parser
|
|
102
|
-
* - Enables CORS headers for all routes
|
|
103
|
-
* - Waits for WalletClient to initialize
|
|
104
|
-
* - Adds authentication middleware
|
|
105
|
-
* - Mounts pre-auth and post-auth route handlers
|
|
106
|
-
*
|
|
107
|
-
* @returns {Promise<void>} Once all middleware and routes are mounted
|
|
108
|
-
* @throws If wallet is not available when needed
|
|
109
|
-
*/
|
|
110
|
-
export async function useRoutes() {
|
|
111
|
-
// Parse incoming JSON bodies with a high limit
|
|
112
|
-
app.use(bodyParser.json({ limit: '1gb', type: 'application/json' }));
|
|
113
|
-
// CORS setup
|
|
114
|
-
app.use((req, res, next) => {
|
|
115
|
-
res.header('Access-Control-Allow-Origin', '*');
|
|
116
|
-
res.header('Access-Control-Allow-Headers', '*');
|
|
117
|
-
res.header('Access-Control-Allow-Methods', '*');
|
|
118
|
-
res.header('Access-Control-Expose-Headers', '*');
|
|
119
|
-
res.header('Access-Control-Allow-Private-Network', 'true');
|
|
120
|
-
if (req.method === 'OPTIONS') {
|
|
121
|
-
res.sendStatus(200);
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
next();
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
// Enable Swagger docs
|
|
128
|
-
setupSwagger(app);
|
|
129
|
-
await walletReady;
|
|
130
|
-
if (_wallet == null) {
|
|
131
|
-
throw new Error('Wallet is not initialized for auth middleware');
|
|
132
|
-
}
|
|
133
|
-
app.use(createAuthMiddleware({
|
|
134
|
-
wallet: _wallet,
|
|
135
|
-
logger: console
|
|
136
|
-
}));
|
|
137
|
-
app.use(createPaymentMiddleware({
|
|
138
|
-
wallet: _wallet,
|
|
139
|
-
calculateRequestPrice: async (req) => {
|
|
140
|
-
if (req.url.includes('/sendMessage')) {
|
|
141
|
-
// TODO: Configure a custom price calculation as needed.
|
|
142
|
-
}
|
|
143
|
-
return 0;
|
|
144
|
-
}
|
|
145
|
-
}));
|
|
146
|
-
// Register pre-authentication routes (no auth required)
|
|
147
|
-
preAuth.forEach((route) => {
|
|
148
|
-
app[route.type](`${String(ROUTING_PREFIX)}${String(route.path)}`, route.func);
|
|
149
|
-
});
|
|
150
|
-
// Register post-authentication routes (requires auth header)
|
|
151
|
-
postAuth.forEach((route) => {
|
|
152
|
-
if (route.path === '/sendMessage') {
|
|
153
|
-
app[route.type](`${ROUTING_PREFIX}${route.path}`, sendMessageRoute.func);
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
app[route.type](`${ROUTING_PREFIX}${route.path}`, route.func);
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
//# sourceMappingURL=app.js.map
|
package/out/src/app.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/app.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { initializeApp, getApps, getApp, cert, applicationDefault } from 'firebase-admin/app';
|
|
2
|
-
import { getMessaging } from 'firebase-admin/messaging';
|
|
3
|
-
import { getFirestore } from 'firebase-admin/firestore';
|
|
4
|
-
import * as path from 'path';
|
|
5
|
-
import dotenv from 'dotenv';
|
|
6
|
-
dotenv.config();
|
|
7
|
-
let firebaseApp = null;
|
|
8
|
-
/**
|
|
9
|
-
* Initialize Firebase Admin SDK.
|
|
10
|
-
* Returns null (and logs a warning) when ENABLE_FIREBASE is not 'true',
|
|
11
|
-
* or when FIREBASE_PROJECT_ID is absent, so the server can run without Firebase.
|
|
12
|
-
*/
|
|
13
|
-
export function initializeFirebase() {
|
|
14
|
-
const enableFirebase = process.env.ENABLE_FIREBASE;
|
|
15
|
-
if (enableFirebase !== 'true') {
|
|
16
|
-
console.log(`Firebase is disabled (ENABLE_FIREBASE=${enableFirebase ?? 'unset'}). Skipping initialization.`);
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
if (firebaseApp != null) {
|
|
20
|
-
console.log('Firebase already initialized');
|
|
21
|
-
return firebaseApp;
|
|
22
|
-
}
|
|
23
|
-
try {
|
|
24
|
-
const serviceAccountJson = process.env.FIREBASE_SERVICE_ACCOUNT_JSON;
|
|
25
|
-
const serviceAccountPath = process.env.FIREBASE_SERVICE_ACCOUNT_PATH;
|
|
26
|
-
const projectId = process.env.FIREBASE_PROJECT_ID;
|
|
27
|
-
if (projectId == null || projectId === '') {
|
|
28
|
-
throw new Error('FIREBASE_PROJECT_ID environment variable is required');
|
|
29
|
-
}
|
|
30
|
-
let firebaseCredential; // Will be assigned based on auth method
|
|
31
|
-
if (serviceAccountJson != null && serviceAccountJson !== '') {
|
|
32
|
-
console.log('Using Firebase service account from environment variable');
|
|
33
|
-
try {
|
|
34
|
-
console.log('Service account JSON length:', serviceAccountJson.length);
|
|
35
|
-
console.log('First 100 chars:', serviceAccountJson.substring(0, 100));
|
|
36
|
-
// Debug credential functions
|
|
37
|
-
console.log('cert function:', typeof cert);
|
|
38
|
-
console.log('applicationDefault function:', typeof applicationDefault);
|
|
39
|
-
const serviceAccount = JSON.parse(serviceAccountJson);
|
|
40
|
-
console.log('Parsed service account keys:', Object.keys(serviceAccount ?? {}));
|
|
41
|
-
if (serviceAccount == null || typeof serviceAccount !== 'object') {
|
|
42
|
-
throw new Error('Parsed service account is not a valid object');
|
|
43
|
-
}
|
|
44
|
-
if (serviceAccount.private_key == null || serviceAccount.client_email == null || serviceAccount.project_id == null) {
|
|
45
|
-
throw new Error('Service account missing required fields (private_key, client_email, project_id)');
|
|
46
|
-
}
|
|
47
|
-
firebaseCredential = cert(serviceAccount);
|
|
48
|
-
console.log('✅ Firebase credential created successfully');
|
|
49
|
-
}
|
|
50
|
-
catch (parseError) {
|
|
51
|
-
console.error('❌ Firebase service account parsing failed:', parseError);
|
|
52
|
-
throw new Error(`Failed to parse FIREBASE_SERVICE_ACCOUNT_JSON: ${(parseError instanceof Error) ? parseError.message : 'Invalid JSON'}`);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
else if (serviceAccountPath != null && serviceAccountPath !== '') {
|
|
56
|
-
console.log('Using Firebase service account key file');
|
|
57
|
-
const absolutePath = path.resolve(process.cwd(), serviceAccountPath);
|
|
58
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
59
|
-
firebaseCredential = cert(require(absolutePath));
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
console.log('Using Firebase default credentials');
|
|
63
|
-
firebaseCredential = applicationDefault();
|
|
64
|
-
}
|
|
65
|
-
// Check if Firebase app is already initialized
|
|
66
|
-
if (getApps().length === 0) {
|
|
67
|
-
firebaseApp = initializeApp({
|
|
68
|
-
credential: firebaseCredential,
|
|
69
|
-
projectId
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
firebaseApp = getApp();
|
|
74
|
-
}
|
|
75
|
-
console.log('✅ Firebase Admin SDK initialized successfully');
|
|
76
|
-
return firebaseApp;
|
|
77
|
-
}
|
|
78
|
-
catch (error) {
|
|
79
|
-
console.error('❌ Firebase initialization failed:', error);
|
|
80
|
-
throw error;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Get Firebase Messaging instance, or null if Firebase is not initialized.
|
|
85
|
-
*/
|
|
86
|
-
export function getFirebaseMessaging() {
|
|
87
|
-
if (firebaseApp == null) {
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
return getMessaging(firebaseApp);
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Get Firestore instance
|
|
94
|
-
*/
|
|
95
|
-
export function getFirebaseFirestore() {
|
|
96
|
-
if (firebaseApp == null) {
|
|
97
|
-
throw new Error('Firebase not initialized. Call initializeFirebase() first.');
|
|
98
|
-
}
|
|
99
|
-
return getFirestore(firebaseApp);
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Send a push notification via FCM
|
|
103
|
-
*/
|
|
104
|
-
export async function sendNotification(fcmToken, payload) {
|
|
105
|
-
try {
|
|
106
|
-
const messaging = getFirebaseMessaging();
|
|
107
|
-
if (messaging == null) {
|
|
108
|
-
throw new Error('Firebase Messaging is not initialized (ENABLE_FIREBASE != true)');
|
|
109
|
-
}
|
|
110
|
-
const message = {
|
|
111
|
-
token: fcmToken,
|
|
112
|
-
notification: {
|
|
113
|
-
title: payload.title,
|
|
114
|
-
body: payload.body,
|
|
115
|
-
...(payload.icon && { imageUrl: payload.icon }),
|
|
116
|
-
},
|
|
117
|
-
data: payload.data || {},
|
|
118
|
-
android: {
|
|
119
|
-
priority: "high",
|
|
120
|
-
notification: {
|
|
121
|
-
clickAction: "OPEN_ACTIVITY_1",
|
|
122
|
-
...(payload.badge && { notificationCount: payload.badge }),
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
apns: {
|
|
126
|
-
headers: {
|
|
127
|
-
"apns-priority": "10",
|
|
128
|
-
},
|
|
129
|
-
payload: {
|
|
130
|
-
aps: {
|
|
131
|
-
alert: {
|
|
132
|
-
title: payload.title,
|
|
133
|
-
body: payload.body,
|
|
134
|
-
},
|
|
135
|
-
sound: "default",
|
|
136
|
-
badge: payload.badge || 1,
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
};
|
|
141
|
-
const response = await messaging.send(message);
|
|
142
|
-
console.log("✅ Notification sent successfully:", response);
|
|
143
|
-
return { success: true, messageId: response };
|
|
144
|
-
}
|
|
145
|
-
catch (error) {
|
|
146
|
-
console.error("❌ Failed to send notification:", error);
|
|
147
|
-
throw error;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
//# sourceMappingURL=firebase.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"firebase.js","sourceRoot":"","sources":["../../../src/config/firebase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,EAAY,MAAM,oBAAoB,CAAA;AACvG,OAAO,EAAE,YAAY,EAAgC,MAAM,0BAA0B,CAAA;AACrF,OAAO,EAAE,YAAY,EAAkB,MAAM,0BAA0B,CAAA;AACvE,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAC5B,OAAO,MAAM,MAAM,QAAQ,CAAA;AAE3B,MAAM,CAAC,MAAM,EAAE,CAAA;AAEf,IAAI,WAAW,GAAe,IAAI,CAAA;AAElC;;;;GAIG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;IAElD,IAAI,cAAc,KAAK,MAAM,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,yCAAyC,cAAc,IAAI,OAAO,6BAA6B,CAAC,CAAA;QAC5G,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,WAAW,IAAI,IAAI,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAA;QAC3C,OAAO,WAAW,CAAA;IACpB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,kBAAkB,GAAG,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAA;QACpE,MAAM,kBAAkB,GAAG,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAA;QACpE,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAA;QAEjD,IAAI,SAAS,IAAI,IAAI,IAAI,SAAS,KAAK,EAAE,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAA;QACzE,CAAC;QAED,IAAI,kBAAuB,CAAA,CAAC,wCAAwC;QAEpE,IAAI,kBAAkB,IAAI,IAAI,IAAI,kBAAkB,KAAK,EAAE,EAAE,CAAC;YAC5D,OAAO,CAAC,GAAG,CAAC,0DAA0D,CAAC,CAAA;YACvE,IAAI,CAAC;gBACH,OAAO,CAAC,GAAG,CAAC,8BAA8B,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAA;gBACtE,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;gBAErE,6BAA6B;gBAC7B,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,OAAO,IAAI,CAAC,CAAA;gBAC1C,OAAO,CAAC,GAAG,CAAC,8BAA8B,EAAE,OAAO,kBAAkB,CAAC,CAAA;gBAEtE,MAAM,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAA;gBACrD,OAAO,CAAC,GAAG,CAAC,8BAA8B,EAAE,MAAM,CAAC,IAAI,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC,CAAA;gBAE9E,IAAI,cAAc,IAAI,IAAI,IAAI,OAAO,cAAc,KAAK,QAAQ,EAAE,CAAC;oBACjE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAA;gBACjE,CAAC;gBAED,IAAI,cAAc,CAAC,WAAW,IAAI,IAAI,IAAI,cAAc,CAAC,YAAY,IAAI,IAAI,IAAI,cAAc,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;oBACnH,MAAM,IAAI,KAAK,CAAC,iFAAiF,CAAC,CAAA;gBACpG,CAAC;gBAED,kBAAkB,GAAG,IAAI,CAAC,cAAc,CAAC,CAAA;gBACzC,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAA;YAC3D,CAAC;YAAC,OAAO,UAAU,EAAE,CAAC;gBACpB,OAAO,CAAC,KAAK,CAAC,4CAA4C,EAAE,UAAU,CAAC,CAAA;gBACvE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,UAAU,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAA;YAC1I,CAAC;QACH,CAAC;aAAM,IAAI,kBAAkB,IAAI,IAAI,IAAI,kBAAkB,KAAK,EAAE,EAAE,CAAC;YACnE,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAA;YACtD,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,kBAAkB,CAAC,CAAA;YACpE,8DAA8D;YAC9D,kBAAkB,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAA;QAClD,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAA;YACjD,kBAAkB,GAAG,kBAAkB,EAAE,CAAA;QAC3C,CAAC;QAED,+CAA+C;QAC/C,IAAI,OAAO,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,WAAW,GAAG,aAAa,CAAC;gBAC1B,UAAU,EAAE,kBAAkB;gBAC9B,SAAS;aACV,CAAC,CAAA;QACJ,CAAC;aAAM,CAAC;YACN,WAAW,GAAG,MAAM,EAAE,CAAA;QACxB,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAA;QAC5D,OAAO,WAAW,CAAA;IACpB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,mCAAmC,EAAE,KAAK,CAAC,CAAA;QACzD,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB;IAClC,IAAI,WAAW,IAAI,IAAI,EAAE,CAAC;QACxB,OAAO,IAAI,CAAA;IACb,CAAC;IACD,OAAO,YAAY,CAAC,WAAW,CAAC,CAAA;AAClC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB;IAClC,IAAI,WAAW,IAAI,IAAI,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,4DAA4D,CAC7D,CAAA;IACH,CAAC;IACD,OAAO,YAAY,CAAC,WAAW,CAAC,CAAA;AAClC,CAAC;AAeD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,QAAgB,EAChB,OAAmB;IAEnB,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,oBAAoB,EAAE,CAAA;QAExC,IAAI,SAAS,IAAI,IAAI,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAA;QACpF,CAAC;QAED,MAAM,OAAO,GAAY;YACvB,KAAK,EAAE,QAAQ;YACf,YAAY,EAAE;gBACZ,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;aAChD;YACD,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,EAAE;YACxB,OAAO,EAAE;gBACP,QAAQ,EAAE,MAAM;gBAChB,YAAY,EAAE;oBACZ,WAAW,EAAE,iBAAiB;oBAC9B,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,iBAAiB,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;iBAC3D;aACF;YACD,IAAI,EAAE;gBACJ,OAAO,EAAE;oBACP,eAAe,EAAE,IAAI;iBACtB;gBACD,OAAO,EAAE;oBACP,GAAG,EAAE;wBACH,KAAK,EAAE;4BACL,KAAK,EAAE,OAAO,CAAC,KAAK;4BACpB,IAAI,EAAE,OAAO,CAAC,IAAI;yBACnB;wBACD,KAAK,EAAE,SAAS;wBAChB,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;qBAC1B;iBACF;aACF;SACF,CAAC;QAEF,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/C,OAAO,CAAC,GAAG,CAAC,mCAAmC,EAAE,QAAQ,CAAC,CAAC;QAC3D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;IAChD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,KAAK,CAAC,CAAC;QACvD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
|