feat: Track user time in meetings (#10)
* feat: Track time users are in meetings * chore: Remove unnecessary overrides from eslint config and console.log * refactor: Separate the event into two: connect & disconnect * chore: Add Segment api keys * feat: Track disconnection only once and pass only world name and content server url
This commit is contained in:
parent
89ebf35e6b
commit
02f1a6df5b
38
package-lock.json
generated
38
package-lock.json
generated
@ -19,7 +19,7 @@
|
||||
"ajv": "^8.12.0",
|
||||
"classnames": "^2.3.2",
|
||||
"decentraland-dapps": "^15.5.0",
|
||||
"decentraland-ui": "^4.0.0",
|
||||
"decentraland-ui": "^4.6.0",
|
||||
"ethers": "^6.6.5",
|
||||
"livekit-client": "^1.12.3",
|
||||
"livekit-server-sdk": "^1.2.5",
|
||||
@ -8346,11 +8346,11 @@
|
||||
"integrity": "sha512-L4/bPD2fOeEdtFx+OnO3N81+/gsOkdensIuV9uFGYSN1mSTFaxHkWkhG8DOZ/8jlD0H2Qjkj6yDcWFaK+qu1Dg=="
|
||||
},
|
||||
"node_modules/decentraland-ui": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/decentraland-ui/-/decentraland-ui-4.1.0.tgz",
|
||||
"integrity": "sha512-lK96yfPhQMusmaiIyhcL4O4nlubRcEq9kem59NBT3O7/gP4O9QLMLoNItZ8gG1U+4Z3FdMLMZ1+/vxq/UMdQTA==",
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/decentraland-ui/-/decentraland-ui-4.6.0.tgz",
|
||||
"integrity": "sha512-dGa0aXpIGUaHTD9T9y7JRXT9JpQtgpwHWmpJWJWeG8A3yUUlfJwdIayLEATmtUpYeCGDJGNZhx4ctgUjoV7Y/Q==",
|
||||
"dependencies": {
|
||||
"@dcl/schemas": "^8.1.0",
|
||||
"@dcl/schemas": "^9.2.0",
|
||||
"balloon-css": "^0.5.0",
|
||||
"classnames": "^2.3.2",
|
||||
"deep-equal": "^2.0.5",
|
||||
@ -8358,8 +8358,8 @@
|
||||
"events": "^3.3.0",
|
||||
"fp-future": "^1.0.1",
|
||||
"parallax-js": "^3.1.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react": "^16.8.0 || ^17.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0",
|
||||
"react-responsive": "^9.0.0-beta.3",
|
||||
"react-semantic-ui-datepickers": "^2.17.2",
|
||||
"react-tile-map": "^0.4.1",
|
||||
@ -8381,9 +8381,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/decentraland-ui/node_modules/@dcl/schemas": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@dcl/schemas/-/schemas-8.1.0.tgz",
|
||||
"integrity": "sha512-1A7st/fESASmss+T1Vxc9lo3LHqSvgfDTLtLD2uhdgtOS0RombNES3KG/2zDfBpg9v3DCEchN6i7mWiCEw7/6g==",
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@dcl/schemas/-/schemas-9.2.0.tgz",
|
||||
"integrity": "sha512-OGjjfbL+JsTqphvc6Msl4a5kYCGJlq2LmAMBY9WDlYRE+9DZBE2Sin+ebekYWBmSUA75mJHO+qUpg0/FC8z+oQ==",
|
||||
"dependencies": {
|
||||
"ajv": "^8.11.0",
|
||||
"ajv-errors": "^3.0.0",
|
||||
@ -26176,11 +26176,11 @@
|
||||
"integrity": "sha512-L4/bPD2fOeEdtFx+OnO3N81+/gsOkdensIuV9uFGYSN1mSTFaxHkWkhG8DOZ/8jlD0H2Qjkj6yDcWFaK+qu1Dg=="
|
||||
},
|
||||
"decentraland-ui": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/decentraland-ui/-/decentraland-ui-4.1.0.tgz",
|
||||
"integrity": "sha512-lK96yfPhQMusmaiIyhcL4O4nlubRcEq9kem59NBT3O7/gP4O9QLMLoNItZ8gG1U+4Z3FdMLMZ1+/vxq/UMdQTA==",
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/decentraland-ui/-/decentraland-ui-4.6.0.tgz",
|
||||
"integrity": "sha512-dGa0aXpIGUaHTD9T9y7JRXT9JpQtgpwHWmpJWJWeG8A3yUUlfJwdIayLEATmtUpYeCGDJGNZhx4ctgUjoV7Y/Q==",
|
||||
"requires": {
|
||||
"@dcl/schemas": "^8.1.0",
|
||||
"@dcl/schemas": "^9.2.0",
|
||||
"balloon-css": "^0.5.0",
|
||||
"classnames": "^2.3.2",
|
||||
"deep-equal": "^2.0.5",
|
||||
@ -26188,8 +26188,8 @@
|
||||
"events": "^3.3.0",
|
||||
"fp-future": "^1.0.1",
|
||||
"parallax-js": "^3.1.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react": "^16.8.0 || ^17.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0",
|
||||
"react-responsive": "^9.0.0-beta.3",
|
||||
"react-semantic-ui-datepickers": "^2.17.2",
|
||||
"react-tile-map": "^0.4.1",
|
||||
@ -26199,9 +26199,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@dcl/schemas": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@dcl/schemas/-/schemas-8.1.0.tgz",
|
||||
"integrity": "sha512-1A7st/fESASmss+T1Vxc9lo3LHqSvgfDTLtLD2uhdgtOS0RombNES3KG/2zDfBpg9v3DCEchN6i7mWiCEw7/6g==",
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@dcl/schemas/-/schemas-9.2.0.tgz",
|
||||
"integrity": "sha512-OGjjfbL+JsTqphvc6Msl4a5kYCGJlq2LmAMBY9WDlYRE+9DZBE2Sin+ebekYWBmSUA75mJHO+qUpg0/FC8z+oQ==",
|
||||
"requires": {
|
||||
"ajv": "^8.11.0",
|
||||
"ajv-errors": "^3.0.0",
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
"ajv": "^8.12.0",
|
||||
"classnames": "^2.3.2",
|
||||
"decentraland-dapps": "^15.5.0",
|
||||
"decentraland-ui": "^4.0.0",
|
||||
"decentraland-ui": "^4.6.0",
|
||||
"ethers": "^6.6.5",
|
||||
"livekit-client": "^1.12.3",
|
||||
"livekit-server-sdk": "^1.2.5",
|
||||
|
||||
@ -4,8 +4,8 @@ import type { SVGProps } from 'react'
|
||||
const PeopleIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="25" viewBox="0 0 28 25" fill="none" {...props}>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10.0003 11.1667C12.9417 11.1667 15.3337 8.77467 15.3337 5.83333C15.3337 2.892 12.9417 0.5 10.0003 0.5C7.05899 0.5 4.66699 2.892 4.66699 5.83333C4.66699 8.77467 7.05899 11.1667 10.0003 11.1667ZM20.667 13.8333C22.8723 13.8333 24.667 12.0387 24.667 9.83333C24.667 7.628 22.8723 5.83333 20.667 5.83333C18.4617 5.83333 16.667 7.628 16.667 9.83333C16.667 12.0387 18.4617 13.8333 20.667 13.8333ZM27.3337 21.8333C27.3337 22.5693 26.7377 23.1667 26.0003 23.1667H19.3337C19.3337 23.9027 18.7377 24.5 18.0003 24.5H2.00033C1.26299 24.5 0.666992 23.9027 0.666992 23.1667C0.666992 18.02 4.85499 13.8333 10.0003 13.8333C12.5697 13.8333 14.8977 14.8773 16.587 16.5613C17.7457 15.6653 19.1723 15.1667 20.667 15.1667C24.343 15.1667 27.3337 18.1573 27.3337 21.8333Z"
|
||||
fill={props.fill ?? 'white'}
|
||||
/>
|
||||
|
||||
@ -1,24 +1,19 @@
|
||||
import { connect } from 'react-redux'
|
||||
import { getAddress, isConnecting } from 'decentraland-dapps/dist/modules/wallet/selectors'
|
||||
import { getServer, getToken } from '../../../modules/conference/selector'
|
||||
import { getServer, getToken, getWorldContentServerUrl, getWorldName } from '../../../modules/conference/selector'
|
||||
import { isLoggingIn } from '../../../modules/identity/selector'
|
||||
import { RootState } from '../../../modules/reducer'
|
||||
import withRouter from '../../../utils/WithRouter'
|
||||
import Conference from './Conference'
|
||||
import { MapDispatch, MapStateProps, OwnProps } from './Conference.types'
|
||||
import { MapStateProps } from './Conference.types'
|
||||
|
||||
const mapStateToProps = (state: RootState, ownProps: OwnProps): MapStateProps => {
|
||||
const addressFromPath = ownProps.router.params.profileAddress
|
||||
const mapStateToProps = (state: RootState): MapStateProps => ({
|
||||
isLoading: isLoggingIn(state) || isConnecting(state),
|
||||
loggedInAddress: getAddress(state)?.toLowerCase(),
|
||||
server: getServer(state),
|
||||
token: getToken(state),
|
||||
worldName: getWorldName(state),
|
||||
worldContentServerUrl: getWorldContentServerUrl(state)
|
||||
})
|
||||
|
||||
return {
|
||||
profileAddress: addressFromPath?.toLowerCase(),
|
||||
isLoading: isLoggingIn(state) || isConnecting(state),
|
||||
loggedInAddress: getAddress(state)?.toLowerCase(),
|
||||
server: getServer(state),
|
||||
token: getToken(state)
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatch = (_dispatch: MapDispatch): any => ({})
|
||||
|
||||
export default withRouter(connect(mapStateToProps, mapDispatch)(Conference))
|
||||
export default withRouter(connect(mapStateToProps)(Conference))
|
||||
|
||||
@ -1,16 +1,58 @@
|
||||
import React from 'react'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { LiveKitRoom } from '@livekit/components-react'
|
||||
import '@livekit/components-styles'
|
||||
import { getAnalytics } from 'decentraland-dapps/dist/modules/analytics/utils'
|
||||
import { Events } from '../../../modules/analytics/types'
|
||||
import { VideoConference } from '../../VideoConference/'
|
||||
import { Props } from './Conference.types'
|
||||
import './Conference.css'
|
||||
|
||||
export default function Conference(props: Props) {
|
||||
const { token, server } = props
|
||||
const { token, server, worldName, worldContentServerUrl } = props
|
||||
const [alreadyDisconnected, setAlreadyDisconnected] = useState(false)
|
||||
const analytics = getAnalytics()
|
||||
|
||||
const track = useCallback(
|
||||
(event: Events) => {
|
||||
if (!worldName || !worldContentServerUrl) return
|
||||
|
||||
analytics.track(event, {
|
||||
worldName,
|
||||
worldContentServerUrl
|
||||
})
|
||||
},
|
||||
[worldName, worldContentServerUrl]
|
||||
)
|
||||
|
||||
const handleConnect = useCallback(() => track(Events.CONNECT), [track])
|
||||
const handleDisconnect = useCallback(() => {
|
||||
// This is to avoid tracking the disconnect event twice
|
||||
if (!alreadyDisconnected) {
|
||||
track(Events.DISCONNECT)
|
||||
setAlreadyDisconnected(true)
|
||||
}
|
||||
}, [track, alreadyDisconnected])
|
||||
|
||||
useEffect(() => {
|
||||
window.onbeforeunload = () => {
|
||||
handleDisconnect()
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.onbeforeunload = null
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<LiveKitRoom token={token} serverUrl={server} connect={true} data-lk-theme="default">
|
||||
<LiveKitRoom
|
||||
token={token}
|
||||
serverUrl={server}
|
||||
connect={true}
|
||||
data-lk-theme="default"
|
||||
onConnected={handleConnect}
|
||||
onDisconnected={handleDisconnect}
|
||||
>
|
||||
<VideoConference />
|
||||
</LiveKitRoom>
|
||||
</>
|
||||
|
||||
@ -1,19 +1,13 @@
|
||||
import { Dispatch } from 'redux'
|
||||
import { RouterProps } from '../../../utils/WithRouter'
|
||||
|
||||
export type Props = {
|
||||
loggedInAddress?: string
|
||||
profileAddress?: string
|
||||
isLoading: boolean
|
||||
server?: string
|
||||
token?: string
|
||||
worldContentServerUrl: string
|
||||
worldName: string
|
||||
}
|
||||
|
||||
export type MapStateProps = Pick<Props, 'loggedInAddress' | 'isLoading' | 'profileAddress' | 'server' | 'token'>
|
||||
export type MapStateProps = Pick<Props, 'loggedInAddress' | 'isLoading' | 'server' | 'token' | 'worldName' | 'worldContentServerUrl'>
|
||||
export type MapDispatch = Dispatch
|
||||
type Params = {
|
||||
profileAddress?: string
|
||||
}
|
||||
export type OwnProps = {
|
||||
router: RouterProps<Params>
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { connect } from 'react-redux'
|
||||
import { getAddress, isConnecting } from 'decentraland-dapps/dist/modules/wallet/selectors'
|
||||
import { setServer, setToken } from '../../../modules/conference/action'
|
||||
import { setServer, setToken, setWorldRelatedInformation } from '../../../modules/conference/action'
|
||||
import { config } from '../../../modules/config'
|
||||
import { getCurrentIdentity, isLoggingIn } from '../../../modules/identity/selector'
|
||||
import { RootState } from '../../../modules/reducer'
|
||||
@ -22,9 +22,10 @@ const mapStateToProps = (state: RootState, ownProps: OwnProps): MapStateProps =>
|
||||
}
|
||||
|
||||
const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({
|
||||
onSubmitConnectForm: (server: string, token: string) => {
|
||||
onSubmitConnectForm: (server: string, token: string, worldsContentServerUrl: string, selectedServer: string) => {
|
||||
dispatch(setServer({ server }))
|
||||
dispatch(setToken({ token }))
|
||||
dispatch(setWorldRelatedInformation({ contentServerUrl: worldsContentServerUrl, name: selectedServer }))
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -90,7 +90,7 @@ function ConnectToWorld(props: Props) {
|
||||
if (!identity) return
|
||||
|
||||
const response: { url: string; token: string } = await livekitConnect(identity, worldsContentServerUrl, selectedServer)
|
||||
onSubmitConnectForm(response.url, response.token)
|
||||
onSubmitConnectForm(response.url, response.token, worldsContentServerUrl, selectedServer)
|
||||
addServerToPreviouslyLoaded(selectedServer)
|
||||
navigate(`/meet/${encodeURIComponent(response.url)}?token=${encodeURIComponent(response.token)}`)
|
||||
} catch (error) {
|
||||
|
||||
@ -8,7 +8,7 @@ export type Props = {
|
||||
previouslyLoadedServers: string[] | null
|
||||
identity: AuthIdentity | null
|
||||
worldsContentServerUrl: string
|
||||
onSubmitConnectForm: (server: string, token: string) => void
|
||||
onSubmitConnectForm: (server: string, token: string, worldsContentServerUrl: string, selectedServer: string) => void
|
||||
}
|
||||
|
||||
export type MapStateProps = Pick<Props, 'loggedInAddress' | 'isLoading' | 'previouslyLoadedServers' | 'identity' | 'worldsContentServerUrl'>
|
||||
|
||||
4
src/modules/analytics/types.ts
Normal file
4
src/modules/analytics/types.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export enum Events {
|
||||
CONNECT = 'Connect',
|
||||
DISCONNECT = 'Disconnect'
|
||||
}
|
||||
@ -1,9 +1,10 @@
|
||||
import { createAction } from '@reduxjs/toolkit'
|
||||
|
||||
export const setServer = createAction<{ server: string }>('Set Server')
|
||||
|
||||
export type SetServerAction = ReturnType<typeof setServer>
|
||||
|
||||
export const setToken = createAction<{ token: string }>('Set Token')
|
||||
|
||||
export type SetTokenAction = ReturnType<typeof setToken>
|
||||
|
||||
export const setWorldRelatedInformation = createAction<{ contentServerUrl: string; name: string }>('Set World Related Information')
|
||||
export type SetWorldRelatedInformationAction = ReturnType<typeof setWorldRelatedInformation>
|
||||
|
||||
@ -1,14 +1,22 @@
|
||||
import { createReducer } from '@reduxjs/toolkit'
|
||||
import { setServer, setToken } from './action'
|
||||
import { setServer, setToken, setWorldRelatedInformation } from './action'
|
||||
|
||||
export type ConferenceState = {
|
||||
token: string
|
||||
server: string
|
||||
worlds: {
|
||||
contentServerUrl: string
|
||||
name: string
|
||||
}
|
||||
}
|
||||
|
||||
export const INITIAL_STATE: ConferenceState = {
|
||||
token: '',
|
||||
server: ''
|
||||
server: '',
|
||||
worlds: {
|
||||
contentServerUrl: '',
|
||||
name: ''
|
||||
}
|
||||
}
|
||||
|
||||
export const conferenceReducer = createReducer<ConferenceState>(INITIAL_STATE, builder =>
|
||||
@ -19,4 +27,12 @@ export const conferenceReducer = createReducer<ConferenceState>(INITIAL_STATE, b
|
||||
.addCase(setToken, (state, action) => {
|
||||
state.token = action.payload.token
|
||||
})
|
||||
.addCase(setWorldRelatedInformation, (state, action) => {
|
||||
const { contentServerUrl, name } = action.payload
|
||||
|
||||
state.worlds = {
|
||||
contentServerUrl,
|
||||
name
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
@ -6,4 +6,8 @@ const getState = (state: RootState) => state.conference
|
||||
export const getToken = (state: RootState) => getState(state).token
|
||||
export const getServer = (state: RootState) => getState(state).server
|
||||
|
||||
const getWorlds = (state: RootState) => getState(state).worlds
|
||||
export const getWorldName = (state: RootState) => getWorlds(state).name
|
||||
export const getWorldContentServerUrl = (state: RootState) => getWorlds(state).contentServerUrl
|
||||
|
||||
export const isLoading = createSelector([getToken], token => !!token)
|
||||
|
||||
3
src/modules/config/env/dev.json
vendored
3
src/modules/config/env/dev.json
vendored
@ -1,5 +1,6 @@
|
||||
{
|
||||
"CHAIN_ID": "11155111",
|
||||
"PEER_URL": "https://peer.decentraland.zone",
|
||||
"WORLDS_CONTENT_SERVER_URL": "https://worlds-content-server.decentraland.zone"
|
||||
"WORLDS_CONTENT_SERVER_URL": "https://worlds-content-server.decentraland.zone",
|
||||
"SEGMENT_API_KEY": "XiymjAbQV5OJZXaT1doKqHgvgoUGHprP"
|
||||
}
|
||||
|
||||
3
src/modules/config/env/prod.json
vendored
3
src/modules/config/env/prod.json
vendored
@ -2,5 +2,6 @@
|
||||
"CHAIN_ID": "1",
|
||||
"EXPLORER_URL": "https://play.decentraland.org",
|
||||
"PEER_URL": "https://peer.decentraland.org",
|
||||
"WORLDS_CONTENT_SERVER_URL": "https://worlds-content-server.decentraland.org"
|
||||
"WORLDS_CONTENT_SERVER_URL": "https://worlds-content-server.decentraland.org",
|
||||
"SEGMENT_API_KEY": "FLpPrzJ8NlZLoRDldyQz4VdRnFGtxCAb"
|
||||
}
|
||||
|
||||
3
src/modules/config/env/stg.json
vendored
3
src/modules/config/env/stg.json
vendored
@ -1,5 +1,6 @@
|
||||
{
|
||||
"CHAIN_ID": "1",
|
||||
"PEER_URL": "https://peer.decentraland.org",
|
||||
"WORLDS_CONTENT_SERVER_URL": "https://worlds-content-server.decentraland.org"
|
||||
"WORLDS_CONTENT_SERVER_URL": "https://worlds-content-server.decentraland.org",
|
||||
"SEGMENT_API_KEY": "FLpPrzJ8NlZLoRDldyQz4VdRnFGtxCAb"
|
||||
}
|
||||
|
||||
@ -21,12 +21,6 @@
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
".eslintrc.js",
|
||||
"vite.config.ts",
|
||||
"jest.config.ts",
|
||||
"scripts"
|
||||
],
|
||||
"include": ["src", ".eslintrc.js", "vite.config.ts", "jest.config.ts", "scripts"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user