Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
11 changes: 4 additions & 7 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@
"homeserverList": ["thirdroom.io", "matrix.org"],
"onboardingVersion": 1,
"repositoryRoomIdOrAlias": "#repository-room:thirdroom.io",
"oidc": {
"clientConfigs": {
"https://id.thirdroom.io/realms/thirdroom/": {
"client_id": "thirdroom",
"uris": ["http://localhost:3000", "https://thirdroom.io"],
"guestKeycloakIdpHint": "guest"
}
"staticOidcClients": {
"https://id.thirdroom.io/realms/thirdroom/": {
"client_id": "thirdroom",
"guestKeycloakIdpHint": "guest"
}
}
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@gltf-transform/core": "^2.4.3",
"@gltf-transform/extensions": "^2.4.3",
"@gltf-transform/functions": "^2.4.3",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz",
"@monaco-editor/react": "^4.4.6",
"@radix-ui/react-checkbox": "^1.0.1",
"@radix-ui/react-dialog": "^0.1.7",
Expand All @@ -43,7 +44,7 @@
"@sentry/react": "^7.13.0",
"@sentry/tracing": "^7.13.0",
"@thi.ng/malloc": "^6.1.19",
"@thirdroom/hydrogen-view-sdk": "0.0.29",
"@thirdroom/hydrogen-view-sdk": "0.1.2",
"@thirdroom/manifold-editor-components": "^0.0.5",
"@thirdroom/ringbuffer": "^0.0.1",
"@webxr-input-profiles/assets": "^1.0.13",
Expand Down
123 changes: 45 additions & 78 deletions src/engine/network/createMatrixNetworkInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,23 @@ function memberComparator(a: Member, b: Member): number {

function isOlderThanLocalHost(groupCall: GroupCall, member: Member): boolean {
if (groupCall.eventTimestamp === member.eventTimestamp) {
return groupCall.deviceIndex! < member.deviceIndex;
return groupCall.deviceIndex! <= member.deviceIndex;
}

return groupCall.eventTimestamp! < member.eventTimestamp;
}

function getReliableHost(groupCall: GroupCall): Member | undefined {
const sortedMembers = Array.from(new Map(groupCall.members).values())
.sort(memberComparator)
.filter((member) => member.isConnected && member.dataChannel);

if (sortedMembers.length === 0) return undefined;
if (isOlderThanLocalHost(groupCall, sortedMembers[0])) return undefined;

return sortedMembers[0];
}

export async function createMatrixNetworkInterface(
ctx: IMainThreadContext,
client: Client,
Expand Down Expand Up @@ -52,91 +63,56 @@ export async function createMatrixNetworkInterface(
// Wait for that member to be connected and return their user id
// If the member hasn't connected in 10 seconds, return the longest connected user id

let hostId: string | undefined;

return new Promise((resolve) => {
let timeout: number | undefined = undefined;

const reliableHost = getReliableHost(groupCall);
if (reliableHost) {
resolve(reliableHost.userId);
return;
}
if (groupCall.members.size === 0) {
resolve(userId);
return;
}

const unsubscribe = groupCall.members.subscribe({
onAdd(_key, member) {
// The host connected, resolve with their id
// NOTE: Do we also need to check for older events here? Maybe there's an older member and we
// haven't received their event yet?
if (hostId && member.userId === hostId && member.isConnected && member.dataChannel) {
onAdd() {
const host = getReliableHost(groupCall);
if (host) {
clearTimeout(timeout);
unsubscribe();
resolve(hostId);
resolve(host.userId);
}
},
onRemove(_key, member) {
if (hostId && member.userId === hostId) {
// The current host disconnected, pick the next best host
const sortedMembers = Array.from(new Map(groupCall.members).values()).sort(memberComparator);

// If there are no other members, you're the host
if (sortedMembers.length === 0 || !isOlderThanLocalHost(groupCall, sortedMembers[0])) {
clearTimeout(timeout);
unsubscribe();
resolve(userId);
} else {
const nextHost = sortedMembers[0];

// If the next best host is connected then resolve with their id
if (nextHost.isConnected && member.dataChannel) {
clearTimeout(timeout);
unsubscribe();
resolve(nextHost.userId);
} else {
hostId = nextHost.userId;
}
}
onRemove() {
const host = getReliableHost(groupCall);
if (host) {
clearTimeout(timeout);
unsubscribe();
resolve(host.userId);
}
},
onReset() {
throw new Error("Unexpected reset of groupCall.members");
},
onUpdate(_key, member) {
// The host connected, resolve with their id
if (hostId && member.userId === hostId && member.isConnected && member.dataChannel) {
onUpdate() {
const host = getReliableHost(groupCall);
if (host) {
clearTimeout(timeout);
unsubscribe();
resolve(hostId);
resolve(host.userId);
}
},
});

// wait if any member to become reliable.
// resolve otherwise
timeout = window.setTimeout(() => {
// The host hasn't connected yet after 10 seconds. Use the oldest connected host instead.
unsubscribe();

const sortedConnectedMembers = Array.from(new Map(groupCall.members).values())
.sort(memberComparator)
.filter((member) => member.isConnected && member.dataChannel);

if (sortedConnectedMembers.length > 0 && isOlderThanLocalHost(groupCall, sortedConnectedMembers[0])) {
resolve(sortedConnectedMembers[0].userId);
} else {
resolve(userId);
}
const host = getReliableHost(groupCall);
resolve(host?.userId ?? userId);
}, 10000);

const initialSortedMembers = Array.from(new Map(groupCall.members).values()).sort(memberComparator);

if (initialSortedMembers.length === 0 || !isOlderThanLocalHost(groupCall, initialSortedMembers[0])) {
clearTimeout(timeout);
unsubscribe();
resolve(userId);
} else {
const hostMember = initialSortedMembers[0];

if (hostMember.isConnected && hostMember.dataChannel) {
clearTimeout(timeout);
unsubscribe();
resolve(hostMember.userId);
} else {
hostId = hostMember.userId;
}
}
});
}

Expand Down Expand Up @@ -176,22 +152,13 @@ export async function createMatrixNetworkInterface(
// Of the connected members find the one whose member event is oldest
// If the member has multiple devices get the device with the lowest device index

const sortedConnectedMembers = Array.from(new Map(groupCall.members).values())
.sort(memberComparator)
.filter((member) => member.isConnected && member.dataChannel);
const reliableHost = getReliableHost(groupCall);

if (sortedConnectedMembers.length === 0 || isOlderThanLocalHost(groupCall, sortedConnectedMembers[0])) {
setHost(ctx, userId);
} else {
if (reliableHost) {
// TODO: use powerlevels to determine host
// find youngest member for now
const hostMember = sortedConnectedMembers.sort((a, b) => {
if (a.eventTimestamp === b.eventTimestamp) {
return a.deviceIndex! > b.deviceIndex ? 1 : -1;
}
return a.eventTimestamp! > b.eventTimestamp ? 1 : -1;
})[0];
setHost(ctx, hostMember.userId);
setHost(ctx, reliableHost.userId);
} else {
setHost(ctx, userId);
}
}

Expand Down
37 changes: 32 additions & 5 deletions src/hydrogen-view-sdk.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,13 @@ declare module "@thirdroom/hydrogen-view-sdk" {

config: {
defaultHomeServer: string;
staticClients: Record<
string,
{
client_id: string;
guestKeycloakIdpHint: string;
}
>;
[key: string]: any;
};
encoding: any;
Expand Down Expand Up @@ -401,15 +408,14 @@ declare module "@thirdroom/hydrogen-view-sdk" {
expires_in?: number;
};
type IssuerUri = string;
interface ClientConfig {
export interface OidcClientConfig {
client_id: string;
client_secret?: string;
uris: string[];
}
export type StaticOidcClientsConfig = Record<IssuerUri, OidcClientConfig>;
export class OidcApi {
constructor(options: {
issuer: string;
clientConfigs: Record<IssuerUri, ClientConfig>;
staticClients?: StaticOidcClientsConfig;
request: RequestFunction;
encoding: any;
crypto: any;
Expand Down Expand Up @@ -952,6 +958,22 @@ declare module "@thirdroom/hydrogen-view-sdk" {
token?: (loginToken: string) => ILoginMethod;
}

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

export class FeatureSet {
constructor(public readonly flags: number = 0);
withFeature(flag: FeatureFlag): FeatureSet;
withoutFeature(flag: FeatureFlag): FeatureSet;
isFeatureEnabled(flag: FeatureFlag): boolean;
get calls(): boolean;
get crossSigning(): boolean;
static async load(settingsStorage: SettingsStorage): Promise<FeatureSet>;
async store(settingsStorage: SettingsStorage): Promise<void>;
}

export interface ClientOptions {
deviceName?: string;
}
Expand All @@ -965,7 +987,7 @@ declare module "@thirdroom/hydrogen-view-sdk" {

loadStatus: ObservableValue<LoadStatus>;

constructor(platform: Platform, options?: ClientOptions);
constructor(platform: Platform, features = new FeatureSet(0), options?: ClientOptions);
get loginFailure(): LoginFailure;

startWithExistingSession(sessionId: string): Promise<void>;
Expand Down Expand Up @@ -1119,6 +1141,11 @@ declare module "@thirdroom/hydrogen-view-sdk" {
get sender(): string;
}

export class DateTile extends SimpleTile {
get relativeDate(): string;
get machineReadableDate(): string;
}

export class GapTile extends SimpleTile {
constructor(entry: any, options: SimpleTileOptions);
fill(): boolean;
Expand Down
18 changes: 6 additions & 12 deletions src/ui/views/HydrogenRootView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
OIDCLoginMethod,
ILoginMethod,
ISessionInfo,
FeatureSet,
FeatureFlag,
} from "@thirdroom/hydrogen-view-sdk";
import downloadSandboxPath from "@thirdroom/hydrogen-view-sdk/download-sandbox.html?url";
import workerPath from "@thirdroom/hydrogen-view-sdk/main.js?url";
Expand Down Expand Up @@ -132,19 +134,10 @@ function initHydrogen() {
};

const oidcClientId = document.location.hostname === "thirdroom.io" ? "thirdroom" : "thirdroom_dev";
const oidcUris = ((): string[] => {
if (document.location.hostname === "thirdroom.io") {
return ["https://thirdroom.io"];
}

const { protocol, hostname, port } = document.location;
return [`${protocol}//${hostname}${port ? `:${port}` : ""}`];
})();

const config = { ...configData };
config.oidc.clientConfigs["https://id.thirdroom.io/realms/thirdroom/"] = {
config.staticOidcClients["https://id.thirdroom.io/realms/thirdroom/"] = {
client_id: oidcClientId,
uris: oidcUris,
guestKeycloakIdpHint: "guest",
};

Expand All @@ -153,11 +146,12 @@ function initHydrogen() {
};

const platform = new Platform({ container, assetPaths, config, options });
const features = new FeatureSet(FeatureFlag.Calls);

const navigation = new Navigation(allowsChild);
platform.setNavigation(navigation);

const client = new Client(platform, { deviceName: "Third Room" });
const client = new Client(platform, features, { deviceName: "Third Room" });

hydrogenInstance = {
client,
Expand Down Expand Up @@ -274,7 +268,7 @@ async function getOidcLoginMethod(platform: Platform, urlCreator: URLRouter, sta
return new OIDCLoginMethod({
oidcApi: new OidcApi({
issuer,
clientConfigs: platform.config.oidc.clientConfigs,
staticClients: platform.config.staticOidcClients,
clientId,
urlCreator,
request: platform.request,
Expand Down
4 changes: 2 additions & 2 deletions src/ui/views/login/LoginView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ async function startOIDCLogin(

function getMatchingClientConfig(platform: Platform, issuer: string) {
const normalisedIssuer = `${issuer}${issuer.endsWith("/") ? "" : "/"}`;
return platform.config.oidc.clientConfigs[normalisedIssuer];
return platform.config.staticOidcClients[normalisedIssuer];
}

export default function LoginView() {
Expand Down Expand Up @@ -209,7 +209,7 @@ export default function LoginView() {
const { issuer } = result.oidc;
const oidcApi = new OidcApi({
issuer,
clientConfigs: platform.config.oidc.clientConfigs,
staticClients: platform.config.staticOidcClients,
request: platform.request,
encoding: platform.encoding,
crypto: platform.crypto,
Expand Down
3 changes: 3 additions & 0 deletions src/ui/views/session/chat/tiles/ChatDate.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.ChatDate {
margin: var(--sp-sm) auto;
}
18 changes: 18 additions & 0 deletions src/ui/views/session/chat/tiles/ChatDate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { TemplateView, DateTile, Builder } from "@thirdroom/hydrogen-view-sdk";

import "./ChatDate.css";

export class ChatDate extends TemplateView<DateTile> {
constructor(vm: DateTile) {
super(vm);
}

render(t: Builder<DateTile>, vm: DateTile) {
return t.p(
{ className: "ChatDate Text Text-b2 Text--surface Text--semi-bold" },
t.time({ dateTime: vm.machineReadableDate }, vm.relativeDate)
);
}

onClick() {}
}
3 changes: 3 additions & 0 deletions src/ui/views/session/chat/tiles/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ChatGap } from "./ChatGap";
import { ChatMessage } from "./ChatMessage";
import { ChatAnnouncement } from "./ChatAnnouncement";
import { ChatImage } from "./ChatImage";
import { ChatDate } from "./ChatDate";

export function viewClassForTile(vm: SimpleTile): TileViewConstructor<any> {
switch (vm.shape) {
Expand All @@ -16,6 +17,8 @@ export function viewClassForTile(vm: SimpleTile): TileViewConstructor<any> {
return ChatMessage;
case "image":
return ChatImage;
case "date-header":
return ChatDate;
default:
throw new Error(
`Tiles of shape "${vm.shape}" are not supported, check the tileClassForEntry function in the view model`
Expand Down
Loading