Skip to content

Commit

Permalink
Add ws utilities, add ws support to prompts
Browse files Browse the repository at this point in the history
  • Loading branch information
darh committed May 13, 2021
1 parent 20d4775 commit 6eb27f7
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 107 deletions.
62 changes: 35 additions & 27 deletions src/components/prompts/CPromptToast.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
:visible="!isActive"
solid
:no-auto-hide="!passive"
:auto-hide-delay="pVal(prompt, 'timeout', 7) * 1000"
:auto-hide-delay="pVal(prompt, 'timeout', defaultTimeout) * 1000"
:no-close-button="!passive"
>
<template #toast-title>
Expand All @@ -17,10 +17,10 @@
<b-button
variant="link"
size="sm"
v-if="!passive && waiting.length > 1"
v-if="!passive && active.length > 1"
@click="activate(true)"
>
{{ waiting.length }} waiting
{{ active.length }} waiting
</b-button>
</div>
</template>
Expand All @@ -44,7 +44,7 @@ export default {
data () {
return {
toasts: []
passive: new Set(),
}
},
Expand All @@ -68,24 +68,28 @@ export default {
},
/**
* Display all toasts that can be displayed:
* - show only prompts with components
* - show passive components first
* - show only one non-passive component (at the end
* All non-passive prompts with components
*/
newToasts () {
const pp = this.withComponents.filter(({ passive }) => passive)
const nonPassive = this.withComponents.find(({ passive }) => !passive)
if (!!nonPassive) {
pp.unshift(nonPassive)
}
return pp
active() {
return this.withComponents.filter(({ passive }) => !passive)
},
waiting () {
return this.withComponents.filter(({ passive }) => !passive)
/**
* Returns list of prompts that we can interpret as toasts: display component is defined
*
* Toasts (prompts with components) are displayed in order received but
* passive (no feedback or input from user required) first and the rest later
*/
toasts () {
return [
...this.passive.values(),
...this.active
]
},
defaultTimeout () {
return 7
}
},
watch: {
Expand All @@ -100,18 +104,22 @@ export default {
}
},
newToasts: {
/**
* Make a copy of prompt if it is defined as passive
*
* We do this because we do not want it to be removed right away
* but through a toast component's timeout
*/
prompts: {
immediate: true,
handler (newToasts = []) {
// Add prompts with unique stateIDs to toasts
const stateIDs = new Set([...this.toasts.map(({ prompt }) => prompt.stateID)])
newToasts.forEach(toast => {
if (!stateIDs.has(toast.prompt.stateID)) {
this.toasts.push(toast)
handler () {
this.withComponents.forEach(p => {
if (p.passive) {
this.passive.add(p)
}
})
}
}
},
},
},
methods: {
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as corredor from './corredor'
import * as filters from './filters'
import * as store from './store'
import * as url from './libs/url'
import * as websocket from './libs/websocket'

export {
plugins,
Expand All @@ -14,4 +15,5 @@ export {
filters,
store,
url,
websocket,
}
79 changes: 79 additions & 0 deletions src/libs/websocket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/* eslint-disable @typescript-eslint/ban-ts-ignore */
/**
* Default websocket cofngiuration
*/
import { Vue } from 'vue/types/vue'

export const config = {
format: 'json',

// (Boolean) whether to reconnect automatically (false)
reconnection: true,

// (Number) number of reconnection attempts before giving up (Infinity),
reconnectionAttempts: 5,

// (Number) how long to initially wait before attempting a new (1000)
reconnectionDelay: 3000,

connectManually: false,
}

/**
* Extract websocket endpoint from window props (set via config.js)
*/
export function endpoint (): string {
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
let { CortezaAPI, CortezaWebsocket, location } = window

if (!CortezaWebsocket) {
// Corteza websocket entrypoint not set, use API and append /websocket
CortezaWebsocket = `${CortezaAPI}/websocket`
}

let proto: string
if (CortezaWebsocket.startsWith('//')) {
// No proto in the configured API endpoint, use location
proto = location.protocol
} else {
const sep = '://';
[proto] = (CortezaWebsocket as string).split(sep, 1)
CortezaWebsocket = CortezaWebsocket.substring(proto.length + sep.length)
}

return `${proto === 'https' ? 'wss' : 'ws'}://${CortezaWebsocket}`
}

/**
* Binds auth and websocket events so that we can pass current access token
* - when ws connection opens
* - when auth token in fetched/renewed
*
* @todo get rid of ts-ignore lines
*/
export function init (vue: Vue): void {
// @ts-ignore
if (!vue.$socket || !vue.$options) {
// (web)socket plugin not ready.
return
}

const wsAuth = (accessToken?: string): void => {
if (accessToken && accessToken.length > 0) {
// @ts-ignore
vue.$socket.sendObj({ '@type': 'credentials', '@value': { accessToken } })
}
}

// update connection with new access token
// @ts-ignore
vue.$on('auth-token-processed', ({ accessToken }) => wsAuth(accessToken))

// make sure that we send auth token as soon as we're connected
// @ts-ignore
vue.$options.sockets.onopen = (): void => wsAuth(vue.$auth.accessTokenFn())

// @ts-ignore
vue.$options.sockets.onmessage = (msg): void => vue.$emit('websocket-message', msg)
}
18 changes: 18 additions & 0 deletions src/plugins/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ export class Auth {
*/
private refreshTimeout?: number

private $emit?: (event: string, ...args: unknown[]) => unknown

constructor ({ app, verbose, cortezaAuthURL, callbackURL, entrypointURL, location, localStorage, refreshFactor }: AuthCtor) {
if (refreshFactor >= 1 || refreshFactor <= 0) {
throw new Error('refreshFactor should be between 0 and 1')
Expand All @@ -148,6 +150,11 @@ export class Auth {
})
}

vue (vue: Vue): Auth {
this.$emit = (event, ...args): void => { vue.$emit(event, ...args) }
return this
}

get axios (): AxiosInstance {
return axios.create({ baseURL: this.cortezaAuthURL })
}
Expand Down Expand Up @@ -414,6 +421,13 @@ export class Auth {
this[accessToken] = oa2tkn.access_token
this[user] = u

if (this.$emit) {
this.$emit('auth-token-processed', {
user: u,
accessToken: this[accessToken],
})
}

return {
accessTokenFn: (): string | undefined => { return this[accessToken] },
user: u,
Expand Down Expand Up @@ -551,6 +565,10 @@ export default function (): PluginFunction<PluginOpts> {
!!window.localStorage.getItem('auth.verbose')
}

// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
console.log(this)

Vue.prototype.$auth = new Auth({
app,
verbose,
Expand Down
69 changes: 30 additions & 39 deletions src/plugins/reminder.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@
import moment from 'moment'
import { system } from '@cortezaproject/corteza-js'

async function sleep (t) {
return new Promise(resolve => setTimeout(resolve, t))
}

function intervalToMS (from, to) {
if (!from || !to) {
throw new Error('intervalToMS.invalidArgs')
Expand All @@ -15,68 +11,63 @@ function intervalToMS (from, to) {
}

export class ReminderService {
constructor ({ api, poolInterval = 1000 * 60 * 5, resource = null, emitter } = {}) {
constructor ({ api, fetchOffset = 1000 * 60 * 5, resource = null, emitter } = {}) {
if (!api) {
throw new Error('reminderService.invalidParams')
}

this.emitter = emitter
this.api = api
this.poolInterval = poolInterval
this.running = false
this.fetchOffset = fetchOffset
this.resource = resource

this.set = []
this.nextRemindAt = null
this.tHandle = null
}

init ({ emitter, filter = {} }) {
init (emitter, { filter = {} }) {
if (emitter) {
this.emitter = emitter
}
this.filter = filter
if (!this.emitter) {
throw new Error('pool.noEmitter')
}
this.pool()
}

stop () {
this.running = false
if (this.tHandle) {
window.clearTimeout(this.tHandle)
this.tHandle = null
this.filter = {
scheduledOnly: true,
excludeDismissed: true,
...filter,
}

this.prefetch().then(rr => {
this.enqueue(...rr)
this.emitter.$emit('reminders.pull')
})
}

/**
* Pools for reminders & schedules them
* Fetches all reminders that are supposed to go off to date (time)
*
* @returns {Promise<system.Reminder>}
*/
async pool () {
this.running = true

while (this.running) {
const { set = [] } = await this.api.reminderList({
limit: 0,
resource: this.resource,
toTime: moment().add(this.poolInterval, 'min').toISOString(),
...this.filter,
}).catch(() => ({ set: [] }))

this.enqueue(set.map(r => new system.Reminder(r)))
await sleep(this.poolInterval)
if (this.emitter) {
this.emitter.$emit('reminders.pull')
}
}
async prefetch () {
return this.api.reminderList({
limit: 0,
resource: this.resource,
toTime: moment().add(this.fetchOffset, 'min').toISOString(),
...this.filter,
}).then(({ set }) => {
return set.map(r => new system.Reminder(r))
})
}

enqueueRaw (raw) {
this.enqueue(new system.Reminder(raw))
}

/**
* Enqueue a given set of reminders
* @param {<Reminder>Array} set Set of reminderIDs to enqueue
* @param {Array<Reminder>} set Set of reminderIDs to enqueue
*/
enqueue (set = []) {
enqueue (...set) {
set.forEach(r => {
// New or replace
const i = this.set.findIndex(({ reminderID }) => reminderID === r.reminderID)
Expand Down
Loading

0 comments on commit 6eb27f7

Please sign in to comment.