Skip to content

Commit

Permalink
Precache fallback lang and implement offline fallback
Browse files Browse the repository at this point in the history
  • Loading branch information
microbit-robert committed Apr 26, 2024
1 parent 710ea9e commit 904e486
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 18 deletions.
6 changes: 6 additions & 0 deletions src/common/chunk-util.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { OfflineError } from "../language-server/error-util";

/**
* (c) 2022, Micro:bit Educational Foundation and contributors
*
Expand All @@ -18,6 +20,10 @@ export const retryAsyncLoad = async <T>(
// Must await here!
return await load();
} catch (e) {
// If the user is offline, bail immediately.
if (!navigator.onLine) {
throw new OfflineError();
}
if (attempts === 4) {
throw e;
}
Expand Down
14 changes: 9 additions & 5 deletions src/common/sanity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,15 @@ export const fetchContent = async <T>(
query: (languageId: string) => string,
adaptContent: (result: any) => T | undefined
): Promise<T> => {
const preferred = adaptContent(
await fetchContentInternal(query(sanityLanguageId(languageId)))
);
if (preferred) {
return preferred;
try {
const preferred = adaptContent(
await fetchContentInternal(query(sanityLanguageId(languageId)))
);
if (preferred) {
return preferred;
}
} catch (err) {
// Fall through to fallback without crashing if user is offline.
}
const fallback = adaptContent(await fetchContentInternal(query("en")));
if (!fallback) {
Expand Down
29 changes: 24 additions & 5 deletions src/language-server/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import {
} from "vscode-languageserver-protocol";
import { retryAsyncLoad } from "../common/chunk-util";
import { microPythonConfig } from "../micropython/micropython";
import { isErrorDueToDispose } from "./error-util";
import { isErrorDueToDispose, OfflineError } from "./error-util";
import { fallbackLocale } from "../settings/settings";

/**
* Create a URI for a source document under the default root of file:///src/.
Expand Down Expand Up @@ -178,7 +179,14 @@ export class LanguageServerClient extends EventEmitter {
// This mostly happens due to React 18 strict mode but could happen due to language changes.
return false;
}
throw e;
if (!navigator.onLine) {
// Fallback to the precached locale if user is offline.
this.locale = fallbackLocale;
this.initializePromise = undefined;
this.initialize();
} else {
throw e;
}
}
return true;
})();
Expand All @@ -187,9 +195,20 @@ export class LanguageServerClient extends EventEmitter {

private async getInitializationOptions(): Promise<any> {
const branch = microPythonConfig.stubs;
const typeshed = await retryAsyncLoad(() => {
return import(`../micropython/${branch}/typeshed.${this.locale}.json`);
});
let typeshed;
try {
typeshed = await retryAsyncLoad(() => {
return import(`../micropython/${branch}/typeshed.${this.locale}.json`);
});
} catch (err) {
if (err instanceof OfflineError) {
typeshed = await import(
`../micropython/${branch}/typeshed.${fallbackLocale}.json`
);
} else {
throw err;
}
}
return {
// Shallow copy as it's an ESM that can't be serialized
files: { files: typeshed.files },
Expand Down
2 changes: 2 additions & 0 deletions src/language-server/error-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ export const isErrorDueToDispose = (e: unknown): boolean =>
(e instanceof ResponseError &&
e.code === ErrorCodes.PendingResponseRejected) ||
e instanceof ConnectionError;

export class OfflineError extends Error {}
13 changes: 11 additions & 2 deletions src/messages/TranslationProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
*
* SPDX-License-Identifier: MIT
*/
import { useSettings } from "../settings/settings";
import { fallbackLocale, useSettings } from "../settings/settings";
import { IntlProvider, MessageFormatElement } from "react-intl";
import { ReactNode, useEffect, useState } from "react";
import { retryAsyncLoad } from "../common/chunk-util";
import { OfflineError } from "../language-server/error-util";

async function loadLocaleData(locale: string) {
switch (locale) {
Expand Down Expand Up @@ -50,7 +51,15 @@ const TranslationProvider = ({ children }: TranslationProviderProps) => {
const [messages, setMessages] = useState<Messages | undefined>();
useEffect(() => {
const load = async () => {
setMessages(await retryAsyncLoad(() => loadLocaleData(languageId)));
try {
setMessages(await retryAsyncLoad(() => loadLocaleData(languageId)));
} catch (err) {
if (err instanceof OfflineError) {
setMessages(await loadLocaleData(fallbackLocale));
} else {
throw err;
}
}
};
load();
}, [languageId]);
Expand Down
4 changes: 4 additions & 0 deletions src/settings/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export interface Language {
preview?: boolean;
}

// We precache files for this locale, so it is a safe fallback
// if a user changes language while offline.
export const fallbackLocale = "en";

// When we add languages we need to update the toolkit search indexing,
// which will require the dynamic import of a new language plugin for lunr.
// See search.ts.
Expand Down
14 changes: 8 additions & 6 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@ export default defineConfig(({ mode }) => {
VitePWA({
registerType: "autoUpdate",
workbox: {
// Ignore all language related assets and cache these at runtime instead.
// Only precache language assets for the fallback language.
// Cache other languages at runtime.
// Cache all pyright-locale files as we can't tell what language they are.
globIgnores: [
"**/{pyright-locale*.js,typeshed*.js,search.worker*.js,ui*.js}",
"**/{typeshed.!(en*).js,search.worker.!(en*).js,ui.!(en*).js}",
],
maximumFileSizeToCacheInBytes: 3097152,
globPatterns: ["**/*.{js,css,html,ico,png,svg,gif,hex}"],
Expand All @@ -83,7 +85,7 @@ export default defineConfig(({ mode }) => {
urlPattern: new RegExp(
`^https://cdn.sanity.io/images/${process.env.VITE_SANITY_PROJECT}/${process.env.VITE_SANITY_DATASET}/.*`
),
handler: "NetworkFirst",
handler: "CacheFirst",
options: {
cacheName: "sanity-images-cache",
expiration: {
Expand All @@ -96,7 +98,7 @@ export default defineConfig(({ mode }) => {
},
{
urlPattern: /^https:\/\/fonts.microbit.org\/.*/,
handler: "NetworkFirst",
handler: "CacheFirst",
options: {
cacheName: "fonts-cache",
expiration: {
Expand All @@ -109,8 +111,8 @@ export default defineConfig(({ mode }) => {
},
{
urlPattern:
/.*(?:pyright-locale|search\.worker|typeshed|ui\.).*.js/,
handler: "NetworkFirst",
/.*(?:pyright-locale|search\.worker|typeshed|ui\.).*\.js/,
handler: "CacheFirst",
options: {
cacheName: "lang-cache",
expiration: {
Expand Down

0 comments on commit 904e486

Please sign in to comment.