Mercurial > xmpp-account-manager
view avatar.js @ 63:ee1df80a1715 default tip
Nicer-looking input form
author | mathieui |
---|---|
date | Sun, 24 May 2020 14:19:29 +0200 |
parents | 6d861d881b96 |
children |
line wrap: on
line source
// SPDX-License-Identifier: AGPL-3.0-only /* * Copyright © 2018-2020 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> * Copyright © 2020 Mathieu Pasquet <mathieui@mathieui.net> * * Released under GNU AGPL v3 only, read the file 'LICENSE' for more information. */ 'use strict'; function initAvatar(connection) { const DEFAULT_AVATAR = 'data:image/svg+xml,' + encodeURIComponent('<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 150 150"><rect width="150" height="150" fill="#888" stroke-width="1" stroke="#000"/><text x="75" y="100" text-anchor="middle" font-size="100">?</text></svg>'); const avatar_data = {}; const avatar_img = document.getElementById('avatar'); const avatar_size = document.getElementById('avatar-size'); const avatar_file = document.getElementById('avatar-file'); const avatar_upload = document.getElementById('avatar-upload'); const avatar_change = document.getElementById('avatar-change'); const avatar_access = document.getElementById('avatar-access'); const spinner_img = document.getElementById('avatar-spinner'); const access_spinner_img = document.getElementById('access-model-avatar-spinner'); avatar_img.src = DEFAULT_AVATAR; const iq = $iq({type: 'get'}) .c('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'}) .c('items', {node: 'urn:xmpp:avatar:metadata'}); connection.sendIQ(iq, onAvatarMetadata, onAvatarRetrievalError.bind(null, 'PubSub metadata query failed.')); displaySpinner(spinner_img); getAccessModel(); function getAccessModel() { avatar_access.disabled = true; displaySpinner(access_spinner_img); retrieveConfiguration(connection, 'urn:xmpp:avatar:metadata').then((access_model) => { if (access_model !== null) { if (access_model === 'open') avatar_access.value = 'open'; else if (access_model === 'presence') avatar_access.value = 'presence'; else console.log('Unsupported avatar access model: ' + access_model); avatar_access.disabled = false; } hideSpinner(access_spinner_img); }, (reason) => { console.log('Failed to retrieve avatar configuration: ' + reason); hideSpinner(access_spinner_img); }); } function onAvatarMetadata(result_iq) { const item = parseXPath(result_iq, './pubsub:pubsub/pubsub:items/pubsub:item'); if (item == null) return onAvatarRetrievalError('no item found.'); const id = item.getAttributeNS(null, 'id'); const info = parseXPath(item, './avatar_metadata:metadata/avatar_metadata:info'); if (info == null) return onAvatarRetrievalError('no info found, your avatar metadata node is broken.'); if (id != info.getAttributeNS(null, 'id')) return onAvatarRetrievalError('invalid id in metadata.'); const parsed_info = { id: id, type: info.getAttributeNS(null, 'type'), bytes: info.getAttributeNS(null, 'bytes'), width: info.getAttributeNS(null, 'width'), height: info.getAttributeNS(null, 'height'), }; const iq = $iq({type: 'get'}) .c('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'}) .c('items', {node: 'urn:xmpp:avatar:data'}) .c('item', {id: id}); connection.sendIQ(iq, onAvatarData.bind(null, parsed_info), onAvatarRetrievalError.bind(null, 'PubSub data query failed.')); } function onAvatarData(info, result_iq) { const item = parseXPath(result_iq, './pubsub:pubsub/pubsub:items/pubsub:item'); if (item == null) return onAvatarRetrievalError('no item found.'); if (info.id != item.getAttributeNS(null, 'id')) return onAvatarRetrievalError('invalid id in data.'); const data = parseXPath(item, './avatar_data:data').textContent; const url = 'data:' + info.type + ';base64,' + data; // TODO: validate the bytes too. /* // TODO: figure out why this didn’t work. avatar_img.onload = function (evt) { const img = evt.target; if (img.naturalWidth != info.width || img.naturalHeight != info.height) return onAvatarRetrievalError('invalid width or height in image data.'); avatar_img.onload = null; }; */ avatar_img.src = url; hideSpinner(spinner_img); getAccessModel(); } function onAvatarRetrievalError(string) { console.log('Failed to retrieve avatar, an empty one is displayed instead: ' + string); avatar_img.src = DEFAULT_AVATAR; hideSpinner(spinner_img); avatar_access.disabled = true; } avatar_upload.addEventListener('click', function (evt) { avatar_file.click(); }); avatar_change.addEventListener('click', function (evt) { const metadata_iq = $iq({type: 'set'}) .c('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'}) .c('publish', {node: 'urn:xmpp:avatar:metadata'}) .c('item', {id: avatar_data.id}) .c('metadata', {xmlns: 'urn:xmpp:avatar:metadata'}) .c('info', { id: avatar_data.id, type: avatar_data.type, bytes: avatar_data.bytes, width: avatar_img.naturalWidth, height: avatar_img.naturalHeight, }); connection.sendIQ(metadata_iq, onAvatarMetadataUpload, onAvatarUploadError); displaySpinner(spinner_img); avatar_access.disabled = true; }); function onAvatarMetadataUpload(iq) { const data_iq = $iq({type: 'set'}) .c('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'}) .c('publish', {node: 'urn:xmpp:avatar:data'}) .c('item', {id: avatar_data.id}) .c('data', {xmlns: 'urn:xmpp:avatar:data'}) .t(avatar_data.data); connection.sendIQ(data_iq, onAvatarDataUpload, onAvatarUploadError); } function onAvatarDataUpload(iq) { console.log('Avatar successfully uploaded!', iq); avatar_change.hidden = true; avatar_size.innerHTML = ''; spinnerOk(spinner_img); getAccessModel(); } function onAvatarUploadError(iq) { console.log("onAvatarUploadError", iq); spinnerError(spinner_img); avatar_access.disabled = true; } avatar_file.addEventListener('change', function (evt) { const file = evt.target.files[0]; avatar_data.type = file.type; avatar_data.bytes = file.size; // Set the preview. avatar_img.src = URL.createObjectURL(file); const [size, unit] = friendlyDataSize(file.size); avatar_size.innerHTML = Math.round(size) + ' ' + unit; // Obtain the base64 version of this file. const base64_reader = new FileReader(); base64_reader.onload = function (evt) { const data = evt.target.result; avatar_data.data = data.substr(data.indexOf(',') + 1); } base64_reader.readAsDataURL(file); // Compute the sha1 of this file. const sha1_reader = new FileReader(); sha1_reader.onload = async function (evt) { const data = evt.target.result; const digest = await window.crypto.subtle.digest('SHA-1', data); const sha1 = (Array .from(new Uint8Array(digest)) .map(b => b.toString(16).padStart(2, "0")) .join("")); avatar_data.id = sha1; avatar_change.hidden = false; } sha1_reader.readAsArrayBuffer(file); }); function friendlyDataSize(bytes) { let unit = 'B' if (bytes >= 1024) { bytes /= 1024; unit = 'KiB'; } if (bytes >= 1024) { bytes /= 1024; unit = 'MiB'; } if (bytes >= 1024) { bytes /= 1024; unit = 'GiB'; } if (bytes >= 1024) { bytes /= 1024; unit = 'TiB'; } return [bytes, unit]; } avatar_access.addEventListener('change', function (evt) { const iq = configurePEPField('urn:xmpp:avatar:metadata', 'pubsub#access_model', evt.target.value); connection.sendIQ(iq, onAvatarMetadataConfigured, onAvatarConfigureError.bind(null, 'PubSub configuration of metadata failed.')); displaySpinner(access_spinner_img); }); function onAvatarMetadataConfigured(result_iq) { const iq = configurePEPField('urn:xmpp:avatar:data', 'pubsub#access_model', avatar_access.value); connection.sendIQ(iq, onAvatarConfigured, onAvatarConfigureError.bind(null, 'PubSub configuration of data failed.')); } function onAvatarConfigured(result_iq) { console.log('Successfully set avatar access model.') spinnerOk(access_spinner_img); } function onAvatarConfigureError(string) { console.log('Failed to configure avatar node: ' + string); spinnerError(access_spinner_img); } }