diff --git a/doc/SDK.md b/doc/SDK.md index 8ce0b304..54e37cca 100644 --- a/doc/SDK.md +++ b/doc/SDK.md @@ -47,7 +47,8 @@ const assetPaths = { wasmBundle: olmJsPath } }; -import "hydrogen-view-sdk/style.css"; +import "hydrogen-view-sdk/theme-element-light.css"; +// OR import "hydrogen-view-sdk/theme-element-dark.css"; async function main() { const app = document.querySelector('#app')! diff --git a/scripts/postcss/css-url-processor.js b/scripts/postcss/css-url-processor.js index 3ae7c60d..f58818f1 100644 --- a/scripts/postcss/css-url-processor.js +++ b/scripts/postcss/css-url-processor.js @@ -39,7 +39,7 @@ function colorsFromURL(url, colorMap) { function processURL(decl, replacer, colorMap) { const value = decl.value; const parsed = valueParser(value); - parsed.walk(async node => { + parsed.walk(node => { if (node.type !== "function" || node.value !== "url") { return; } diff --git a/scripts/postcss/svg-colorizer.js b/scripts/postcss/svg-colorizer.js index 95355ea8..06b7b14b 100644 --- a/scripts/postcss/svg-colorizer.js +++ b/scripts/postcss/svg-colorizer.js @@ -37,7 +37,7 @@ module.exports.buildColorizedSVG = function (svgLocation, primaryColor, secondar if (svgCode === coloredSVGCode) { throw new Error("svg-colorizer made no color replacements! The input svg should only contain colors #ff00ff (primary, case-sensitive) and #00ffff (secondary, case-sensitive)."); } - const fileName = svgLocation.match(/.+\/(.+\.svg)/)[1]; + const fileName = svgLocation.match(/.+[/\\](.+\.svg)/)[1]; const outputName = `${fileName.substring(0, fileName.length - 4)}-${createHash(coloredSVGCode)}.svg`; const outputPath = path.resolve(__dirname, "../../.tmp"); try { diff --git a/scripts/sdk/build.sh b/scripts/sdk/build.sh index 2ac4be3a..ae3a794e 100755 --- a/scripts/sdk/build.sh +++ b/scripts/sdk/build.sh @@ -1,4 +1,8 @@ #!/bin/bash +# Exit whenever one of the commands fail with a non-zero exit code +set -e +set -o pipefail + rm -rf target yarn run vite build -c vite.sdk-assets-config.js yarn run vite build -c vite.sdk-lib-config.js diff --git a/src/platform/web/LegacyPlatform.js b/src/platform/web/LegacyPlatform.js index 85632bf2..b8a6d7e7 100644 --- a/src/platform/web/LegacyPlatform.js +++ b/src/platform/web/LegacyPlatform.js @@ -19,6 +19,6 @@ import {hkdf} from "../../utils/crypto/hkdf"; import {Platform as ModernPlatform} from "./Platform.js"; -export function Platform(container, assetPaths, config, options = null) { - return new ModernPlatform(container, assetPaths, config, options, {aesjs, hkdf}); +export function Platform({ container, assetPaths, config, configURL, options = null }) { + return new ModernPlatform({ container, assetPaths, config, configURL, options, cryptoExtras: { aesjs, hkdf }}); } diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 7426b138..9f2bd615 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -128,10 +128,11 @@ function adaptUIOnVisualViewportResize(container) { } export class Platform { - constructor(container, assetPaths, config, options = null, cryptoExtras = null) { + constructor({ container, assetPaths, config, configURL, options = null, cryptoExtras = null }) { this._container = container; this._assetPaths = assetPaths; this._config = config; + this._configURL = configURL; this.settingsStorage = new SettingsStorage("hydrogen_setting_v1_"); this.clock = new Clock(); this.encoding = new Encoding(); @@ -144,7 +145,7 @@ export class Platform { this._serviceWorkerHandler = new ServiceWorkerHandler(); this._serviceWorkerHandler.registerAndStart(assetPaths.serviceWorker); } - this.notificationService = new NotificationService(this._serviceWorkerHandler, config.push); + this.notificationService = undefined; // Only try to use crypto when olm is provided if(this._assetPaths.olm) { this.crypto = new Crypto(cryptoExtras); @@ -169,6 +170,20 @@ export class Platform { this.webRTC = new DOMWebRTC(); } + async init() { + if (!this._config) { + if (!this._configURL) { + throw new Error("Neither config nor configURL was provided!"); + } + const {body}= await this.request(this._configURL, {method: "GET", format: "json", cache: true}).response(); + this._config = body; + } + this._notificationService = new NotificationService( + this._serviceWorkerHandler, + this._config.push + ); + } + _createLogger(isDevelopment) { // Make sure that loginToken does not end up in the logs const transformer = (item) => { diff --git a/src/platform/web/index.html b/src/platform/web/index.html index 064c61a1..f73e65ed 100644 --- a/src/platform/web/index.html +++ b/src/platform/web/index.html @@ -17,17 +17,17 @@ diff --git a/src/platform/web/main.js b/src/platform/web/main.js index 1729c17c..edc2cf14 100644 --- a/src/platform/web/main.js +++ b/src/platform/web/main.js @@ -32,6 +32,7 @@ export async function main(platform) { // const recorder = new RecordRequester(createFetchRequest(clock.createTimeout)); // const request = recorder.request; // window.getBrawlFetchLog = () => recorder.log(); + await platform.init(); const navigation = createNavigation(); platform.setNavigation(navigation); const urlRouter = createRouter({navigation, history: platform.history}); diff --git a/src/platform/web/sw.js b/src/platform/web/sw.js index c5f69438..a9a92979 100644 --- a/src/platform/web/sw.js +++ b/src/platform/web/sw.js @@ -92,8 +92,12 @@ function isCacheableThumbnail(url) { const baseURL = new URL(self.registration.scope); let pendingFetchAbortController = new AbortController(); + async function handleRequest(request) { try { + if (request.url.includes("config.json")) { + return handleConfigRequest(request); + } const url = new URL(request.url); // rewrite / to /index.html so it hits the cache if (url.origin === baseURL.origin && url.pathname === baseURL.pathname) { @@ -119,6 +123,27 @@ async function handleRequest(request) { } } +async function handleConfigRequest(request) { + let response = await readCache(request); + const networkResponsePromise = fetchAndUpdateConfig(request); + if (response) { + return response; + } else { + return await networkResponsePromise; + } +} + +async function fetchAndUpdateConfig(request) { + const response = await fetch(request, { + signal: pendingFetchAbortController.signal, + headers: { + "Cache-Control": "no-cache", + }, + }); + updateCache(request, response.clone()); + return response; +} + async function updateCache(request, response) { // don't write error responses to the cache if (response.status >= 400) { diff --git a/src/platform/web/ui/general/TemplateView.ts b/src/platform/web/ui/general/TemplateView.ts index b50fdf15..ef6320e7 100644 --- a/src/platform/web/ui/general/TemplateView.ts +++ b/src/platform/web/ui/general/TemplateView.ts @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { setAttribute, text, isChildren, classNames, TAG_NAMES, HTML_NS, ClassNames, Child} from "./html"; +import { setAttribute, text, isChildren, classNames, TAG_NAMES, HTML_NS, ClassNames, Child as NonBoundChild} from "./html"; import {mountView} from "./utils"; import {BaseUpdateView, IObservableValue} from "./BaseUpdateView"; import {IMountArgs, ViewNode, IView} from "./types"; @@ -30,12 +30,15 @@ function objHasFns(obj: ClassNames): obj is { [className: string]: bool } export type RenderFn = (t: Builder, vm: T) => ViewNode; +type TextBinding = (T) => string | number | boolean | undefined | null; +type Child = NonBoundChild | TextBinding; +type Children = Child | Child[]; type EventHandler = ((event: Event) => void); type AttributeStaticValue = string | boolean; type AttributeBinding = (value: T) => AttributeStaticValue; export type AttrValue = AttributeStaticValue | AttributeBinding | EventHandler | ClassNames; export type Attributes = { [attribute: string]: AttrValue }; -type ElementFn = (attributes?: Attributes | Child | Child[], children?: Child | Child[]) => Element; +type ElementFn = (attributes?: Attributes | Children, children?: Children) => Element; export type Builder = TemplateBuilder & { [tagName in typeof TAG_NAMES[string][number]]: ElementFn }; /** @@ -195,15 +198,15 @@ export class TemplateBuilder { this._addAttributeBinding(node, "className", value => classNames(obj, value)); } - _addTextBinding(fn: (value: T) => string): Text { - const initialValue = fn(this._value); + _addTextBinding(fn: (value: T) => ReturnType>): Text { + const initialValue = fn(this._value)+""; const node = text(initialValue); let prevValue = initialValue; const binding = () => { - const newValue = fn(this._value); + const newValue = fn(this._value)+""; if (prevValue !== newValue) { prevValue = newValue; - node.textContent = newValue+""; + node.textContent = newValue; } }; @@ -242,7 +245,7 @@ export class TemplateBuilder { } } - _setNodeChildren(node: Element, children: Child | Child[]): void{ + _setNodeChildren(node: Element, children: Children): void{ if (!Array.isArray(children)) { children = [children]; } @@ -276,14 +279,16 @@ export class TemplateBuilder { return node; } - el(name: string, attributes?: Attributes | Child | Child[], children?: Child | Child[]): ViewNode { + el(name: string, attributes?: Attributes | Children, children?: Children): ViewNode { return this.elNS(HTML_NS, name, attributes, children); } - elNS(ns: string, name: string, attributes?: Attributes | Child | Child[], children?: Child | Child[]): ViewNode { + elNS(ns: string, name: string, attributesOrChildren?: Attributes | Children, children?: Children): ViewNode { + let attributes: Attributes | undefined; if (attributes !== undefined && isChildren(attributes)) { children = attributes; - attributes = undefined; + } else { + attributes = attributesOrChildren as Attributes; } const node = document.createElementNS(ns, name); diff --git a/vite.common-config.js b/vite.common-config.js index f5a90154..8a82a9da 100644 --- a/vite.common-config.js +++ b/vite.common-config.js @@ -31,6 +31,7 @@ const commonOptions = { assetsInlineLimit: 0, polyfillModulePreload: false, }, + assetsInclude: ['**/config.json'], define: { DEFINE_VERSION: JSON.stringify(version), DEFINE_GLOBAL_HASH: JSON.stringify(null), diff --git a/vite.config.js b/vite.config.js index 4dd35af2..87e3d063 100644 --- a/vite.config.js +++ b/vite.config.js @@ -14,6 +14,11 @@ export default defineConfig(({mode}) => { outDir: "../../../target", minify: true, sourcemap: true, + rollupOptions: { + output: { + assetFileNames: (asset) => asset.name.includes("config.json") ? "assets/[name][extname]": "assets/[name].[hash][extname]", + }, + }, }, plugins: [ themeBuilder({