0.0.1 • Published 4 years ago

xr3-client v0.0.1

Weekly downloads
2
License
ISC
Repository
github
Last release
4 years ago

Build Status

XRChat Client

About

XRChat is an end-to-end solution for hosting humans and non-humans in a virtual space. This project would literally not be possible without the community contributions of Mozilla Hubs, Janus VR, Avaer + Exokit, Mr Doob, Hayden James Lee and many others.

Our goal is an easy-to-use, well documented, end-to-end Javascript (or Typescript) exprience that anyone with a little bit of Javascript and HTML knowledge can dig into, deploy and make meaningful modifications and contributions to. If you fit this category and you are struggling with any aspect of getting started, we want to hear fromm you so that this can be a better exprience.

XRChat is a free, open source, MIT-licensed project. You are welcome to do anything you want with it. We hope that you use it to make something beautiful.

This is the client portion of XRChat. To deploy everything at once with Kubernetes or Docker Compose, check out the branches in xrchat-ops. Or you can start the server with NPM (check out scripts/start-db.sh to get the database runnning), run the xrchat-client client and everything should connect out of the box.

Getting Started

To run

npm install
npm run build
npm run dev

config file

xr

networked-scene

properties for the NAF networked-scene component

  • app unique app name (no spaces)
  • room name of initial room
  • audio set true to enable audio streaming (if using an adapter that supports it)
  • adapter which network service to use
  • serverURL where the WebSocket / signalling server is located

avatar

  • defaultAvatarModelSrc gltf for the default avatar
  • defaultAvatarModelScale scale for the default avatar

environment

floor
  • src source for the floor texture
  • height height(length) of the floor
  • width width of the floor
skybox
  • src source for the skybox texture
  • height height(length) of the skybox texture
  • width width of the skybox texture
  • radius radius of the skybox
  • rotation x,y,z rotation of the skybox
  • thetaLength skybox's vertical sweep angle size in degrees

Docker

You can run it using docker, if you don't have node installed or need to test.

# Build the image
docker build --tag xrchat-client .

# Run the image (deletes itself when you close it)
docker run -d --rm --name client -e "NEXT_PUBLIC_API_SERVER=http://localhost:3030" -p "3000:3000"  xrchat-client

# Stop the server
docker stop client

Docker image configurations

This image uses build-time arguments, they are not used during runtime yet

Redux store management

We are putting Redux actions into redux folder.

There are following files.

actions.ts : In this file, we are defining all actions which is using this app.

feathers.ts : In this file, we defined the feathers app. You don't need to change this file. You can set the featherStoreKey from /config/server.ts for the storage key of the feathers.

persisted.store.ts : In this file, we synchronize store varialble with local storage. You don't need to change this file. You can set localStorageKey from /config/server.ts for the redux store key.

reducers : In this file, we are defining all reducers. Once you add a new reducer, please add that under the line auth: authReducer. If you don't add the reducer into this, you can't dispatch actions you added.

service.common.ts : In this file, we are defining utility functions which are commonly used.

store.ts : In this file, we defined Redux store.

auth/actions.ts : In this file, we defined authentication actions.

auth/reducers.ts : In this file, we defined AuthState and action process routines.

auth/services.ts : In this file, we defined service function for authorization.

auth/selector.ts : In this file, we defined state selector for AuthState.

How to use Redux.

import { connect } from 'react-redux'
import { bindActionCreators, Dispatch } from 'redux'
import { 
  loginUserByPassword,
} from '../../../redux/auth/service'
import { selectAuthState } from '../../../redux/auth/selector'

...

interface Props {
  auth: any,
  loginUserByPassword: typeof loginUserByPassword
}

const mapStateToProps = (state: any) => {
  return {
    auth: selectAuthState(state),   // fetch authState from store.
                                    // Note: `state` and `auth` is Immutable variable.
  }
}

const mapDispatchToProps = (dispatch: Dispatch) => ({
  loginUserByPassword: bindActionCreators(loginUserByPassword, dispatch)  // Mapping service to props
})

...

class Login extends React.Component<LoginProps> {
...
    handleEmailLogin = (e: any) => {
        // call service which is mapping with redux.
        this.props.loginUserByPassword({
            email: this.state.email,
            password: this.state.password
        })
  }
...
}

...

// we should connect Redux and Component like as following.
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Login)

Tutorial for Redux store.

Let's explain step by step about the login process. 1. We should declare LOGIN_USER_SUCCESS and LOGIN_USER_ERROR actions. Add following lines in /redux/actions.ts.

```
export const LOGIN_USER_SUCCESS = 'LOGIN_USER_SUCCESS'
export const LOGIN_USER_ERROR = 'LOGIN_USER_ERROR'
```
  1. We should define actions && reducers && services.

    • Create the auth folder in /redux folder.
    • Add the actions.ts file in /redux/auth folder.
    • Add the following codes in actions.ts file.
    import {
        LOGIN_USER_BY_GITHUB_ERROR,
        LOGIN_USER_BY_GITHUB_SUCCESS
    } from '../actions'
    
    export function loginUserSuccess(user: any): any {
        return {
            type: LOGIN_USER_SUCCESS,
            user,
            message: ''
        }
    }
    
    export function loginUserError(err: string): any {
        return {
            type: LOGIN_USER_ERROR,
            message: err
        }
    }
    • Add the reducers.ts file in /redux/auth folder.
    • Add the following codes in reducers.ts
    import Immutable from 'immutable'
    import { 
        LOGIN_USER_SUCCESS,
        LOGIN_USER_ERROR
    } from "../actions"
    
    export const initialState: AuthState = {
        isLoggedIn: false,
        user: undefined,
        error: '',
    
        isProcessing: false
    }
    
    // Note: In next.js, we should use Immutable variable for state type.
    const immutableState = Immutable.fromJS(initialState)
    
    const authReducer = (state = immutableState, action: any): any => {
        switch(action.type) {
            case LOGIN_USER_SUCCESS:
                return state
                    .set('isLoggedIn', true)
                    .set('user', (action as LoginResultAction).user)
            case LOGIN_USER_ERROR:
                return state
                    .set('error', (action as LoginResultAction).message)
        }
    
        return state
    }
    
    export default authReducer
    • Add the selector.ts file in /redux/auth folder.
    import { createSelector } from 'reselect'
    
    // Note: 'auth' is the state key so you can change this whatever you want.
    const selectState = (state: any) => state.get('auth')
    export const selectAuthState = createSelector([selectState], (auth) => auth)
    • Add the service.ts file in /redux/auth folder.
    ...
    
    export function loginUserByPassword(form: any) {
        return (dispatch: Dispatch) => {
            dispatch(actionProcessing(true))
    
            client.authenticate({
                strategy: 'local',
                email: form.email,
                password: form.password
            })
            .then((res: any) => {
            const val = res as AuthUser
            if (!val.user.isVerified) {
                client.logout()
                return dispatch(loginUserError('Unverified user'))
            }
            return dispatch(loginUserSuccess(val))
            })
            .catch(() => dispatch(loginUserError('Failed to login')))
            .finally( ()=> dispatch(actionProcessing(false)))
        }
    }
    • Add the following codes in /redux/reducers.ts file to combine reducers.
    import { combineReducers } from 'redux-immutable'
    import authReducer from './auth/reducers'
    
    export default combineReducers({
        auth: authReducer
    })
  2. How to use the Redux store and services in Component. Please refer How to use Redux section.

How to access detected device and if WebXR is supported:

import { connect } from 'react-redux'
import { detectDeviceType } from '../../../redux/devicedetect/service'
import { bindActionCreators, Dispatch } from 'redux'
import { selectDeviceDetectState } from '../../../redux/devicedetect/selector'

...

interface Props {
  deviceInfo: any
}

const mapStateToProps = (state: any) => {
  return {
    deviceInfo: selectDeviceDetectState(state),
  }
}

...

class Login extends React.Component<Props> {
...
    //this.props.deviceInfo.get('isDetected') -> it will tell us that whether the device is detected or not.
    //this.props.deviceInfo.get('isDetected') -> returns true always because it is dispatched in _app.tsx
    //this.props.deviceInfo.get('content') -> it will return the entire object with the following content in it
    <!-- {
        WebXRSupported: true,
        device: {
            "client": {
                "type": "browser",
                "name": "Chrome",
                "version": "69.0",
                "engine": "Blink",
                "engineVersion": ""
            },
            "os": {
                "name": "Mac",
                "version": "10.13",
                "platform": ""
            },
            "device": {
                "type": "desktop",
                "brand": "Apple",
                "model": ""
            },
            "bot": null
        }
    } -->
    

...
}

...

// we should connect Redux and Component like as following.
export default connect(
  mapStateToProps
)(Login)

//No need to dispatch, as it is already being dispatched from _app.tsx
//If needed, just in case, then dispatch the 'detectDeviceType' service that is imported above by using mapDispatchToProps, bindActionCreator and Dispatch
//example
<!-- const mapDispatchToProps = (dispatch: Dispatch) => ({
  detectDeviceType: bindActionCreators(detectDeviceType(arg), dispatch)
}) -->
//detectDeviceType(arg) takes an argument of type 'any' which is object containing device information
//Don't forget to change the Props interface
<!-- interface Props {
  deviceInfo: any
  detectDeviceType: typeof detectDeviceType
} -->
//lastly add mapDispatchToProps in the connect argument.