185 lines
		
	
	
		
			No EOL
		
	
	
		
			6.1 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			185 lines
		
	
	
		
			No EOL
		
	
	
		
			6.1 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| <: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 { getThisUserAccount } from '../../_utils/mastodon/user'
 | |
|   import { store } from '../../_utils/store'
 | |
|   import { goto } from 'sapper/runtime.js'
 | |
|   import { switchToTheme } from '../../_utils/themeEngine'
 | |
|   import LoadingMask from '../../_components/LoadingMask'
 | |
| 
 | |
|   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) {
 | |
|           if (process.env.NODE_ENV !== 'production') {
 | |
|             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 instanceData = await registerApplication(instanceName, REDIRECT_URI)
 | |
|         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 in the SW
 | |
|         getThisUserAccount(currentRegisteredInstanceName, instanceData.access_token)
 | |
|         goto('/')
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| </script> |