@dbs-portal/core-api
Version:
HTTP client and API utilities for DBS Portal
349 lines (306 loc) • 9.29 kB
JavaScript
/**
* Build utilities for MSW integration
*/
// Use path-browserify for browser environments
import * as nodePath from 'path-browserify';
// Detect if we're running in a Node.js environment
const isNode = typeof process !== 'undefined' &&
process.versions != null &&
process.versions.node != null;
// Initialize fs with empty functions for browser environments
let fs = {
existsSync: () => false,
mkdirSync: () => { },
writeFileSync: () => { }
};
// Dynamically import Node.js modules only in Node environment
if (isNode) {
try {
// Use dynamic import for Node.js modules
fs = require('node:fs');
}
catch (error) {
console.error('Failed to import Node.js fs module:', error);
}
}
/**
* Cross-platform path resolver
*/
function resolvePath(...paths) {
if (isNode) {
// Use Node.js path for server
return require('node:path').resolve(...paths);
}
// Use path-browserify for browser
return paths.reduce((acc, path) => nodePath.resolve(acc, path), '');
}
/**
* Initialize MSW service worker in public directory
*/
export async function initializeMSWServiceWorker(publicDir = 'public', filename = 'mockServiceWorker.js') {
if (!isNode) {
console.warn('MSW service worker initialization is only supported in Node.js environment');
return;
}
const publicPath = resolvePath(process.cwd(), publicDir);
const serviceWorkerPath = resolvePath(publicPath, filename);
// Create public directory if it doesn't exist
if (!fs.existsSync(publicPath)) {
fs.mkdirSync(publicPath, { recursive: true });
}
// Check if service worker already exists
if (fs.existsSync(serviceWorkerPath)) {
console.log(`MSW service worker already exists at ${serviceWorkerPath}`);
return;
}
try {
// Try to use MSW CLI to generate service worker
const { exec } = await import('node:child_process');
const { promisify } = await import('node:util');
const execAsync = promisify(exec);
await execAsync(`npx msw init ${publicDir} --save`);
console.log(`MSW service worker initialized at ${serviceWorkerPath}`);
}
catch (error) {
console.warn('Failed to initialize MSW service worker via CLI, creating manually...');
// Fallback: create service worker manually
const serviceWorkerContent = await generateServiceWorkerContent();
fs.writeFileSync(serviceWorkerPath, serviceWorkerContent, 'utf-8');
console.log(`MSW service worker created manually at ${serviceWorkerPath}`);
}
}
/**
* Generate service worker content
*/
async function generateServiceWorkerContent() {
return `
/**
* Mock Service Worker (MSW) Service Worker
* Generated by @dbs-portal/core-api
*/
/* eslint-disable */
/* tslint:disable */
const INTEGRITY_CHECKSUM = '02f4ad4a2ca266d84b5226dda4ca245d'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()
self.addEventListener('install', function () {
self.skipWaiting()
})
self.addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim())
})
self.addEventListener('message', async function (event) {
const clientId = event.source.id
if (!clientId || !event.data) {
return
}
const allClients = await self.clients.matchAll({
type: 'window',
})
switch (event.data.type) {
case 'KEEPALIVE_REQUEST': {
sendToClient(event.source, {
type: 'KEEPALIVE_RESPONSE',
payload: Date.now(),
})
break
}
case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(event.source, {
type: 'INTEGRITY_CHECK_RESPONSE',
payload: INTEGRITY_CHECKSUM,
})
break
}
case 'MOCK_ACTIVATE': {
activeClientIds.add(clientId)
sendToClient(event.source, {
type: 'MOCKING_ENABLED',
payload: true,
})
break
}
case 'MOCK_DEACTIVATE': {
activeClientIds.delete(clientId)
break
}
case 'CLIENT_CLOSED': {
activeClientIds.delete(clientId)
const remainingClients = allClients.filter((client) => {
return client.id !== clientId
})
if (remainingClients.length === 0) {
self.registration.unregister()
}
break
}
}
})
self.addEventListener('fetch', function (event) {
const { request } = event
if (request.method === 'GET' && request.headers.get('accept')?.includes('text/html')) {
return
}
if (activeClientIds.size === 0) {
return
}
const requestId = Math.random().toString(16).slice(2)
event.respondWith(
handleRequest(event, requestId).catch((error) => {
if (error.name === 'NetworkError') {
console.warn(
'[MSW] Successfully emulated a network error for the "%s %s" request.',
request.method,
request.url,
)
return
}
console.error(
'[MSW] Caught an exception from the "%s %s" request (%s).',
request.method,
request.url,
\`\${error.name}: \${error.message}\`,
)
}),
)
})
async function handleRequest(event, requestId) {
const client = await resolveMainClient(event)
const response = await getResponse(event, client, requestId)
if (client && activeClientIds.has(client.id)) {
;(response.headers || {}).forEach((value, name) => {
event.response.headers.append(name, value)
})
}
return response
}
async function resolveMainClient(event) {
const url = new URL(event.request.url)
if (url.origin === self.location.origin) {
const allClients = await self.clients.matchAll({
type: 'window',
})
return allClients
.filter((client) => activeClientIds.has(client.id))
.find((client) => {
const clientUrl = new URL(client.url)
return clientUrl.pathname === url.pathname
})
}
return await self.clients.get(event.clientId)
}
async function getResponse(event, client, requestId) {
const { request } = event
const clonedRequest = request.clone()
function passthrough() {
return fetch(clonedRequest)
}
if (!client) {
return passthrough()
}
const requestBuffer = await request.arrayBuffer()
const clientMessage = await sendToClient(client, {
type: 'REQUEST',
payload: {
id: requestId,
url: request.url,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
mode: request.mode,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: requestBuffer,
bodyUsed: request.bodyUsed,
keepalive: request.keepalive,
},
})
switch (clientMessage.type) {
case 'MOCK_RESPONSE': {
return respondWithMock(clientMessage.data)
}
case 'MOCK_NOT_FOUND': {
return passthrough()
}
case 'NETWORK_ERROR': {
const error = new Error(clientMessage.data)
error.name = 'NetworkError'
throw error
}
}
return passthrough()
}
function sendToClient(client, message) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()
channel.port1.onmessage = (event) => {
if (event.data && event.data.error) {
return reject(event.data.error)
}
resolve(event.data)
}
client.postMessage(message, [channel.port2])
})
}
function respondWithMock(response) {
return new Response(response.body, response)
}
`.trim();
}
/**
* Check if MSW service worker is properly installed
*/
export function checkMSWServiceWorker(publicDir = 'public', filename = 'mockServiceWorker.js') {
if (!isNode) {
console.warn('MSW service worker check is only supported in Node.js environment');
return false;
}
const serviceWorkerPath = resolvePath(process.cwd(), publicDir, filename);
return fs.existsSync(serviceWorkerPath);
}
/**
* Get MSW build configuration for different environments
*/
export function getMSWBuildConfig(environment) {
const baseConfig = {
enabled: false,
mode: environment,
publicDir: 'public',
serviceWorkerFilename: 'mockServiceWorker.js',
autoGenerateServiceWorker: true,
verbose: false,
};
switch (environment) {
case 'development':
return {
...baseConfig,
enabled: process.env['VITE_ENABLE_MOCKING'] === 'true',
verbose: true,
};
case 'testing':
return {
...baseConfig,
enabled: true,
autoGenerateServiceWorker: false, // Not needed for Node.js tests
verbose: false,
};
case 'storybook':
return {
...baseConfig,
enabled: true,
publicDir: '.storybook/public',
verbose: true,
};
case 'production':
default:
return {
...baseConfig,
enabled: false,
};
}
}
//# sourceMappingURL=build-utils.js.map