Skip to content

Commit

Permalink
Merge branch 'peeking_with_guest_login' into integrations
Browse files Browse the repository at this point in the history
  • Loading branch information
ashfame committed Mar 14, 2023
2 parents 9e1d62e + f95684b commit a165fd8
Show file tree
Hide file tree
Showing 13 changed files with 396 additions and 9 deletions.
16 changes: 15 additions & 1 deletion src/domain/session/RoomViewModelObservable.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,24 @@ export class RoomViewModelObservable extends ObservableValue {
} else if (status & RoomStatus.Archived) {
return await this._sessionViewModel._createArchivedRoomViewModel(this.id);
} else {
return this._sessionViewModel._createUnknownRoomViewModel(this.id);
return this._sessionViewModel._createUnknownRoomViewModel(this.id, this._isWorldReadablePromise());
}
}

async _isWorldReadablePromise() {
const {session} = this._sessionViewModel._client;
const isWorldReadable = await session.isWorldReadableRoom(this.id);
if (isWorldReadable) {
const vm = await this._sessionViewModel._createWorldReadableRoomViewModel(this.id);
if (vm) {
this.get()?.dispose();
this.set(vm);
return true;
}
}
return false;
}

dispose() {
if (this._statusSubscription) {
this._statusSubscription = this._statusSubscription();
Expand Down
13 changes: 12 additions & 1 deletion src/domain/session/SessionViewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ limitations under the License.
import {LeftPanelViewModel} from "./leftpanel/LeftPanelViewModel.js";
import {RoomViewModel} from "./room/RoomViewModel.js";
import {UnknownRoomViewModel} from "./room/UnknownRoomViewModel.js";
import {WorldReadableRoomViewModel} from "./room/WorldReadableRoomViewModel.js";
import {InviteViewModel} from "./room/InviteViewModel.js";
import {RoomBeingCreatedViewModel} from "./room/RoomBeingCreatedViewModel.js";
import {LightboxViewModel} from "./room/LightboxViewModel.js";
Expand Down Expand Up @@ -231,13 +232,23 @@ export class SessionViewModel extends ViewModel {
return null;
}

_createUnknownRoomViewModel(roomIdOrAlias) {
_createUnknownRoomViewModel(roomIdOrAlias, isWorldReadablePromise) {
return new UnknownRoomViewModel(this.childOptions({
roomIdOrAlias,
session: this._client.session,
isWorldReadablePromise: isWorldReadablePromise
}));
}

async _createWorldReadableRoomViewModel(roomIdOrAlias) {
const roomVM = new WorldReadableRoomViewModel(this.childOptions({
room: await this._client.session.loadWorldReadableRoom(roomIdOrAlias),
session: this._client.session,
}));
roomVM.load();
return roomVM;
}

async _createArchivedRoomViewModel(roomId) {
const room = await this._client.session.loadArchivedRoom(roomId);
if (room) {
Expand Down
4 changes: 2 additions & 2 deletions src/domain/session/room/RoomViewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class RoomViewModel extends ErrorReportViewModel {
this._composerVM = null;
if (room.isArchived) {
this._composerVM = this.track(new ArchivedViewModel(this.childOptions({archivedRoom: room})));
} else {
} else if (!room.isWorldReadable) {
this._recreateComposerOnPowerLevelChange();
}
this._clearUnreadTimout = null;
Expand Down Expand Up @@ -218,7 +218,7 @@ export class RoomViewModel extends ErrorReportViewModel {
}
}
}

_sendMessage(message, replyingTo) {
return this.logAndCatch("RoomViewModel.sendMessage", async log => {
let success = false;
Expand Down
20 changes: 19 additions & 1 deletion src/domain/session/room/UnknownRoomViewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,35 @@ import {ViewModel} from "../../ViewModel";
export class UnknownRoomViewModel extends ViewModel {
constructor(options) {
super(options);
const {roomIdOrAlias, session} = options;
const {roomIdOrAlias, session, isWorldReadablePromise, guestJoinAllowed} = options;
this._session = session;
this.roomIdOrAlias = roomIdOrAlias;
this._error = null;
this._busy = false;
this._guestJoinAllowed = typeof guestJoinAllowed !== 'undefined' && guestJoinAllowed;

this.checkingPreviewCapability = true;
isWorldReadablePromise.then(() => {
this.checkingPreviewCapability = false;
this.emitChange('checkingPreviewCapability');
})

// join allowed for the current user/session?
this._joinAllowed = false;
this._session.isGuest().then((isGuest) => {
this._joinAllowed = isGuest ? this._guestJoinAllowed : true;
this.emitChange('joinAllowed');
});
}

get error() {
return this._error?.message;
}

get joinAllowed() {
return this._joinAllowed;
}

async join() {
this._busy = true;
this.emitChange("busy");
Expand Down
64 changes: 64 additions & 0 deletions src/domain/session/room/WorldReadableRoomViewModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {RoomViewModel} from "./RoomViewModel";

export class WorldReadableRoomViewModel extends RoomViewModel {
constructor(options) {
options.room.isWorldReadable = true;
super(options);
const {room, session, guestJoinAllowed} = options;
this._room = room;
this._session = session;
this._error = null;
this._busy = false;
this._guestJoinAllowed = typeof guestJoinAllowed !== 'undefined' && guestJoinAllowed;

// join allowed for the current user/session?
this._joinAllowed = false;
this._session.isGuest().then((isGuest) => {
this._joinAllowed = isGuest ? this._guestJoinAllowed : true;
this.emitChange('joinAllowed');
});
}

get kind() {
return "preview";
}

get busy() {
return this._busy;
}

get joinAllowed() {
return this._joinAllowed;
}

async join() {
this._busy = true;
this.emitChange("busy");
try {
const roomId = await this._session.joinRoom(this._room.id);
// navigate to roomId if we were at the alias
// so we're subscribed to the right room status
// and we'll switch to the room view model once
// the join is synced
this.navigation.push("room", roomId);
// keep busy on true while waiting for the join to sync
} catch (err) {
this._error = err;
this._busy = false;
this.emitChange("error");
}
}

login() {
this.navigation.push("login");
}

dispose() {
super.dispose();

// if joining the room, _busy would be true and in that case don't delete records
if (!this._busy) {
void this._session.deleteWorldReadableRoomData(this._room.id);
}
}
}
28 changes: 28 additions & 0 deletions src/matrix/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,34 @@ export class Client {
return registration;
}

async doGuestLogin(homeserver) {
const currentStatus = this._status.get();
if (currentStatus !== LoadStatus.LoginFailed &&
currentStatus !== LoadStatus.NotLoading &&
currentStatus !== LoadStatus.Error) {
return;
}
this._resetStatus();

await this._platform.logger.run("login", async log => {
this._status.set(LoadStatus.Login);
try {
const request = this._platform.request;
const hsApi = new HomeServerApi({homeserver: homeserver, request: request});
const loginData = await hsApi.guestLogin().response();
await this.startWithAuthData({
accessToken: loginData.access_token,
deviceId: loginData.device_id,
userId: loginData.user_id,
homeserver: homeserver
});
} catch (err) {
this._error = err;
this._status.set(LoadStatus.Error);
}
});
}

/** Method to start client after registration or with given access token.
* To start the client after registering, use `startWithAuthData(registration.authData)`.
* `homeserver` won't be resolved or normalized using this method,
Expand Down
150 changes: 150 additions & 0 deletions src/matrix/Session.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ import {SecretStorage} from "./ssss/SecretStorage";
import {ObservableValue, RetainedObservableValue} from "../observable/value";
import {CallHandler} from "./calls/CallHandler";
import {RoomStateHandlerSet} from "./room/state/RoomStateHandlerSet";
import {EventKey} from "./room/timeline/EventKey";
import {createEventEntry} from "./room/timeline/persistence/common";

const PICKLE_KEY = "DEFAULT_KEY";
const PUSHER_KEY = "pusher";
Expand Down Expand Up @@ -132,6 +134,15 @@ export class Session {
return this._callHandler;
}

async isGuest() {
if (typeof this._guestUser !== 'undefined') {
return this._guestUser;
}
const whoami = await this._hsApi.whoami().response();
this._guestUser = whoami.is_guest;
return Boolean(whoami.is_guest);
}

_setupCallHandler() {
this._callHandler = new CallHandler({
clock: this._platform.clock,
Expand Down Expand Up @@ -655,6 +666,22 @@ export class Session {
return room;
}

/** @internal */
_createWorldReadableRoom(roomId) {
return new Room({
roomId,
getSyncToken: this._getSyncToken,
storage: this._storage,
emitCollectionChange: this._roomUpdateCallback,
hsApi: this._hsApi,
mediaRepository: this._mediaRepository,
pendingEvents: [],
user: this._user,
platform: this._platform,
roomStateHandler: this._roomStateHandler
});
}

get invites() {
return this._invites;
}
Expand Down Expand Up @@ -1031,12 +1058,135 @@ export class Session {
});
}

loadWorldReadableRoom(roomId, log = null) {
return this._platform.logger.wrapOrRun(log, "loadWorldReadableRoom", async log => {
log.set("id", roomId);

const room = this._createWorldReadableRoom(roomId);
let response = await this._fetchWorldReadableRoomEvents(roomId, 100, 'b', null, log);
// Note: response.end to be used in the next call for sync functionality

let summary = await this._prepareWorldReadableRoomSummary(roomId, log);
const txn = await this._storage.readTxn([
this._storage.storeNames.timelineFragments,
this._storage.storeNames.timelineEvents,
this._storage.storeNames.roomMembers,
]);
await room.load(summary, txn, log);

return room;
});
}

async _prepareWorldReadableRoomSummary(roomId, log = null) {
return this._platform.logger.wrapOrRun(log, "prepareWorldReadableRoomSummary", async log => {
log.set("id", roomId);

let summary = {};
const resp = await this._hsApi.currentState(roomId).response();
for ( let i=0; i<resp.length; i++ ) {
if ( resp[i].type === 'm.room.name') {
summary["name"] = resp[i].content.name;
} else if ( resp[i].type === 'm.room.canonical_alias' ) {
summary["canonicalAlias"] = resp[i].content.alias;
} else if ( resp[i].type === 'm.room.avatar' ) {
summary["avatarUrl"] = resp[i].content.url;
}
}

return summary;
});
}

async _fetchWorldReadableRoomEvents(roomId, limit = 30, dir = 'b', end = null, log = null) {
return this._platform.logger.wrapOrRun(log, "fetchWorldReadableRoomEvents", async log => {
log.set("id", roomId);
let options = {
limit: limit,
dir: 'b',
filter: {
lazy_load_members: true,
include_redundant_members: true,
}
}
if (end !== null) {
options['from'] = end;
}

const response = await this._hsApi.messages(roomId, options, {log}).response();
log.set("/messages endpoint response", response);

await this.deleteWorldReadableRoomData(roomId, log);

const txn = await this._storage.readWriteTxn([
this._storage.storeNames.timelineFragments,
this._storage.storeNames.timelineEvents,
]);

// insert fragment and event records for this room
const fragment = {
roomId: roomId,
id: 0,
previousId: null,
nextId: null,
previousToken: response.start,
nextToken: null,
};
txn.timelineFragments.add(fragment);

let eventKey = EventKey.defaultLiveKey;
for (let i = 0; i < response.chunk.length; i++) {
if (i) {
eventKey = eventKey.previousKey();
}
let txn = await this._storage.readWriteTxn([this._storage.storeNames.timelineEvents]);
let eventEntry = createEventEntry(eventKey, roomId, response.chunk[i]);
await txn.timelineEvents.tryInsert(eventEntry, log);
}

return response;
});
}

async deleteWorldReadableRoomData(roomId, log = null) {
return this._platform.logger.wrapOrRun(log, "deleteWorldReadableRoomData", async log => {
log.set("id", roomId);

const txn = await this._storage.readWriteTxn([
this._storage.storeNames.timelineFragments,
this._storage.storeNames.timelineEvents,
]);

// clear old records for this room
txn.timelineFragments.removeAllForRoom(roomId);
txn.timelineEvents.removeAllForRoom(roomId);
});
}

joinRoom(roomIdOrAlias, log = null) {
return this._platform.logger.wrapOrRun(log, "joinRoom", async log => {
const body = await this._hsApi.joinIdOrAlias(roomIdOrAlias, {log}).response();
return body.room_id;
});
}

async isWorldReadableRoom(roomIdOrAlias, log = null) {
return this._platform.logger.wrapOrRun(log, "isWorldReadableRoom", async log => {
try {
let roomId;
if (!roomIdOrAlias.startsWith("!")) {
let response = await this._hsApi.resolveRoomAlias(roomIdOrAlias).response();
roomId = response.room_id;
} else {
roomId = roomIdOrAlias;
}
const body = await this._hsApi.state(roomId, 'm.room.history_visibility', '', {log}).response();
return body.history_visibility === 'world_readable';
} catch {
return false;
}
});
}
}

import {FeatureSet} from "../features";
Expand Down
Loading

0 comments on commit a165fd8

Please sign in to comment.