Mobile-BillTracker/nodejs-assets/nodejs-project/main.js

120 lines
5.1 KiB
JavaScript
Raw Normal View History

'use strict';
const fs = require('fs');
const path = require('path');
// better-sqlite3's better_sqlite3.node is architecture-specific (it links
// against libnode.so, which is the real Android binary already loaded into
// this process by nodejs-mobile-cordova). The copy under node_modules/ is
// whichever architecture it happened to be built for last; replace it with
// the prebuild matching this device's actual arch before anything requires
feat(mobile): run the TypeScript backend on Node 18 in local mode (batch 7-9) The web app migrated its server to TypeScript (server.cts) on Express 5 / openid-client 6 / kysely, none of which ran on the old embedded Node 12.19. Rebuild the local-mode pipeline for Node 18: - sync-nodejs-project.sh: sync server.cts + .cts/.mts + setup/ + seed data. - transpile-node18.js (replaces transpile-node12.js): esbuild TS->CJS in place, keeping .cts/.mts filenames (Node 18's CJS loader loads unknown extensions via the .js handler, so the explicit-extension require() graph keeps resolving). - prepare-local-mode-deps.js: bundle ESM-only kysely to CJS, stub openid-client (unused on-device), and rewrite path-to-regexp's \p{} regexes to ASCII (the no-ICU Node 18 build can't compile unicode-property escapes). - build-better-sqlite3-node18.sh (replaces the Node-12 build/fix scripts): cross-compile for all three ABIs against the Node 18 ABI, injecting libnode.so as a NEEDED entry so V8 symbols resolve at dlopen. - main.js: entry -> server.cts; drop the now-native randomUUID/hkdfSync polyfills (keep the Intl shim); add TMPDIR + a startup-error relay to the WebView. - nodejs-project/package.json: server dep set matched to the web app. Verified end-to-end on the x86_64 emulator: boot -> migrations -> seed -> login -> create bill -> force-stop -> relaunch -> data persists. arm64/armv7 prebuilds are built and link-verified; a physical-device run is still pending. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-11 09:42:53 -05:00
// better-sqlite3. See scripts/build-better-sqlite3-node18.sh.
(function installBetterSqlite3Prebuild() {
const archDirs = {
arm64: 'arm64-v8a',
arm: 'armeabi-v7a',
x64: 'x86_64',
ia32: 'x86',
};
const archDir = archDirs[process.arch];
if (!archDir) return; // Unknown arch — fall back to whatever's bundled.
const prebuilt = path.join(__dirname, 'prebuilds', archDir, 'better_sqlite3.node');
const target = path.join(__dirname, 'node_modules', 'better-sqlite3', 'build', 'Release', 'better_sqlite3.node');
if (!fs.existsSync(prebuilt)) return;
try {
fs.mkdirSync(path.dirname(target), { recursive: true });
fs.copyFileSync(prebuilt, target);
} catch (err) {
console.error('[main.js] Failed to install better-sqlite3 prebuild for', process.arch, err);
}
})();
feat(mobile): run the TypeScript backend on Node 18 in local mode (batch 7-9) The web app migrated its server to TypeScript (server.cts) on Express 5 / openid-client 6 / kysely, none of which ran on the old embedded Node 12.19. Rebuild the local-mode pipeline for Node 18: - sync-nodejs-project.sh: sync server.cts + .cts/.mts + setup/ + seed data. - transpile-node18.js (replaces transpile-node12.js): esbuild TS->CJS in place, keeping .cts/.mts filenames (Node 18's CJS loader loads unknown extensions via the .js handler, so the explicit-extension require() graph keeps resolving). - prepare-local-mode-deps.js: bundle ESM-only kysely to CJS, stub openid-client (unused on-device), and rewrite path-to-regexp's \p{} regexes to ASCII (the no-ICU Node 18 build can't compile unicode-property escapes). - build-better-sqlite3-node18.sh (replaces the Node-12 build/fix scripts): cross-compile for all three ABIs against the Node 18 ABI, injecting libnode.so as a NEEDED entry so V8 symbols resolve at dlopen. - main.js: entry -> server.cts; drop the now-native randomUUID/hkdfSync polyfills (keep the Intl shim); add TMPDIR + a startup-error relay to the WebView. - nodejs-project/package.json: server dep set matched to the web app. Verified end-to-end on the x86_64 emulator: boot -> migrations -> seed -> login -> create bill -> force-stop -> relaunch -> data persists. arm64/armv7 prebuilds are built and link-verified; a physical-device run is still pending. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-11 09:42:53 -05:00
// nodejs-mobile's Node 18.20.4 build ships WITHOUT ICU (verified:
// process.versions.icu === undefined, typeof Intl === 'undefined'), so `Intl`
// is entirely absent. Polyfill just the bits the server uses — en-US number
// formatting with thousands separators, and a no-op DateTimeFormat whose
// resolvedOptions().timeZone the callers already treat as optional.
feat(mobile): run the TypeScript backend on Node 18 in local mode (batch 7-9) The web app migrated its server to TypeScript (server.cts) on Express 5 / openid-client 6 / kysely, none of which ran on the old embedded Node 12.19. Rebuild the local-mode pipeline for Node 18: - sync-nodejs-project.sh: sync server.cts + .cts/.mts + setup/ + seed data. - transpile-node18.js (replaces transpile-node12.js): esbuild TS->CJS in place, keeping .cts/.mts filenames (Node 18's CJS loader loads unknown extensions via the .js handler, so the explicit-extension require() graph keeps resolving). - prepare-local-mode-deps.js: bundle ESM-only kysely to CJS, stub openid-client (unused on-device), and rewrite path-to-regexp's \p{} regexes to ASCII (the no-ICU Node 18 build can't compile unicode-property escapes). - build-better-sqlite3-node18.sh (replaces the Node-12 build/fix scripts): cross-compile for all three ABIs against the Node 18 ABI, injecting libnode.so as a NEEDED entry so V8 symbols resolve at dlopen. - main.js: entry -> server.cts; drop the now-native randomUUID/hkdfSync polyfills (keep the Intl shim); add TMPDIR + a startup-error relay to the WebView. - nodejs-project/package.json: server dep set matched to the web app. Verified end-to-end on the x86_64 emulator: boot -> migrations -> seed -> login -> create bill -> force-stop -> relaunch -> data persists. arm64/armv7 prebuilds are built and link-verified; a physical-device run is still pending. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-11 09:42:53 -05:00
//
// (crypto.randomUUID and crypto.hkdfSync are NATIVE on Node 18 — the Node-12
// shims for them are gone. See PLAN/FUTURE Batch 6.)
if (typeof global.Intl === 'undefined') {
global.Intl = {
NumberFormat: function (_locale, options) {
const opts = options || {};
const minFrac = opts.minimumFractionDigits || 0;
const maxFrac = Math.max(opts.maximumFractionDigits || 0, minFrac);
this.format = function (value) {
const fixed = Number(value).toFixed(maxFrac);
let [intPart, fracPart] = fixed.split('.');
const negative = intPart.startsWith('-');
if (negative) intPart = intPart.slice(1);
intPart = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
let out = (negative ? '-' : '') + intPart;
if (fracPart) out += '.' + fracPart;
return out;
};
},
DateTimeFormat: function () {
this.resolvedOptions = function () {
return { timeZone: undefined };
};
},
};
}
// Server-side paths. CRITICAL: keep the database, backups, and temp OUTSIDE the
// nodejs-project directory. nodejs-mobile-cordova re-copies nodejs-project from
// the APK assets on every app update, which would wipe anything stored inside it
// (a silent data-loss bug). __dirname is <filesDir>/www/nodejs-project, so two
// levels up is the app-private filesDir, which persists across updates.
const DATA_ROOT = path.join(__dirname, '..', '..', 'bt-data');
process.env.DB_PATH = path.join(DATA_ROOT, 'db', 'bills.db');
process.env.BACKUP_PATH = path.join(DATA_ROOT, 'backups');
feat(mobile): run the TypeScript backend on Node 18 in local mode (batch 7-9) The web app migrated its server to TypeScript (server.cts) on Express 5 / openid-client 6 / kysely, none of which ran on the old embedded Node 12.19. Rebuild the local-mode pipeline for Node 18: - sync-nodejs-project.sh: sync server.cts + .cts/.mts + setup/ + seed data. - transpile-node18.js (replaces transpile-node12.js): esbuild TS->CJS in place, keeping .cts/.mts filenames (Node 18's CJS loader loads unknown extensions via the .js handler, so the explicit-extension require() graph keeps resolving). - prepare-local-mode-deps.js: bundle ESM-only kysely to CJS, stub openid-client (unused on-device), and rewrite path-to-regexp's \p{} regexes to ASCII (the no-ICU Node 18 build can't compile unicode-property escapes). - build-better-sqlite3-node18.sh (replaces the Node-12 build/fix scripts): cross-compile for all three ABIs against the Node 18 ABI, injecting libnode.so as a NEEDED entry so V8 symbols resolve at dlopen. - main.js: entry -> server.cts; drop the now-native randomUUID/hkdfSync polyfills (keep the Intl shim); add TMPDIR + a startup-error relay to the WebView. - nodejs-project/package.json: server dep set matched to the web app. Verified end-to-end on the x86_64 emulator: boot -> migrations -> seed -> login -> create bill -> force-stop -> relaunch -> data persists. arm64/armv7 prebuilds are built and link-verified; a physical-device run is still pending. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-11 09:42:53 -05:00
// Android has no /tmp; point os.tmpdir() at app-writable storage so the daily
// worker's temp-export cleanup doesn't error (ENOENT scandir '/tmp').
process.env.TMPDIR = path.join(DATA_ROOT, 'tmp');
try {
fs.mkdirSync(path.dirname(process.env.DB_PATH), { recursive: true });
fs.mkdirSync(process.env.BACKUP_PATH, { recursive: true });
fs.mkdirSync(process.env.TMPDIR, { recursive: true });
} catch (_) { /* best effort — database.cts also ensures the DB dir */ }
process.env.PORT = process.env.PORT || '3000';
process.env.BIND_HOST = '127.0.0.1';
// The Capacitor WebView's origin (androidScheme: 'https') is cross-origin
// from this server's http://localhost:3000, so the LoadingScreen's
// /api/health poll and the app's API calls need CORS allowed for it.
process.env.CORS_ORIGIN = 'https://localhost';
const cordova = require('cordova-bridge');
feat(mobile): run the TypeScript backend on Node 18 in local mode (batch 7-9) The web app migrated its server to TypeScript (server.cts) on Express 5 / openid-client 6 / kysely, none of which ran on the old embedded Node 12.19. Rebuild the local-mode pipeline for Node 18: - sync-nodejs-project.sh: sync server.cts + .cts/.mts + setup/ + seed data. - transpile-node18.js (replaces transpile-node12.js): esbuild TS->CJS in place, keeping .cts/.mts filenames (Node 18's CJS loader loads unknown extensions via the .js handler, so the explicit-extension require() graph keeps resolving). - prepare-local-mode-deps.js: bundle ESM-only kysely to CJS, stub openid-client (unused on-device), and rewrite path-to-regexp's \p{} regexes to ASCII (the no-ICU Node 18 build can't compile unicode-property escapes). - build-better-sqlite3-node18.sh (replaces the Node-12 build/fix scripts): cross-compile for all three ABIs against the Node 18 ABI, injecting libnode.so as a NEEDED entry so V8 symbols resolve at dlopen. - main.js: entry -> server.cts; drop the now-native randomUUID/hkdfSync polyfills (keep the Intl shim); add TMPDIR + a startup-error relay to the WebView. - nodejs-project/package.json: server dep set matched to the web app. Verified end-to-end on the x86_64 emulator: boot -> migrations -> seed -> login -> create bill -> force-stop -> relaunch -> data persists. arm64/armv7 prebuilds are built and link-verified; a physical-device run is still pending. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-11 09:42:53 -05:00
// Surface fatal errors to logcat and to the WebView (LoadingScreen) instead of
// dying silently behind an endless "Starting…" spinner.
function reportError(where, err) {
const msg = err && err.stack ? err.stack : String(err);
console.error('[main.js] ' + where + ':', msg);
try {
cordova.channel.post('message', { type: 'serverError', message: String((err && err.message) || err) });
} catch (_) { /* channel may not be ready */ }
}
process.on('uncaughtException', (err) => reportError('uncaughtException', err));
process.on('unhandledRejection', (err) => reportError('unhandledRejection', err));
// Wait for the WebView to hand over the device-bound encryption key (stored in
// Android Keystore / iOS Keychain — see src/crypto.ts) before starting the
// server, so encryptionService picks up TOKEN_ENCRYPTION_KEY on first use.
cordova.channel.on('message', function (msg) {
if (msg && msg.type === 'encryptionKey' && typeof msg.key === 'string') {
process.env.TOKEN_ENCRYPTION_KEY = msg.key;
feat(mobile): run the TypeScript backend on Node 18 in local mode (batch 7-9) The web app migrated its server to TypeScript (server.cts) on Express 5 / openid-client 6 / kysely, none of which ran on the old embedded Node 12.19. Rebuild the local-mode pipeline for Node 18: - sync-nodejs-project.sh: sync server.cts + .cts/.mts + setup/ + seed data. - transpile-node18.js (replaces transpile-node12.js): esbuild TS->CJS in place, keeping .cts/.mts filenames (Node 18's CJS loader loads unknown extensions via the .js handler, so the explicit-extension require() graph keeps resolving). - prepare-local-mode-deps.js: bundle ESM-only kysely to CJS, stub openid-client (unused on-device), and rewrite path-to-regexp's \p{} regexes to ASCII (the no-ICU Node 18 build can't compile unicode-property escapes). - build-better-sqlite3-node18.sh (replaces the Node-12 build/fix scripts): cross-compile for all three ABIs against the Node 18 ABI, injecting libnode.so as a NEEDED entry so V8 symbols resolve at dlopen. - main.js: entry -> server.cts; drop the now-native randomUUID/hkdfSync polyfills (keep the Intl shim); add TMPDIR + a startup-error relay to the WebView. - nodejs-project/package.json: server dep set matched to the web app. Verified end-to-end on the x86_64 emulator: boot -> migrations -> seed -> login -> create bill -> force-stop -> relaunch -> data persists. arm64/armv7 prebuilds are built and link-verified; a physical-device run is still pending. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-11 09:42:53 -05:00
try {
require('./server/server.cts');
} catch (err) {
reportError('server failed to start', err);
}
}
});
cordova.channel.post('message', { type: 'ready' });