import htmlTemplate from './ImmersHUD.html'
import styles from './ImmersHUD.css'
import { ImmersClient } from '../client'
import { roles } from '../authUtils'
/**
* Web Component heads-up display for Immers profile login.
* Unobtrusively connects your Immersive Web experience to Immers Space,
* allowing immersers to connect with their profiles from your site
* and share your site with their friends. Grants access to profile information
* so you can bring users' preferred identity into your experience.
*
* The HTML attributes are listed in the Properties table below.
* Properties you can access from the element object directly are listed under Members.
*
* The following CSS properties can be set on the immers-hud:
*
* color: text color for floating text
*
* --main-margin: distance from edge of window in overlay mode
*
* --inner-margin: gap between elements
*
* --handle-input-width: size of the handle input
*
* @class ImmersHUD
*
* @fires immers-hud-connected - On successful login, detail.profile will include users {@link Profile}
*
* @prop {'top-left'|'top-right'|'bottom-left'|'bottom-right'} [position] - Enable overlay positioning.
* @prop {string} [token-catcher] - OAuth redirect URL, a page on your domain that runs {@link catchToken} on load (default: current url)
* @prop {string} [access-role] - Requested authorization scope from {@link roles}. Users are given the option to alter this and grant a different level. (default: modAdditive)
* @prop {string} [destination-name] Title for your experience (default: meta[og:description], document.title)
* @prop {string} [destination-url] Sharable URL for your experience (default: current url)
* @prop {string} [destination-description] Social share preview test (default meta[og:description], meta[twitter:description])
* @prop {string} [destination-image] Image url for social share previews (default: meta[og:image], meta[twitter:image])
* @prop {string} [local-immer] Origin of your local Immers Server, if you have one
* @prop {boolean} [allow-storage] Enable local storage of user identity to reconnect when returning to page
* @prop {'true'|'false'} open - Toggles between icon and full HUD view (default: true is user's handle is saved but login is needed, false otherwise)
*
* @example <caption>Load & register the custom element via import (option 1)</caption>
* import { ImmersHUD } from 'immers-client';
* ImmersHUD.Register();
* @example <caption>Load & register the custom element via CDN (option 2)</caption>
* <script type="module" src="https://cdn.jsdelivr.net/npm/immers-client/dist/ImmersHUD.bundle.js"></script>
* @example <caption>Using the custom element in HTML</caption>
* <immers-hud position="bottom-left" access-role="friends"
* destination-name="My Immer" destination-url="https://myimmer.com/"
* token-catcher="https://myimmer.com/"></immers-hud>
*
*/
export class ImmersHUD extends window.HTMLElement {
#queryCache = {}
#container
/**
* Live-updated friends list with current status
* @type {FriendStatus[]}
*/
friends = []
/**
* Immers client instance
* @type {ImmersClient}
*/
immersClient
constructor () {
super()
this.attachShadow({ mode: 'open' })
const styleTag = document.createElement('style')
styleTag.innerHTML = styles
const template = document.createElement('template')
template.innerHTML = htmlTemplate.trim()
this.shadowRoot.append(styleTag, template.content.cloneNode(true))
this.#container = this.shadowRoot.lastElementChild
}
connectedCallback () {
if (this.immersClient) {
// already initialized
return
}
// Immers client setup
const destination = {
name: this.getAttribute('destination-name') || this.#meta('og:title') || document.title,
url: this.getAttribute('destination-url') || window.location.href
}
const description = this.getAttribute('destination-description') || this.#meta('og:description') || this.#meta('twitter:description')
if (description) {
destination.description = description
}
const image = this.getAttribute('destination-image') || this.#meta('og:image') || this.#meta('twitter:image')
if (image) {
destination.previewImage = image
}
this.immersClient = new ImmersClient(destination, {
localImmer: this.getAttribute('local-immer'),
allowStorage: this.hasAttribute('allow-storage')
})
this.immersClient.addEventListener(
'immers-client-friends-update',
({ detail: { friends } }) => this.onFriendsUpdate(friends)
)
this.immersClient.addEventListener(
'immers-client-connected',
({ detail: { profile } }) => this.onClientConnected(profile)
)
this.immersClient.addEventListener(
'immers-client-disconnected',
() => this.onClientDisconnected()
)
this.#container.addEventListener('click', evt => {
switch (evt.target.id) {
case 'login':
evt.preventDefault()
this.login()
break
case 'logo':
this.setAttribute('open', this.getAttribute('open') !== 'true')
break
case 'exit-button':
// TODO: add confirmation modal
this.remove()
break
case 'logout':
this.immersClient.logout()
break
}
})
if (this.immersClient.handle) {
this.#el('handle-input').value = this.immersClient.handle
this.immersClient.reconnect().then(connected => {
if (!connected) {
// user has logged in before, but action required to reconnect
// prompt with open, pre-filled login
this.setAttribute('open', true)
}
})
}
}
attributeChangedCallback (name, oldValue, newValue) {
switch (name) {
case 'position':
if (newValue && !ImmersHUD.POSITION_OPTIONS.includes(newValue)) {
console.warn(`immers-hud: unknown position ${newValue}. Valid options are ${ImmersHUD.POSITION_OPTIONS.join(', ')}`)
}
break
case 'open':
this.#el('notification').classList.add('hidden')
break
}
}
login () {
this.immersClient.login(
this.getAttribute('token-catcher') || window.location.href,
this.getAttribute('access-role') || roles[2],
this.#el('handle-input').value
).then(() => this.immersClient.enter())
}
onClientConnected (profile) {
this.#el('login-container').classList.add('removed')
this.#el('status-container').classList.remove('removed')
// show profile info
if (profile.avatarImage) {
this.#el('logo').style.backgroundImage = `url(${profile.avatarImage})`
}
this.#el('username').textContent = profile.displayName
this.#el('profile-link').setAttribute('href', profile.url)
this.#emit('immers-hud-connected', { profile })
}
onClientDisconnected () {
this.#el('login-container').classList.remove('removed')
this.#el('status-container').classList.add('removed')
this.#el('handle-input').value = ''
this.#el('logo').style.backgroundImage = ''
this.#el('username').textContent = ''
this.#el('profile-link').setAttribute('href', '#')
}
onFriendsUpdate (friends) {
this.friends = friends
if (this.getAttribute('open') !== 'true') {
this.#el('notification').classList.remove('hidden')
}
this.#el('status-message').textContent = `${friends.filter(f => f.isOnline).length}/${friends.length} friends online`
}
#el (id) {
return this.#queryCache[id] ?? (this.#queryCache[id] = this.#container.querySelector(`#${id}`))
}
#emit (type, data) {
this.dispatchEvent(new window.CustomEvent(type, {
detail: data
}))
}
#meta (name) {
const attr = name.startsWith('og:') ? 'property' : 'name'
return document.querySelector(`meta[${attr}="${name}"]`)?.getAttribute('content')
}
static get observedAttributes () {
return ['position', 'open']
}
static get POSITION_OPTIONS () {
return ['top-left', 'bottom-left', 'top-right', 'bottom-right']
}
static Register () {
window.customElements.define('immers-hud', ImmersHUD)
}
}
Source