<:Head> <title>Add an Instance</title> </:Head> <Layout page='settings'> <SettingsLayout page='settings/instances/add' label="Add an Instance"> <h1 id="add-an-instance-h1">Add an Instance</h1> <LoadingMask show="{{loading}}"/> {{#if $isUserLoggedIn}} <p>Connect to an instance to log in.</p> {{else}} <p>Log in to an instance to start using Pinafore.</p> {{/if}} <form class="add-new-instance" on:submit='onSubmit(event)' aria-labelledby="add-an-instance-h1"> <label for="instanceInput">Instance:</label> <input class="new-instance-input" type="text" id="instanceInput" bind:value='$instanceNameInSearch' placeholder='' required > <button class="primary" type="submit" id="submitButton" disabled="{{!$instanceNameInSearch}}">Add instance</button> {{#if error}} <div class="form-error" role="alert"> Error: {{error}} </div> {{/if}} </form> {{#if !$isUserLoggedIn}} <p>Don't have an instance? <a rel="noopener" target="_blank" href="https://joinmastodon.org">Join Mastodon!</a></p> {{/if}} </SettingsLayout> </Layout> <style> .form-error { border: 2px solid red; border-radius: 2px; padding: 10px; font-size: 1.3em; margin: 5px; background-color: var(--main-bg); } input.new-instance-input { min-width: 50%; max-width: 100%; } form.add-new-instance { background: var(--form-bg); padding: 5px 10px 15px; margin: 20px auto; border: 1px solid var(--form-border); border-radius: 4px; } form.add-new-instance label, form.add-new-instance input, form.add-new-instance button { display: block; margin: 20px 5px; } @media (max-width: 767px) { input.new-instance-input { max-width: 80%; } } </style> <script> import Layout from '../../_components/Layout.html'; import SettingsLayout from '../_components/SettingsLayout.html' import { registerApplication, generateAuthLink, getAccessTokenFromAuthCode } from '../../_utils/mastodon/oauth' import { getVerifyCredentials } from '../../_utils/mastodon/user' import { getInstanceInfo } from '../../_utils/mastodon/instance' import { store } from '../../_utils/store' import { goto } from 'sapper/runtime.js' import { switchToTheme } from '../../_utils/themeEngine' import LoadingMask from '../../_components/LoadingMask' import { database } from '../../_utils/database/database' const REDIRECT_URI = (typeof location !== 'undefined' ? location.origin : 'https://pinafore.social') + '/settings/instances/add' export default { async oncreate () { let params = new URLSearchParams(location.search) if (params.has('code')) { this.onReceivedOauthCode(params.get('code')) } this.store.observe('instanceNameInSearch', () => { this.set({error: false}) }) }, components: { Layout, SettingsLayout, LoadingMask }, store: () => store, methods: { onSubmit: async function(event) { event.preventDefault() this.set({loading: true}) try { await this.redirectToOauth() } catch (err) { console.error(err) let error = `${err.message || err.name}. ` + (navigator.onLine ? `Is this a valid Mastodon instance?` : `Are you offline?`) this.set({error: error}) } finally { this.set({loading: false}) } }, redirectToOauth: async function() { let instanceName = this.store.get('instanceNameInSearch') let loggedInInstances = this.store.get('loggedInInstances') instanceName = instanceName.replace(/^https?:\/\//, '').replace('/$', '') if (Object.keys(loggedInInstances).includes(instanceName)) { this.set({error: `You've already logged in to ${instanceName}`}) return } let registrationPromise = registerApplication(instanceName, REDIRECT_URI) let instanceInfo = await getInstanceInfo(instanceName) await database.setInstanceInfo(instanceName, instanceInfo) // cache for later let instanceData = await registrationPromise this.store.set({ currentRegisteredInstanceName: instanceName, currentRegisteredInstance: instanceData }) this.store.save() let oauthUrl = generateAuthLink( instanceName, instanceData.client_id, REDIRECT_URI ) document.location.href = oauthUrl }, onReceivedOauthCode: async function(code) { try { this.set({loading: true}) await this.registerNewInstance(code) } catch (err) { this.set({error: `${err.message || err.name}. Failed to connect to instance.`}) } finally { this.set({loading: false}) } }, registerNewInstance: async function (code) { let currentRegisteredInstanceName = this.store.get('currentRegisteredInstanceName') let currentRegisteredInstance = this.store.get('currentRegisteredInstance') let instanceData = await getAccessTokenFromAuthCode( currentRegisteredInstanceName, currentRegisteredInstance.client_id, currentRegisteredInstance.client_secret, code, REDIRECT_URI ) let loggedInInstances = this.store.get('loggedInInstances') let loggedInInstancesInOrder = this.store.get('loggedInInstancesInOrder') let instanceThemes = this.store.get('instanceThemes') instanceThemes[currentRegisteredInstanceName] = 'default' loggedInInstances[currentRegisteredInstanceName] = instanceData if (!loggedInInstancesInOrder.includes(currentRegisteredInstanceName)) { loggedInInstancesInOrder.push(currentRegisteredInstanceName) } this.store.set({ instanceNameInSearch: '', currentRegisteredInstanceName: null, currentRegisteredInstance: null, loggedInInstances: loggedInInstances, currentInstance: currentRegisteredInstanceName, loggedInInstancesInOrder: loggedInInstancesInOrder, instanceThemes: instanceThemes }) this.store.save() switchToTheme('default') // fire off request for account so it's cached getVerifyCredentials(currentRegisteredInstanceName, instanceData.access_token).then(verifyCredentials => { database.setInstanceVerifyCredentials(currentRegisteredInstanceName, verifyCredentials) }) goto('/') } } } </script>