Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion src/frontend/apps/conversations/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,29 @@
env: {
NEXT_PUBLIC_BUILD_ID: buildId,
},
webpack(config, { isServer }) {
webpack(config, { isServer, dev, webpack }) {
// Only configure service worker for client-side builds in production
if (!isServer && !dev) {
const path = require('path');

Check warning on line 25 in src/frontend/apps/conversations/next.config.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `node:path` over `path`.

See more on https://sonarcloud.io/project/issues?id=suitenumerique_conversations&issues=AZq6QTs0AkcgHC3iMOzD&open=AZq6QTs0AkcgHC3iMOzD&pullRequest=150
config.plugins.push(
new InjectManifest({
swSrc: path.join(__dirname, 'public', 'sw.src.js'),
swDest: path.join(__dirname, 'public', 'sw.js'), // Output compiled SW to public, Next.js will copy to out/
exclude: [
/\.map$/,
/manifest$/,
/\.htaccess$/,
/service-worker\.js$/,
/sw\.js$/,
],
// Maximum file size to cache in bytes (5MB)
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
// Compile the service worker with webpack
compileSrc: true,
})
);
}

// Grab the existing rule that handles SVG imports
const fileLoaderRule = config.module.rules.find((rule) =>
rule.test?.test?.('.svg'),
Expand Down
6 changes: 6 additions & 0 deletions src/frontend/apps/conversations/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@
"stylelint-prettier": "5.0.3",
"typescript": "*",
"webpack": "5.99.9",
"workbox-cacheable-response": "7.1.0",
"workbox-core": "7.1.0",
"workbox-expiration": "7.1.0",
"workbox-precaching": "7.1.0",
"workbox-routing": "7.1.0",
"workbox-strategies": "7.1.0",
"workbox-webpack-plugin": "7.1.0"
}
}
39 changes: 39 additions & 0 deletions src/frontend/apps/conversations/public/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "Assistant",
"short_name": "Assistant",
"description": "Your new companion to use AI efficiently, intuitively, and securely.",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#1B2E5F",
"orientation": "portrait-primary",
"icons": [
{
"src": "/assets/favicon.ico",
"sizes": "48x48",
"type": "image/x-icon"
},
{
"src": "/assets/favicon-light.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/assets/favicon-dark.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/assets/icon-assistant.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any maskable"
}
],
"categories": ["productivity", "utilities"],
"screenshots": [],
"shortcuts": []
}

100 changes: 100 additions & 0 deletions src/frontend/apps/conversations/public/sw.src.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Service Worker for PWA offline functionality
// This file will be processed by workbox-webpack-plugin during build

// Import workbox modules
import { clientsClaim } from 'workbox-core';
import { ExpirationPlugin } from 'workbox-expiration';
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate, CacheFirst, NetworkFirst } from 'workbox-strategies';

Check warning on line 9 in src/frontend/apps/conversations/public/sw.src.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused import of 'StaleWhileRevalidate'.

See more on https://sonarcloud.io/project/issues?id=suitenumerique_conversations&issues=AZq6QTtCAkcgHC3iMOzE&open=AZq6QTtCAkcgHC3iMOzE&pullRequest=150
import { CacheableResponsePlugin } from 'workbox-cacheable-response';

// Take control of all clients as soon as the service worker is activated
clientsClaim();

// Precache all of the assets generated by the build process
precacheAndRoute(self.__WB_MANIFEST);

Check warning on line 16 in src/frontend/apps/conversations/public/sw.src.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `self`.

See more on https://sonarcloud.io/project/issues?id=suitenumerique_conversations&issues=AZq6QTtCAkcgHC3iMOzF&open=AZq6QTtCAkcgHC3iMOzF&pullRequest=150

// Set up App Shell-style routing, so that all navigation requests
// are fulfilled with the index.html shell.
const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');

Check warning on line 20 in src/frontend/apps/conversations/public/sw.src.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use a regular expression literal instead of the 'RegExp' constructor.

See more on https://sonarcloud.io/project/issues?id=suitenumerique_conversations&issues=AZq6QTtCAkcgHC3iMOzG&open=AZq6QTtCAkcgHC3iMOzG&pullRequest=150

Check warning on line 20 in src/frontend/apps/conversations/public/sw.src.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

`String.raw` should be used to avoid escaping `\`.

See more on https://sonarcloud.io/project/issues?id=suitenumerique_conversations&issues=AZq6QTtCAkcgHC3iMOzI&open=AZq6QTtCAkcgHC3iMOzI&pullRequest=150
registerRoute(
// Return false to exempt requests from being fulfilled by index.html.
({ request, url }) => {
// If this isn't a navigation, skip.
if (request.mode !== 'navigate') {
return false;
} // If this is a URL that starts with /_, skip.
if (url.pathname.startsWith('/_')) {
return false;
} // If this looks like a URL for a resource, because it contains // a file extension, skip.
if (url.pathname.match(fileExtensionRegexp)) {
return false;
}
return true;
},
createHandlerBoundToURL('/index.html')
);

// Cache static assets (JS, CSS, images) with CacheFirst strategy
registerRoute(
({ request }) =>
request.destination === 'script' ||
request.destination === 'style' ||
request.destination === 'image',
new CacheFirst({
cacheName: 'static-resources',
plugins: [
new CacheableResponsePlugin({
statuses: [0, 200],
}),
new ExpirationPlugin({
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days
}),
],
})
);

// Cache API responses with NetworkFirst strategy
registerRoute(
({ url }) => url.origin === self.location.origin && url.pathname.startsWith('/api/'),
new NetworkFirst({
cacheName: 'api-cache',
plugins: [
new CacheableResponsePlugin({
statuses: [0, 200],
}),
new ExpirationPlugin({
maxEntries: 50,
maxAgeSeconds: 5 * 60, // 5 minutes
}),
],
})
);

// Cache fonts with CacheFirst strategy
registerRoute(
({ request }) => request.destination === 'font',
new CacheFirst({
cacheName: 'fonts',
plugins: [
new CacheableResponsePlugin({
statuses: [0, 200],
}),
new ExpirationPlugin({
maxEntries: 30,
maxAgeSeconds: 365 * 24 * 60 * 60, // 1 year
}),
],
})
);

// This allows the web app to trigger skipWaiting via
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();

Check warning on line 97 in src/frontend/apps/conversations/public/sw.src.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `self`.

See more on https://sonarcloud.io/project/issues?id=suitenumerique_conversations&issues=AZq6QTtCAkcgHC3iMOzJ&open=AZq6QTtCAkcgHC3iMOzJ&pullRequest=150
}
});

6 changes: 6 additions & 0 deletions src/frontend/apps/conversations/src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type { AppProps } from 'next/app';
import Head from 'next/head';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';

import { AppProvider, productName } from '@/core/';
import { useCunninghamTheme } from '@/cunningham';
import '@/i18n/initI18n';
import { NextPageWithLayout } from '@/types/next';
import { registerServiceWorker } from '@/utils/registerServiceWorker';

import './globals.css';

Expand All @@ -19,6 +21,10 @@ export default function App({ Component, pageProps }: AppPropsWithLayout) {
const { componentTokens } = useCunninghamTheme();
const favicon = componentTokens['favicon'];

useEffect(() => {
registerServiceWorker();
}, []);

return (
<>
<Head>
Expand Down
15 changes: 14 additions & 1 deletion src/frontend/apps/conversations/src/pages/_document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,20 @@ import { Head, Html, Main, NextScript } from 'next/document';
export default function RootLayout() {
return (
<Html>
<Head />
<Head>
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#1B2E5F" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="Assistant" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="msapplication-TileColor" content="#1B2E5F" />
<meta name="msapplication-tap-highlight" content="no" />
<link
rel="apple-touch-icon"
href="/assets/icon-assistant.svg"
/>
</Head>
<body suppressHydrationWarning={process.env.NODE_ENV === 'development'}>
<Main />
<NextScript />
Expand Down
67 changes: 67 additions & 0 deletions src/frontend/apps/conversations/src/utils/registerServiceWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* Service Worker Registration
* Registers the service worker for PWA functionality
*/

export function registerServiceWorker() {
if (
typeof window !== 'undefined' &&

Check warning on line 8 in src/frontend/apps/conversations/src/utils/registerServiceWorker.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis.window` over `window`.

See more on https://sonarcloud.io/project/issues?id=suitenumerique_conversations&issues=AZq6QTqlAkcgHC3iMOy-&open=AZq6QTqlAkcgHC3iMOy-&pullRequest=150
'serviceWorker' in navigator &&
process.env.NODE_ENV === 'production'
) {
window.addEventListener('load', () => {
const swUrl = '/sw.js';

navigator.serviceWorker
.register(swUrl)
.then((registration) => {
// Registration was successful
console.log(
'ServiceWorker registration successful with scope: ',
registration.scope
);

// Check for updates every hour
setInterval(() => {
registration.update();
}, 60 * 60 * 1000);

// Handle updates
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
if (newWorker) {
newWorker.addEventListener('statechange', () => {

Check failure on line 33 in src/frontend/apps/conversations/src/utils/registerServiceWorker.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this code to not nest functions more than 4 levels deep.

See more on https://sonarcloud.io/project/issues?id=suitenumerique_conversations&issues=AZq6QTqlAkcgHC3iMOy_&open=AZq6QTqlAkcgHC3iMOy_&pullRequest=150
if (
newWorker.state === 'installed' &&
navigator.serviceWorker.controller
) {
// New service worker available, prompt user to refresh
if (
window.confirm(

Check warning on line 40 in src/frontend/apps/conversations/src/utils/registerServiceWorker.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=suitenumerique_conversations&issues=AZq6QTqlAkcgHC3iMOzA&open=AZq6QTqlAkcgHC3iMOzA&pullRequest=150
'New version available! Would you like to update?'
)
) {
newWorker.postMessage({ type: 'SKIP_WAITING' });
window.location.reload();

Check warning on line 45 in src/frontend/apps/conversations/src/utils/registerServiceWorker.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=suitenumerique_conversations&issues=AZq6QTqlAkcgHC3iMOzB&open=AZq6QTqlAkcgHC3iMOzB&pullRequest=150
}
}
});
}
});
})
.catch((error) => {
console.error('ServiceWorker registration failed: ', error);
});

// Handle service worker updates
let refreshing = false;
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (!refreshing) {
refreshing = true;
window.location.reload();

Check warning on line 61 in src/frontend/apps/conversations/src/utils/registerServiceWorker.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=suitenumerique_conversations&issues=AZq6QTqlAkcgHC3iMOzC&open=AZq6QTqlAkcgHC3iMOzC&pullRequest=150
}
});
});
}
}

Loading