Offline App with @PWA

I have code like below -

But When I go offline, I just see offline.html page. I expect my app to load and function normally. I also have middleware installed which makes copy of all requests and stores offline and returns from cache if needed.

Can someone please point me to what I am missing?

@Modulith
@EnableAsync
@SpringBootApplication
@ConfigurationPropertiesScan
@Theme(value = "myTheme", variant = "light")
@PWA(name = "My Application",
        shortName = "my-app",
        offlinePath = "offline.html",
        themeColor = "#256e22",
        offlineResources = {"./images/offline.png"})
public class Application implements AppShellConfigurator {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}
// Uses the Vaadin provided login an logout helper methods
import {
    login as loginImpl,
    type LoginOptions,
    type LoginResult,
    logout as logoutImpl,
    type LogoutOptions,
} from '@vaadin/hilla-frontend';
import { UsersController } from 'Frontend/generated/endpoints';
import Users from 'Frontend/generated/com/myapp/application/security/entity/Users';
import { configureAuth } from '@vaadin/hilla-react-auth';

interface Authentication {
    user: Users | undefined;
    timestamp: number;
}

let authentication: Authentication | undefined;

const AUTHENTICATION_KEY = 'authentication';
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
/**
 * Forces the session to expire and removes user information stored in
 * `localStorage`.
 */
export function setSessionExpired() {
    authentication = undefined;

    // Delete the authentication from the local storage
    localStorage.removeItem(AUTHENTICATION_KEY);
}

// Get authentication from local storage
const storedAuthenticationJson = localStorage.getItem(AUTHENTICATION_KEY);
if (storedAuthenticationJson !== null) {
    const storedAuthentication = JSON.parse(storedAuthenticationJson) as Authentication;
    // Check that the stored timestamp is not older than 30 days
    const hasRecentAuthenticationTimestamp = new Date().getTime() - storedAuthentication.timestamp < THIRTY_DAYS_MS;
    if (hasRecentAuthenticationTimestamp) {
        // Use loaded authentication
        authentication = storedAuthentication;
    } else {
        // Delete expired stored authentication
        setSessionExpired();
    }
}

/**
 * Login wrapper method that retrieves user information.
 *
 * Uses `localStorage` for offline support.
 */
export async function login(username: string, password: string, options: LoginOptions = {}): Promise<LoginResult> {
    return await loginImpl(username, password, {
        ...options,
        async onSuccess() {
            // Get user info from endpoint
            const user = await UsersController.getAuthenticatedUser();
            authentication = {
                user,
                timestamp: new Date().getTime(),
            };

            // Save the authentication to local storage
            localStorage.setItem(AUTHENTICATION_KEY, JSON.stringify(authentication));
        },
    });
}

/**
 * Login wrapper method that retrieves user information.
 *
 * Uses `localStorage` for offline support.
 */
export async function logout(options: LogoutOptions = {}) {
    return await logoutImpl({
        ...options,
        onSuccess() {
            setSessionExpired();
        },
    });
}

/**
 * Checks if the user is logged in.
 */
export function isLoggedIn() {
    return !!authentication;
}

/**
 * Gets the current user.
 */
export function getUser() {
    return authentication?.user;
}
/**
 * Checks if the user has the role.
 */
export function isUserInRole(role: string) {
    if (!authentication) {
        return false;
    }

    return authentication?.user?.authorities?.map((value) => value?.authority).includes(`ROLE_${role}`);
}

const auth = configureAuth(async () => {
    return getUser();
});

export const AuthProvider = auth.AuthProvider;

Create a custom hook for using local storage and detecting when application is offline.

Here is one that can be used copy-paste to your project

If you think it works well and should be part off official Hilla, then just give thumbs up here

3 Likes

Thank you for your input.

I have issue when i am offline, and i refresh the page, or in PWA i close the app and reopen it, it’s not loading index.html but instead going to offline.html,
so no react app initialized here. I am only seeing our standard you are offline page.

I guess when i am offline service worker should return cached copy of index.html and index.js.
Please correct me if i am wrong.

I figured out my issue. It is due to adding custom offline page in the @PWA. Once I removed that, it is working as expected.

2 Likes