Skip to content

Commit

Permalink
Merge pull request #11 from Automattic/sync-worker-kickoff
Browse files Browse the repository at this point in the history
Sync worker kickoff
  • Loading branch information
psrpinto authored Mar 20, 2023
2 parents 8c74e54 + 2b64cd5 commit cd6c759
Show file tree
Hide file tree
Showing 12 changed files with 224 additions and 10 deletions.
15 changes: 15 additions & 0 deletions scripts/build-plugins/service-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,21 @@ const NON_PRECACHED_JS = [

function isPreCached(asset) {
const {name, fileName} = asset;

// For sync-worker.js and sync-worker.js.map, `name` isn't set, so we must rely on `fileName` only.
if (!name && fileName.includes("sync-worker")) {
// Don't precache sourcemap.
if (fileName.endsWith(".js.map")) {
return false;
}

// sync-worker.js is only used when the SameSessionInMultipleTabs feature is enabled, so we don't precache it.
// TODO: Once the SameSessionInMultipleTabs feature is removed, we should probably precache sync-worker.js by returning true below.
if (fileName.endsWith(".js")) {
return false;
}
}

return name.endsWith(".svg") ||
name.endsWith(".png") ||
name.endsWith(".css") ||
Expand Down
3 changes: 3 additions & 0 deletions scripts/sdk/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ shopt -s extglob
rm -rf target/*
yarn run vite build -c vite.sdk-assets-config.js
yarn run vite build -c vite.sdk-lib-config.js
# Remove sync-worker.js from SDK build.
# TODO: Once SameSessionInMultipleTabs feature flag is globally enabled, remove the following line.
rm -rf target/lib-build/assets
yarn tsc -p tsconfig-declaration.json
./scripts/sdk/create-manifest.js ./target/package.json
mkdir target/paths
Expand Down
9 changes: 7 additions & 2 deletions src/domain/session/settings/FeaturesViewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,17 @@ export class FeaturesViewModel extends ViewModel {
new FeatureViewModel(this.childOptions({
name: this.i18n`Audio/video calls`,
description: this.i18n`Allows starting and participating in A/V calls compatible with Element Call (MSC3401). Look for the start call option in the room menu ((...) in the right corner) to start a call.`,
feature: FeatureFlag.Calls
feature: FeatureFlag.Calls,
})),
new FeatureViewModel(this.childOptions({
name: this.i18n`Cross-Signing`,
description: this.i18n`Allows verifying the identity of people you chat with. This feature is still evolving constantly, expect things to break.`,
feature: FeatureFlag.CrossSigning
feature: FeatureFlag.CrossSigning,
})),
new FeatureViewModel(this.childOptions({
name: this.i18n`Open the same session in multiple tabs`,
description: this.i18n`Allows having the same session open in multiple browser tabs or windows. This feature is currently not functional and is intended only for usage by Hydrogen developers. Do not enable.`,
feature: FeatureFlag.SameSessionInMultipleTabs,
})),
];
}
Expand Down
7 changes: 6 additions & 1 deletion src/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import type {SettingsStorage} from "./platform/web/dom/SettingsStorage";

export enum FeatureFlag {
Calls = 1 << 0,
CrossSigning = 1 << 1
CrossSigning = 1 << 1,
SameSessionInMultipleTabs = 1 << 2,
}

export class FeatureSet {
Expand All @@ -44,6 +45,10 @@ export class FeatureSet {
return this.isFeatureEnabled(FeatureFlag.CrossSigning);
}

get sameSessionInMultipleTabs(): boolean {
return this.isFeatureEnabled(FeatureFlag.SameSessionInMultipleTabs);
}

static async load(settingsStorage: SettingsStorage): Promise<FeatureSet> {
const flags = await settingsStorage.getInt("enabled_features") || 0;
return new FeatureSet(flags);
Expand Down
10 changes: 7 additions & 3 deletions src/matrix/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,14 +291,18 @@ export class Client {
await log.wrap("createIdentity", log => this._session.createIdentity(log));
}

this._sync = new Sync({hsApi: this._requestScheduler.hsApi, storage: this._storage, session: this._session, logger: this._platform.logger});
this._sync = this._platform.syncFactory.make({
scheduler: this._requestScheduler,
storage: this._storage,
session: this._session,
});
// notify sync and session when back online
this._reconnectSubscription = this._reconnector.connectionStatus.subscribe(state => {
if (state === ConnectionStatus.Online) {
this._platform.logger.runDetached("reconnect", async log => {
// needs to happen before sync and session or it would abort all requests
this._requestScheduler.start();
this._sync.start();
await this._sync.start();
this._sessionStartedByReconnector = true;
const d = dehydratedDevice;
dehydratedDevice = undefined;
Expand Down Expand Up @@ -329,7 +333,7 @@ export class Client {
}

async _waitForFirstSync() {
this._sync.start();
await this._sync.start();
this._status.set(LoadStatus.FirstSync);
// only transition into Ready once the first sync has succeeded
this._waitForFirstSyncHandle = this._sync.status.waitFor(s => {
Expand Down
25 changes: 25 additions & 0 deletions src/matrix/ISync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import {ObservableValue} from "../observable/value";
import {SyncStatus} from "./Sync";

export interface ISync {
get status(): ObservableValue<SyncStatus>;
get error(): Error | null;
start(): Promise<void>;
stop(): void;
}
5 changes: 3 additions & 2 deletions src/matrix/Sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ function timelineIsEmpty(roomResponse) {
* await room.afterSyncCompleted(changes);
* ```
*/
// TODO: When this file gets converted to Typescript, Sync must implement ISync.
export class Sync {
constructor({hsApi, session, storage, logger}) {
this._hsApi = hsApi;
Expand All @@ -71,7 +72,7 @@ export class Sync {
return this._error;
}

start() {
async start() {
// not already syncing?
if (this._status.get() !== SyncStatus.Stopped) {
return;
Expand All @@ -83,7 +84,7 @@ export class Sync {
} else {
this._status.set(SyncStatus.InitialSync);
}
this._syncLoop(syncToken);
void this._syncLoop(syncToken);
}

async _syncLoop(syncToken) {
Expand Down
4 changes: 4 additions & 0 deletions src/platform/web/Platform.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import {MediaDevicesWrapper} from "./dom/MediaDevices";
import {DOMWebRTC} from "./dom/WebRTC";
import {ThemeLoader} from "./theming/ThemeLoader";
import {TimeFormatter} from "./dom/TimeFormatter";
import {FeatureSet} from "../../features";
import {SyncFactory} from "./sync/SyncFactory";

function addScript(src) {
return new Promise(function (resolve, reject) {
Expand Down Expand Up @@ -201,6 +203,8 @@ export class Platform {
log.log({ l: "Active theme", name: themeName, variant: themeVariant });
await this._themeLoader.setTheme(themeName, themeVariant, log);
}
this.features = await FeatureSet.load(this.settingsStorage);
this.syncFactory = new SyncFactory({logger: this.logger, features: this.features});
});
} catch (err) {
this._container.innerText = err.message;
Expand Down
3 changes: 1 addition & 2 deletions src/platform/web/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export async function main(platform) {
// const request = recorder.request;
// window.getBrawlFetchLog = () => recorder.log();
await platform.init();
const features = await FeatureSet.load(platform.settingsStorage);
const navigation = createNavigation();
platform.setNavigation(navigation);
const urlRouter = createRouter({navigation, history: platform.history});
Expand All @@ -46,7 +45,7 @@ export async function main(platform) {
// so we call it that in the view models
urlRouter: urlRouter,
navigation,
features
features: platform.features,
});
await vm.load();
platform.createAndMountRootView(vm);
Expand Down
66 changes: 66 additions & 0 deletions src/platform/web/sync/SyncFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import {ISync} from "../../../matrix/ISync";
import {Sync} from "../../../matrix/Sync";
import {RequestScheduler} from "../../../matrix/net/RequestScheduler";
import {Session} from "../../../matrix/Session";
import {Logger} from "../../../logging/Logger";
import {Storage} from "../../../matrix/storage/idb/Storage";
import {FeatureSet} from "../../../features";
import {SyncProxy} from "./SyncProxy";

type Options = {
logger: Logger;
features: FeatureSet;
}

type MakeOptions = {
scheduler: RequestScheduler;
storage: Storage;
session: Session;
}

export class SyncFactory {
private readonly _logger: Logger;
private readonly _features: FeatureSet;

constructor(options: Options) {
const {logger, features} = options;
this._logger = logger;
this._features = features;
}

make(options: MakeOptions): ISync {
const {scheduler, storage, session} = options;
let runSyncInWorker = this._features.sameSessionInMultipleTabs;

if (typeof SharedWorker === "undefined") {
runSyncInWorker = false;
}

if (runSyncInWorker) {
return new SyncProxy({session});
}

return new Sync({
logger: this._logger,
hsApi: scheduler.hsApi,
storage,
session,
});
}
}
58 changes: 58 additions & 0 deletions src/platform/web/sync/SyncProxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import {ISync} from "../../../matrix/ISync";
import {ObservableValue} from "../../../observable/value";
import {SyncStatus} from "../../../matrix/Sync";
import {Session} from "../../../matrix/Session";

type Options = {
session: Session;
}

export class SyncProxy implements ISync {
private _session: Session;
private readonly _status: ObservableValue<SyncStatus> = new ObservableValue(SyncStatus.Stopped);
private _error: Error | null = null;
private _worker?: SharedWorker;

constructor(options: Options) {
const {session} = options;
this._session = session;
}

get status(): ObservableValue<SyncStatus> {
return this._status;
}

get error(): Error | null {
return this._error;
}

async start(): Promise<void> {
this._worker = new SharedWorker(new URL("./sync-worker", import.meta.url), {
type: "module",
});
this._worker.port.onmessage = (event: MessageEvent) => {
// TODO
console.log(event);
};
}

stop(): void {
// TODO
}
}
29 changes: 29 additions & 0 deletions src/platform/web/sync/sync-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// TODO: Figure out how to get WebWorkers Typescript lib working. For now we just disable checks on the whole file.
// @ts-nocheck

// The empty export makes this a module. It can be removed once there's at least one import.
export {}

declare let self: SharedWorkerGlobalScope;

self.onconnect = (event: MessageEvent) => {
const port = event.ports[0];
port.postMessage("hello from sync worker");
console.log("hello from sync worker");
}

0 comments on commit cd6c759

Please sign in to comment.