Skip to content

Commit 98288c7

Browse files
committed
RD-T39 Trying to fix web export
1 parent c560226 commit 98288c7

File tree

115 files changed

+2223
-685
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

115 files changed

+2223
-685
lines changed

Dockerfile

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ RUN yarn install --frozen-lockfile
1515
# Copy source files
1616
COPY . .
1717

18-
# Build the web application without environment variables
19-
# Environment variables will be injected at runtime via docker-entrypoint.sh
20-
RUN yarn web:build
18+
# Build the web application with production defaults
19+
# Runtime environment variables will be injected at startup via docker-entrypoint.sh
20+
# APP_ENV=production ensures the build uses production defaults and no .env suffix on IDs
21+
RUN APP_ENV=production yarn web:build
2122

2223
### STAGE 2: Run ###
2324
FROM nginx:1.25-alpine
@@ -42,6 +43,8 @@ EXPOSE 80
4243
ENV APP_ENV=production \
4344
UNIT_NAME="Resgrid Unit" \
4445
UNIT_SCHEME="ResgridUnit" \
46+
UNIT_BUNDLE_ID="com.resgrid.unit" \
47+
UNIT_PACKAGE="com.resgrid.unit" \
4548
UNIT_VERSION="0.0.1" \
4649
UNIT_BASE_API_URL="https://api.resgrid.com" \
4750
UNIT_API_VERSION="v4" \

babel.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ module.exports = function (api) {
2525
extensions: ['.ios.ts', '.android.ts', '.ts', '.ios.tsx', '.android.tsx', '.tsx', '.jsx', '.js', '.json'],
2626
},
2727
],
28+
'babel-plugin-transform-import-meta',
2829
'react-native-reanimated/plugin',
2930
],
3031
};

docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ services:
1212
- APP_ENV=production
1313
- UNIT_NAME=Resgrid Unit
1414
- UNIT_SCHEME=ResgridUnit
15+
- UNIT_BUNDLE_ID=com.resgrid.unit
16+
- UNIT_PACKAGE=com.resgrid.unit
1517
- UNIT_VERSION=0.0.1
1618
- UNIT_BASE_API_URL=https://api.resgrid.com
1719
- UNIT_API_VERSION=v4

docker/docker-entrypoint.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,18 @@ js_escape() {
1111
}
1212

1313
# Create the env-config.js file with environment variables
14+
# Includes ALL fields expected by the client env schema in env.js
1415
cat > "${HTML_DIR}/env-config.js" << EOF
1516
// Runtime environment configuration - generated by docker-entrypoint.sh
1617
// This file is generated at container startup and injects environment variables
1718
window.__ENV__ = {
1819
APP_ENV: "$(js_escape "${APP_ENV:-production}")",
1920
NAME: "$(js_escape "${UNIT_NAME:-Resgrid Unit}")",
2021
SCHEME: "$(js_escape "${UNIT_SCHEME:-ResgridUnit}")",
22+
BUNDLE_ID: "$(js_escape "${UNIT_BUNDLE_ID:-com.resgrid.unit}")",
23+
PACKAGE: "$(js_escape "${UNIT_PACKAGE:-com.resgrid.unit}")",
2124
VERSION: "$(js_escape "${UNIT_VERSION:-0.0.1}")",
25+
ANDROID_VERSION_CODE: 1,
2226
BASE_API_URL: "$(js_escape "${UNIT_BASE_API_URL:-https://api.resgrid.com}")",
2327
API_VERSION: "$(js_escape "${UNIT_API_VERSION:-v4}")",
2428
RESGRID_API_URL: "$(js_escape "${UNIT_RESGRID_API_URL:-/api/v4}")",

electron/main.js

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
11
/* eslint-disable no-undef */
2-
const { app, BrowserWindow, ipcMain, Notification, nativeTheme, Menu } = require('electron');
2+
const { app, BrowserWindow, ipcMain, Notification, nativeTheme, Menu, protocol, net } = require('electron');
33
const path = require('path');
4+
const fs = require('fs');
5+
6+
// Register custom protocol scheme before app is ready
7+
// This allows serving the Expo web export with absolute paths (/_expo/static/...)
8+
// via a custom protocol instead of file://, which breaks absolute path resolution.
9+
protocol.registerSchemesAsPrivileged([
10+
{
11+
scheme: 'app',
12+
privileges: {
13+
standard: true,
14+
secure: true,
15+
supportFetchAPI: true,
16+
corsEnabled: true,
17+
stream: true,
18+
},
19+
},
20+
]);
421

522
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
623
if (require('electron-squirrel-startup')) {
@@ -33,9 +50,14 @@ function createWindow() {
3350
});
3451

3552
// Load the app
36-
const startUrl = isDev ? 'http://localhost:8081' : `file://${path.join(__dirname, '../dist/index.html')}`;
37-
38-
mainWindow.loadURL(startUrl);
53+
if (isDev) {
54+
// In development, load from the Expo dev server
55+
mainWindow.loadURL('http://localhost:8081');
56+
} else {
57+
// In production, load via the custom app:// protocol
58+
// which correctly resolves absolute paths (/_expo/static/...) from the dist directory
59+
mainWindow.loadURL('app://bundle/index.html');
60+
}
3961

4062
// Show window when ready
4163
mainWindow.once('ready-to-show', () => {
@@ -154,6 +176,30 @@ ipcMain.handle('get-platform', () => {
154176

155177
// Handle app ready
156178
app.whenReady().then(() => {
179+
// Register custom protocol handler for serving the Expo web export
180+
// This resolves absolute paths like /_expo/static/js/... from the dist directory
181+
const distPath = path.join(__dirname, '..', 'dist');
182+
183+
protocol.handle('app', (request) => {
184+
const url = new URL(request.url);
185+
// Decode the pathname and resolve to a file in dist/
186+
let filePath = path.join(distPath, decodeURIComponent(url.pathname));
187+
188+
// If the path points to a directory or file doesn't exist, fall back to index.html
189+
// This supports SPA client-side routing
190+
try {
191+
const stat = fs.statSync(filePath);
192+
if (stat.isDirectory()) {
193+
filePath = path.join(distPath, 'index.html');
194+
}
195+
} catch {
196+
// File not found - serve index.html for client-side routing
197+
filePath = path.join(distPath, 'index.html');
198+
}
199+
200+
return net.fetch('file://' + filePath);
201+
});
202+
157203
createMenu();
158204
createWindow();
159205

global.css

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
11
@tailwind base;
22
@tailwind components;
33
@tailwind utilities;
4+
5+
/* Web-only pulse animation for user location marker on map */
6+
@keyframes pulse-ring {
7+
0%, 100% {
8+
transform: scale(1);
9+
opacity: 0.3;
10+
}
11+
50% {
12+
transform: scale(1.2);
13+
opacity: 0.15;
14+
}
15+
}
16+
17+
/* Web-only skeleton loading animation */
18+
@keyframes skeleton-pulse {
19+
0%, 100% {
20+
opacity: 1;
21+
}
22+
50% {
23+
opacity: 0.75;
24+
}
25+
}

metro.config.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,52 @@ config.resolver.resolveRequest = (context, moduleName, platform) => {
4242
type: 'sourceFile',
4343
};
4444
}
45+
46+
// Countly SDK needs its own shim with proper default export.
47+
// The CountlyConfig subpath must resolve to a dedicated shim whose
48+
// default export is the CountlyConfig class (not the Countly object).
49+
if (moduleName === 'countly-sdk-react-native-bridge/CountlyConfig') {
50+
return {
51+
filePath: path.resolve(__dirname, 'src/lib/countly-config-shim.web.ts'),
52+
type: 'sourceFile',
53+
};
54+
}
55+
if (moduleName === 'countly-sdk-react-native-bridge' || moduleName.startsWith('countly-sdk-react-native-bridge/')) {
56+
return {
57+
filePath: path.resolve(__dirname, 'src/lib/countly-shim.web.ts'),
58+
type: 'sourceFile',
59+
};
60+
}
61+
62+
// Force zustand and related packages to use CJS build instead of ESM
63+
// The ESM build uses import.meta.env which Metro doesn't support
64+
const zustandModules = {
65+
zustand: path.resolve(__dirname, 'node_modules/zustand/index.js'),
66+
'zustand/shallow': path.resolve(__dirname, 'node_modules/zustand/shallow.js'),
67+
'zustand/middleware': path.resolve(__dirname, 'node_modules/zustand/middleware.js'),
68+
'zustand/traditional': path.resolve(__dirname, 'node_modules/zustand/traditional.js'),
69+
'zustand/vanilla': path.resolve(__dirname, 'node_modules/zustand/vanilla.js'),
70+
'zustand/context': path.resolve(__dirname, 'node_modules/zustand/context.js'),
71+
};
72+
73+
if (zustandModules[moduleName]) {
74+
return {
75+
filePath: zustandModules[moduleName],
76+
type: 'sourceFile',
77+
};
78+
}
79+
80+
// Block build-time/dev packages that use import.meta from being bundled
81+
// These are dev tools that should never be included in a client bundle
82+
const buildTimePackages = ['tinyglobby', 'fdir', 'node-gyp', 'electron-builder', 'electron-rebuild', '@electron/rebuild', 'app-builder-lib', 'dmg-builder'];
83+
84+
if (buildTimePackages.some((pkg) => moduleName === pkg || moduleName.startsWith(`${pkg}/`))) {
85+
// Return an empty module shim
86+
return {
87+
filePath: path.resolve(__dirname, 'src/lib/empty-module.web.js'),
88+
type: 'sourceFile',
89+
};
90+
}
4591
}
4692

4793
// Use the original resolver for everything else

nginx.conf

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,17 @@ http {
3838
# Security headers
3939
add_header X-Frame-Options "SAMEORIGIN" always;
4040
add_header X-Content-Type-Options "nosniff" always;
41-
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self';" always;
41+
# Note: CSP connect-src/img-src must allow the configured API URL, Mapbox, Sentry, etc.
42+
# The docker-entrypoint.sh can generate a tighter policy if needed.
43+
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data: https:; connect-src 'self' https: wss:; worker-src 'self' blob:;" always;
4244

4345
# Static assets with cache
4446
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
4547
expires 1y;
4648
add_header Cache-Control "public, immutable";
4749
add_header X-Frame-Options "SAMEORIGIN" always;
4850
add_header X-Content-Type-Options "nosniff" always;
49-
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self';" always;
51+
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data: https:; connect-src 'self' https: wss:; worker-src 'self' blob:;" always;
5052
try_files $uri =404;
5153
}
5254

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@
193193
"@typescript-eslint/eslint-plugin": "~5.62.0",
194194
"@typescript-eslint/parser": "~5.62.0",
195195
"babel-jest": "~30.0.0",
196+
"babel-plugin-transform-import-meta": "^2.3.3",
196197
"concurrently": "9.2.1",
197198
"cross-env": "~7.0.3",
198199
"dotenv": "~16.4.5",

0 commit comments

Comments
 (0)