Merge pull request #1517 from thecodingmachine/develop

Release 1.5.5
This commit is contained in:
grégoire parant 2021-10-12 20:52:42 +02:00 committed by GitHub
commit ee906dc52b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 178 additions and 98 deletions

View File

@ -1,5 +1,10 @@
## Version develop
### Bugfix
- Moving a discussion over a user will now add this user to the discussion
- Being in a silent zone new forces mediaConstraints to false (#1508)
- Fixes for the emote menu (#1501)
## Version 1.5.0
### Updates
- Added support for login with OpenID Connect

View File

@ -181,6 +181,7 @@ export class GameRoom {
private updateUserGroup(user: User): void {
user.group?.updatePosition();
user.group?.searchForNearbyUsers();
if (user.silent) {
return;
@ -206,6 +207,7 @@ export class GameRoom {
const group: Group = new Group(
this.roomUrl,
[user, closestUser],
this.groupRadius,
this.connectCallback,
this.disconnectCallback,
this.positionNotifier

View File

@ -1,10 +1,10 @@
import { ConnectCallback, DisconnectCallback } from "./GameRoom";
import { ConnectCallback, DisconnectCallback, GameRoom } from "./GameRoom";
import { User } from "./User";
import { PositionInterface } from "_Model/PositionInterface";
import { Movable } from "_Model/Movable";
import { PositionNotifier } from "_Model/PositionNotifier";
import { gaugeManager } from "../Services/GaugeManager";
import { MAX_PER_GROUP } from "../Enum/EnvironmentVariable";
import type { Zone } from "../Model/Zone";
export class Group implements Movable {
private static nextId: number = 1;
@ -13,13 +13,14 @@ export class Group implements Movable {
private users: Set<User>;
private x!: number;
private y!: number;
private hasEditedGauge: boolean = false;
private wasDestroyed: boolean = false;
private roomId: string;
private currentZone: Zone | null = null;
constructor(
roomId: string,
users: User[],
private groupRadius: number,
private connectCallback: ConnectCallback,
private disconnectCallback: DisconnectCallback,
private positionNotifier: PositionNotifier
@ -28,13 +29,6 @@ export class Group implements Movable {
this.users = new Set<User>();
this.id = Group.nextId;
Group.nextId++;
//we only send a event for prometheus metrics if the group lives more than 5 seconds
setTimeout(() => {
if (!this.wasDestroyed) {
this.hasEditedGauge = true;
gaugeManager.incNbGroupsPerRoomGauge(roomId);
}
}, 5000);
users.forEach((user: User) => {
this.join(user);
@ -85,9 +79,22 @@ export class Group implements Movable {
this.y = y;
if (oldX === undefined) {
this.positionNotifier.enter(this);
this.currentZone = this.positionNotifier.enter(this);
} else {
this.positionNotifier.updatePosition(this, { x, y }, { x: oldX, y: oldY });
this.currentZone = this.positionNotifier.updatePosition(this, { x, y }, { x: oldX, y: oldY });
}
}
searchForNearbyUsers(): void {
if (!this.currentZone) return;
for (const user of this.positionNotifier.getAllUsersInSquareAroundZone(this.currentZone)) {
if (user.group || this.isFull()) return; //we ignore users that are already in a group.
const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), this.getPosition());
if (distance < this.groupRadius) {
this.join(user);
this.updatePosition();
}
}
}
@ -126,7 +133,6 @@ export class Group implements Movable {
* Usually used when there is only one user left.
*/
destroy(): void {
if (this.hasEditedGauge) gaugeManager.decNbGroupsPerRoomGauge(this.roomId);
for (const user of this.users) {
this.leave(user);
}

View File

@ -12,7 +12,7 @@ import { EmoteCallback, EntersCallback, LeavesCallback, MovesCallback, Zone } fr
import { Movable } from "_Model/Movable";
import { PositionInterface } from "_Model/PositionInterface";
import { ZoneSocket } from "../RoomManager";
import { User } from "_Model/User";
import { User } from "../Model/User";
import { EmoteEventMessage } from "../Messages/generated/messages_pb";
interface ZoneDescriptor {
@ -20,6 +20,17 @@ interface ZoneDescriptor {
j: number;
}
export function* getNearbyDescriptorsMatrix(middleZoneDescriptor: ZoneDescriptor): Generator<ZoneDescriptor> {
for (let n = 0; n < 9; n++) {
const i = middleZoneDescriptor.i + ((n % 3) - 1);
const j = middleZoneDescriptor.j + (Math.floor(n / 3) - 1);
if (i >= 0 && j >= 0) {
yield { i, j };
}
}
}
export class PositionNotifier {
// TODO: we need a way to clean the zones if no one is in the zone and no one listening (to free memory!)
@ -41,14 +52,15 @@ export class PositionNotifier {
};
}
public enter(thing: Movable): void {
public enter(thing: Movable): Zone {
const position = thing.getPosition();
const zoneDesc = this.getZoneDescriptorFromCoordinates(position.x, position.y);
const zone = this.getZone(zoneDesc.i, zoneDesc.j);
zone.enter(thing, null, position);
return zone;
}
public updatePosition(thing: Movable, newPosition: PositionInterface, oldPosition: PositionInterface): void {
public updatePosition(thing: Movable, newPosition: PositionInterface, oldPosition: PositionInterface): Zone {
// Did we change zone?
const oldZoneDesc = this.getZoneDescriptorFromCoordinates(oldPosition.x, oldPosition.y);
const newZoneDesc = this.getZoneDescriptorFromCoordinates(newPosition.x, newPosition.y);
@ -62,9 +74,11 @@ export class PositionNotifier {
// Enter new zone
newZone.enter(thing, oldZone, newPosition);
return newZone;
} else {
const zone = this.getZone(oldZoneDesc.i, oldZoneDesc.j);
zone.move(thing, newPosition);
return zone;
}
}
@ -106,4 +120,16 @@ export class PositionNotifier {
const zone = this.getZone(zoneDesc.i, zoneDesc.j);
zone.emitEmoteEvent(emoteEventMessage);
}
public *getAllUsersInSquareAroundZone(zone: Zone): Generator<User> {
const zoneDescriptor = this.getZoneDescriptorFromCoordinates(zone.x, zone.y);
for (const d of getNearbyDescriptorsMatrix(zoneDescriptor)) {
const zone = this.getZone(d.i, d.j);
for (const thing of zone.getThings()) {
if (thing instanceof User) {
yield thing;
}
}
}
}
}

View File

@ -52,15 +52,6 @@ class GaugeManager {
this.nbClientsGauge.dec();
this.nbClientsPerRoomGauge.dec({ room: roomId });
}
incNbGroupsPerRoomGauge(roomId: string): void {
this.nbGroupsPerRoomCounter.inc({ room: roomId });
this.nbGroupsPerRoomGauge.inc({ room: roomId });
}
decNbGroupsPerRoomGauge(roomId: string): void {
this.nbGroupsPerRoomGauge.dec({ room: roomId });
}
}
export const gaugeManager = new GaugeManager();

View File

@ -0,0 +1,67 @@
import "jasmine";
import { getNearbyDescriptorsMatrix } from "../src/Model/PositionNotifier";
describe("getNearbyDescriptorsMatrix", () => {
it("should create a matrix of coordinates in a square around the parameter", () => {
const matrix = [];
for (const d of getNearbyDescriptorsMatrix({ i: 1, j: 1 })) {
matrix.push(d);
}
expect(matrix).toEqual([
{ i: 0, j: 0 },
{ i: 1, j: 0 },
{ i: 2, j: 0 },
{ i: 0, j: 1 },
{ i: 1, j: 1 },
{ i: 2, j: 1 },
{ i: 0, j: 2 },
{ i: 1, j: 2 },
{ i: 2, j: 2 },
]);
});
it("should create a matrix of coordinates in a square around the parameter bis", () => {
const matrix = [];
for (const d of getNearbyDescriptorsMatrix({ i: 8, j: 3 })) {
matrix.push(d);
}
expect(matrix).toEqual([
{ i: 7, j: 2 },
{ i: 8, j: 2 },
{ i: 9, j: 2 },
{ i: 7, j: 3 },
{ i: 8, j: 3 },
{ i: 9, j: 3 },
{ i: 7, j: 4 },
{ i: 8, j: 4 },
{ i: 9, j: 4 },
]);
});
it("should not create a matrix with negative coordinates", () => {
const matrix = [];
for (const d of getNearbyDescriptorsMatrix({ i: 0, j: 0 })) {
matrix.push(d);
}
expect(matrix).toEqual([
{ i: 0, j: 0 },
{ i: 1, j: 0 },
{ i: 0, j: 1 },
{ i: 1, j: 1 },
]);
});
/*it("should not create a matrix with coordinates bigger than its dimmensions", () => {
const matrix = getNearbyDescriptorsMatrix({i: 4, j: 4}, 5, 5);
expect(matrix).toEqual([
{i: 3,j: 3},
{i: 4,j: 3},
{i: 3,j: 4},
{i: 4,j: 4},
])
});*/
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 516 B

After

Width:  |  Height:  |  Size: 389 B

View File

@ -50,7 +50,7 @@
"phaser": "^3.54.0",
"phaser-animated-tiles": "workadventure/phaser-animated-tiles#da68bbededd605925621dd4f03bd27e69284b254",
"phaser3-rex-plugins": "^1.1.42",
"posthog-js": "^1.13.12",
"posthog-js": "^1.14.1",
"queue-typescript": "^1.0.1",
"quill": "1.3.6",
"quill-delta-to-html": "^0.12.0",

View File

@ -1,4 +1,6 @@
import { POSTHOG_API_KEY, POSTHOG_URL } from "../Enum/EnvironmentVariable";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
declare let window: any;
class AnalyticsClient {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -8,6 +10,8 @@ class AnalyticsClient {
if (POSTHOG_API_KEY && POSTHOG_URL) {
this.posthogPromise = import("posthog-js").then(({ default: posthog }) => {
posthog.init(POSTHOG_API_KEY, { api_host: POSTHOG_URL, disable_cookie: true });
//the posthog toolbar need a reference in window to be able to work
window.posthog = posthog;
return posthog;
});
} else {
@ -15,10 +19,10 @@ class AnalyticsClient {
}
}
identifyUser(uuid: string) {
identifyUser(uuid: string, email: string | null) {
this.posthogPromise
.then((posthog) => {
posthog.identify(uuid, { uuid, wa: true });
posthog.identify(uuid, { uuid, email, wa: true });
})
.catch();
}
@ -39,10 +43,10 @@ class AnalyticsClient {
.catch();
}
enteredRoom(roomId: string) {
enteredRoom(roomId: string, roomGroup: string | null) {
this.posthogPromise
.then((posthog) => {
posthog.capture("$pageView", { roomId });
posthog.capture("$pageView", { roomId, roomGroup });
})
.catch();
}

View File

@ -15,6 +15,7 @@
import {onDestroy} from "svelte";
function screenSharingClick(): void {
if (isSilent) return;
if ($requestedScreenSharingState === true) {
requestedScreenSharingState.disableScreenSharing();
} else {
@ -23,6 +24,7 @@
}
function cameraClick(): void {
if (isSilent) return;
if ($requestedCameraState === true) {
requestedCameraState.disableWebcam();
} else {
@ -31,6 +33,7 @@
}
function microphoneClick(): void {
if (isSilent) return;
if ($requestedMicrophoneState === true) {
requestedMicrophoneState.disableMicrophone();
} else {

View File

@ -1,11 +1,16 @@
<script lang="typescript">
import logoWA from "../images/logo-WA-min.png"
import logoWA from "../images/logo-WA-pixel.png"
import logoTalk from "../images/logo-message-pixel.png"
import {menuVisiblilityStore} from "../../Stores/MenuStore";
import {chatVisibilityStore} from "../../Stores/ChatStore";
import {get} from "svelte/store";
function showMenu(){
menuVisiblilityStore.set(!get(menuVisiblilityStore))
}
function showChat(){
chatVisibilityStore.set(true);
}
function onKeyDown(e: KeyboardEvent) {
if (e.key === "Tab") {
@ -18,17 +23,23 @@
<main class="menuIcon">
<img src={logoWA} alt="open menu" class="nes-pointer" on:click|preventDefault={showMenu}>
<img src={logoTalk} alt="open menu" class="nes-pointer" on:click|preventDefault={showChat}>
</main>
<style lang="scss">
.menuIcon {
display: inline-grid;
margin: 25px;
img {
pointer-events: auto;
width: 60px;
padding-top: 0;
margin: 3px
}
}
.menuIcon img:hover{
transform: scale(1.2);
}
@media only screen and (max-width: 800px), only screen and (max-height: 800px) {
.menuIcon {
margin: 3px;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

View File

@ -112,7 +112,7 @@ class ConnectionManager {
const data = await Axios.post(`${PUSHER_URL}/register`, { organizationMemberToken }).then(
(res) => res.data
);
this.localUser = new LocalUser(data.userUuid, data.textures);
this.localUser = new LocalUser(data.userUuid, data.textures, data.email);
this.authToken = data.authToken;
localUserStore.saveUser(this.localUser);
localUserStore.setAuthToken(this.authToken);
@ -196,7 +196,7 @@ class ConnectionManager {
return Promise.reject(new Error("Invalid URL"));
}
if (this.localUser) {
analyticsClient.identifyUser(this.localUser.uuid);
analyticsClient.identifyUser(this.localUser.uuid, this.localUser.email);
}
this.serviceWorker = new _ServiceWorker();

View File

@ -24,5 +24,9 @@ export function areCharacterLayersValid(value: string[] | null): boolean {
}
export class LocalUser {
constructor(public readonly uuid: string, public textures: CharacterTexture[]) {}
constructor(
public readonly uuid: string,
public textures: CharacterTexture[],
public email: string | null = null
) {}
}

View File

@ -21,6 +21,7 @@ export class Room {
private instance: string | undefined;
private readonly _search: URLSearchParams;
private _contactPage: string | undefined;
private _group: string | null = null;
private constructor(private roomUrl: URL) {
this.id = roomUrl.pathname;
@ -104,6 +105,7 @@ export class Room {
console.log("Map ", this.id, " resolves to URL ", data.mapUrl);
this._mapUrl = data.mapUrl;
this._textures = data.textures;
this._group = data.group;
this._authenticationMandatory = data.authenticationMandatory || false;
this._iframeAuthentication = data.iframeAuthentication;
this._contactPage = data.contactPage || CONTACT_URL;
@ -133,25 +135,6 @@ export class Room {
}
}
/**
* @deprecated
*/
private parsePrivateUrl(url: string): { organizationSlug: string; worldSlug: string; roomSlug?: string } {
const regex = /@\/([^/]+)\/([^/]+)(?:\/([^/]*))?/gm;
const match = regex.exec(url);
if (!match) {
throw new Error("Invalid URL " + url);
}
const results: { organizationSlug: string; worldSlug: string; roomSlug?: string } = {
organizationSlug: match[1],
worldSlug: match[2],
};
if (match[3] !== undefined) {
results.roomSlug = match[3];
}
return results;
}
public isDisconnected(): boolean {
const alone = this._search.get("alone");
if (alone && alone !== "0" && alone.toLowerCase() !== "false") {
@ -204,4 +187,8 @@ export class Room {
get contactPage(): string | undefined {
return this._contactPage;
}
get group(): string | null {
return this._group;
}
}

View File

@ -1,17 +0,0 @@
import { DEPTH_INGAME_TEXT_INDEX } from "../Game/DepthIndexes";
import { chatVisibilityStore } from "../../Stores/ChatStore";
export const openChatIconName = "openChatIcon";
export class OpenChatIcon extends Phaser.GameObjects.Image {
constructor(scene: Phaser.Scene, x: number, y: number) {
super(scene, x, y, openChatIconName, 3);
scene.add.existing(this);
this.setScrollFactor(0, 0);
this.setOrigin(0, 1);
this.setInteractive();
//this.setVisible(false);
this.setDepth(DEPTH_INGAME_TEXT_INDEX);
this.on("pointerup", () => chatVisibilityStore.set(true));
}
}

View File

@ -38,7 +38,6 @@ import { HtmlUtils } from "../../WebRtc/HtmlUtils";
import { mediaManager } from "../../WebRtc/MediaManager";
import { SimplePeer } from "../../WebRtc/SimplePeer";
import { addLoader, removeLoader } from "../Components/Loader";
import { OpenChatIcon, openChatIconName } from "../Components/OpenChatIcon";
import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
import { RemotePlayer } from "../Entity/RemotePlayer";
import type { ActionableItem } from "../Items/ActionableItem";
@ -174,7 +173,6 @@ export class GameScene extends DirtyScene {
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
private iframeSubscriptionList!: Array<Subscription>;
private peerStoreUnsubscribe!: () => void;
private chatVisibilityUnsubscribe!: () => void;
private emoteUnsubscribe!: () => void;
private emoteMenuUnsubscribe!: () => void;
private biggestAvailableAreaStoreUnsubscribe!: () => void;
@ -197,7 +195,6 @@ export class GameScene extends DirtyScene {
private outlinedItem: ActionableItem | null = null;
public userInputManager!: UserInputManager;
private isReconnecting: boolean | undefined = undefined;
private openChatIcon!: OpenChatIcon;
private playerName!: string;
private characterLayers!: string[];
private companion!: string | null;
@ -245,7 +242,6 @@ export class GameScene extends DirtyScene {
}
}
this.load.image(openChatIconName, "resources/objects/talk.png");
if (touchScreenManager.supportTouchScreen) {
this.load.image(joystickBaseKey, joystickBaseImg);
this.load.image(joystickThumbKey, joystickThumbImg);
@ -432,7 +428,7 @@ export class GameScene extends DirtyScene {
gameManager.gameSceneIsCreated(this);
urlManager.pushRoomIdToUrl(this.room);
analyticsClient.enteredRoom(this.room.id);
analyticsClient.enteredRoom(this.room.id, this.room.group);
contactPageStore.set(this.room.contactPage);
if (touchScreenManager.supportTouchScreen) {
@ -584,8 +580,6 @@ export class GameScene extends DirtyScene {
this.outlinedItem?.activate();
});
this.openChatIcon = new OpenChatIcon(this, 2, this.game.renderer.height - 2);
this.reposition();
// From now, this game scene will be notified of reposition events
@ -618,10 +612,6 @@ export class GameScene extends DirtyScene {
oldPeerNumber = newPeerNumber;
});
this.chatVisibilityUnsubscribe = chatVisibilityStore.subscribe((v) => {
this.openChatIcon.setVisible(!v);
});
this.emoteUnsubscribe = emoteStore.subscribe((emoteKey) => {
if (emoteKey) {
this.CurrentPlayer?.playEmote(emoteKey);
@ -1327,7 +1317,6 @@ ${escapedMessage}
this.pinchManager?.destroy();
this.emoteManager.destroy();
this.peerStoreUnsubscribe();
this.chatVisibilityUnsubscribe();
this.emoteUnsubscribe();
this.emoteMenuUnsubscribe();
this.biggestAvailableAreaStoreUnsubscribe();
@ -1802,8 +1791,6 @@ ${escapedMessage}
return undefined;
}
private reposition(): void {
this.openChatIcon.setY(this.game.renderer.height - 2);
// Recompute camera offset if needed
biggestAvailableAreaStore.recompute();
}

View File

@ -177,6 +177,11 @@ function createVideoConstraintStore() {
};
}
/**
* A store containing if user is silent, so if he is in silent zone. This permit to show et hide camera of user
*/
export const isSilentStore = writable(false);
export const videoConstraintStore = createVideoConstraintStore();
/**
@ -234,6 +239,7 @@ export const mediaStreamConstraintsStore = derived(
audioConstraintStore,
privacyShutdownStore,
cameraEnergySavingStore,
isSilentStore,
],
(
[
@ -245,6 +251,7 @@ export const mediaStreamConstraintsStore = derived(
$audioConstraintStore,
$privacyShutdownStore,
$cameraEnergySavingStore,
$isSilentStore,
],
set
) => {
@ -294,6 +301,11 @@ export const mediaStreamConstraintsStore = derived(
//currentAudioConstraint = false;
}
if ($isSilentStore === true) {
currentVideoConstraint = false;
currentAudioConstraint = false;
}
// Let's make the changes only if the new value is different from the old one.
if (
previousComputedVideoConstraint != currentVideoConstraint ||
@ -607,13 +619,3 @@ localStreamStore.subscribe((streamResult) => {
}
}
});
/**
* A store containing the real active media is mobile
*/
export const obtainedMediaConstraintIsMobileStore = writable(false);
/**
* A store containing if user is silent, so if he is in silent zone. This permit to show et hide camera of user
*/
export const isSilentStore = writable(false);

View File

@ -4494,10 +4494,10 @@ postcss@^8.2.10:
nanoid "^3.1.23"
source-map "^0.6.1"
posthog-js@^1.13.12:
version "1.13.12"
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.13.12.tgz#b54efcb92de43724c889048135ccaae3dc4b874c"
integrity sha512-MU1I0zSVhdCcnWI8jAZLtbNJmjfg9AnhUDq5dUzNkb0qPXtNz17BekalnNwDMKs0Zlek3UCOVsIpyc85M+VRNA==
posthog-js@^1.14.1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.14.1.tgz#68553b9074c686784b994b3f433ad035b241deaa"
integrity sha512-gVFk6fsTxG9T+5oqZghffYovENSrs05v3hQMiRQENtGlsHGpt1fnoQ52En8Vg5brMxqLv1XZO4lMgm12/ubH0Q==
dependencies:
"@sentry/types" "^6.11.0"
fflate "^0.4.1"

View File

@ -148,6 +148,7 @@ export class AuthenticateController extends BaseController {
JSON.stringify({
authToken,
userUuid,
email,
roomUrl,
mapUrlStart,
organizationMemberToken,

View File

@ -60,6 +60,7 @@ export class MapController extends BaseController {
mapUrl,
policy_type: GameRoomPolicyTypes.ANONYMOUS_POLICY,
roomSlug: "", // Deprecated
group: null,
tags: [],
textures: [],
contactPage: undefined,