@data-fair/lib-vue 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/session.d.ts +12 -0
  3. package/session.js +53 -13
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@data-fair/lib-vue",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Composables and other utilities for Vue applications in the data-fair stack.",
5
5
  "main": "index.js",
6
6
  "files": [
package/session.d.ts CHANGED
@@ -17,6 +17,14 @@ export interface SessionOptions {
17
17
  req?: IncomingMessage;
18
18
  cookies?: GenericCookies;
19
19
  customFetch?: typeof fetch;
20
+ siteInfo?: boolean;
21
+ }
22
+ export interface Theme {
23
+ primaryColor: string;
24
+ }
25
+ export interface SiteInfo {
26
+ theme: Theme;
27
+ logo?: string;
20
28
  }
21
29
  export interface Session {
22
30
  state: SessionState;
@@ -26,6 +34,7 @@ export interface Session {
26
34
  accountRole: ComputedRef<SessionState['accountRole']>;
27
35
  lang: ComputedRef<SessionState['lang']>;
28
36
  dark: ComputedRef<SessionState['dark']>;
37
+ site: Ref<SiteInfo | null>;
29
38
  loginUrl: (redirect?: string, extraParams?: Record<string, string>, immediateRedirect?: true) => string;
30
39
  login: (redirect?: string, extraParams?: Record<string, string>, immediateRedirect?: true) => void;
31
40
  logout: (redirect?: string) => Promise<void>;
@@ -34,6 +43,7 @@ export interface Session {
34
43
  asAdmin: (user: any | null) => Promise<void>;
35
44
  cancelDeletion: () => Promise<void>;
36
45
  keepalive: () => Promise<void>;
46
+ refreshSiteInfo: () => Promise<void>;
37
47
  switchDark: (value: boolean) => void;
38
48
  switchLang: (value: string) => void;
39
49
  topLocation: Ref<Location | undefined>;
@@ -56,6 +66,7 @@ export declare function createSession(initOptions: Partial<SessionOptions>): Pro
56
66
  accountRole: ComputedRef<SessionState["accountRole"]>;
57
67
  lang: ComputedRef<SessionState["lang"]>;
58
68
  dark: ComputedRef<SessionState["dark"]>;
69
+ site: Ref<SiteInfo | null>;
59
70
  loginUrl: (redirect?: string, extraParams?: Record<string, string>, immediateRedirect?: true) => string;
60
71
  login: (redirect?: string, extraParams?: Record<string, string>, immediateRedirect?: true) => void;
61
72
  logout: (redirect?: string) => Promise<void>;
@@ -64,6 +75,7 @@ export declare function createSession(initOptions: Partial<SessionOptions>): Pro
64
75
  asAdmin: (user: any | null) => Promise<void>;
65
76
  cancelDeletion: () => Promise<void>;
66
77
  keepalive: () => Promise<void>;
78
+ refreshSiteInfo: () => Promise<void>;
67
79
  switchDark: (value: boolean) => void;
68
80
  switchLang: (value: string) => void;
69
81
  topLocation: Ref<Location | undefined>;
package/session.js CHANGED
@@ -1,4 +1,4 @@
1
- import { reactive, computed, watch, inject } from 'vue'
1
+ import { reactive, computed, watch, inject, ref } from 'vue'
2
2
  import { ofetch } from 'ofetch'
3
3
  import { jwtDecode } from 'jwt-decode'
4
4
  import cookiesModule from 'universal-cookie'
@@ -55,9 +55,10 @@ export async function getSession (initOptions) {
55
55
  })
56
56
  // the core state of the session that is filled by reading cookies
57
57
  const state = reactive({})
58
+ const site = ref(null)
58
59
  // cookies are the source of truth and this information is transformed into the state reactive object
59
60
  const cookies = initOptions?.cookies ?? new Cookies(options.req?.headers.cookie)
60
- const readCookies = () => {
61
+ const readState = () => {
61
62
  const darkCookie = cookies.get('theme_dark')
62
63
  state.dark = darkCookie === '1' || darkCookie === 'true'
63
64
  const langCookie = cookies.get('i18n_lang')
@@ -107,16 +108,22 @@ export async function getSession (initOptions) {
107
108
  }
108
109
  state.accountRole = 'admin'
109
110
  }
111
+ if (!ssr) {
112
+ const siteInfoStorage = getSiteInfoStorage()
113
+ site.value = siteInfoStorage ? siteInfoStorage.info : null
114
+ }
110
115
  }
111
- readCookies()
116
+ readState()
112
117
  debug('initial state', state)
113
118
  if (!ssr) {
114
119
  // sessionData is also stored in localStorage as a way to access it in simpler pages that do not require use-session
115
120
  // and in order to listen to storage event from other contexts and sync session info accross windows and tabs
116
- const storageListener = (event) => {
117
- if (event.key === 'sd-session' + options.sitePath) { readCookies() }
121
+ if (!ssr) {
122
+ const storageListener = (event) => {
123
+ if (event.key === 'sd-session' + options.sitePath) { readState() }
124
+ }
125
+ window.addEventListener('storage', storageListener)
118
126
  }
119
- window.addEventListener('storage', storageListener)
120
127
  // we cannot use onUnmounted here or we get warnings "onUnmounted is called when there is no active component instance to be associated with. "
121
128
  // TODO: should we have another cleanup mechanism ?
122
129
  // onUnmounted(() => { window.removeEventListener('storage', storageListener) })
@@ -166,7 +173,7 @@ export async function getSession (initOptions) {
166
173
  const switchOrganization = (org, dep) => {
167
174
  if (org) { cookies.set('id_token_org', org, { path: cookiesPath }) } else { cookies.remove('id_token_org') }
168
175
  if (dep) { cookies.set('id_token_dep', dep, { path: cookiesPath }) } else { cookies.remove('id_token_dep') }
169
- readCookies()
176
+ readState()
170
177
  }
171
178
  const setAdminMode = async (adminMode, redirect) => {
172
179
  if (adminMode) {
@@ -185,36 +192,67 @@ export async function getSession (initOptions) {
185
192
  } else {
186
193
  await customFetch(`${options.directoryUrl}/api/auth/asadmin`, { method: 'DELETE' })
187
194
  }
188
- readCookies()
195
+ readState()
189
196
  }
190
197
  const cancelDeletion = async () => {
191
198
  if (state.user == null) { return }
192
199
  await customFetch(`${options.directoryUrl}/api/users/${state.user.id}`, { method: 'PATCH', body: ({ plannedDeletion: null }) })
193
- readCookies()
200
+ readState()
194
201
  }
195
202
  const switchDark = (value) => {
196
203
  const maxAge = 60 * 60 * 24 * 365 // 1 year
197
204
  cookies.set('theme_dark', `${value}`, { maxAge, path: cookiesPath })
198
- readCookies()
205
+ readState()
199
206
  }
200
207
  const switchLang = (value) => {
201
208
  const maxAge = 60 * 60 * 24 * 365 // 1 year
202
209
  cookies.set('i18n_lang', value, { maxAge, path: cookiesPath })
203
- readCookies()
210
+ readState()
204
211
  }
205
212
  const keepalive = async () => {
206
213
  if (state.user == null) { return }
207
- window.localStorage.setItem('sd-keepalive' + options.sitePath, `${new Date().getTime()}`)
214
+ if (!ssr) {
215
+ window.localStorage.setItem('sd-keepalive' + options.sitePath, `${new Date().getTime()}`)
216
+ }
208
217
  await customFetch(`${options.directoryUrl}/api/auth/keepalive`, { method: 'POST' })
209
- readCookies()
218
+ readState()
219
+ }
220
+ const getSiteInfoStorage = () => {
221
+ const siteInfoStorageStr = window.localStorage.getItem('sd-site-info' + options.sitePath)
222
+ return siteInfoStorageStr ? JSON.parse(siteInfoStorageStr) : null
223
+ }
224
+ const setSiteInfoStorage = (siteInfo) => {
225
+ const siteInfoStorage = { info: siteInfo, updatedAt: new Date().getTime() }
226
+ window.localStorage.setItem('sd-site-info' + options.sitePath, JSON.stringify(siteInfoStorage))
227
+ }
228
+ const refreshSiteInfo = async () => {
229
+ const siteInfo = await customFetch(`${options.directoryUrl}/api/sites/_public`) ?? null
230
+ site.value = siteInfo
231
+ if (!ssr) {
232
+ setSiteInfoStorage(siteInfo)
233
+ }
210
234
  }
211
235
  // immediately performs a keepalive, but only on top windows (not iframes or popups)
212
236
  // and only if it was not done very recently (maybe from a refreshed page next to this one)
237
+ // also run an auto-refresh loop
213
238
  if (!ssr && window.top === window.self) {
214
239
  const lastKeepalive = window.localStorage.getItem('sd-keepalive' + options.sitePath)
215
240
  if (!lastKeepalive || (new Date().getTime() - Number(lastKeepalive)) > 10000) {
216
241
  await keepalive()
217
242
  }
243
+ if (options.siteInfo) {
244
+ const lastSiteInfoStorage = getSiteInfoStorage()
245
+ if (!lastSiteInfoStorage || (new Date().getTime() - Number(lastSiteInfoStorage.updatedAt)) > 10000) {
246
+ await refreshSiteInfo()
247
+ }
248
+ }
249
+ const refreshLoopDelay = 10 * 60 * 1000 // 10 minutes
250
+ setInterval(() => {
251
+ const lastKeepalive = window.localStorage.getItem('sd-keepalive' + options.sitePath)
252
+ if (!lastKeepalive || (new Date().getTime() - Number(lastKeepalive)) > refreshLoopDelay / 2) {
253
+ keepalive().catch(err => console.error(err))
254
+ }
255
+ }, refreshLoopDelay)
218
256
  }
219
257
  const session = {
220
258
  state,
@@ -224,6 +262,7 @@ export async function getSession (initOptions) {
224
262
  accountRole: computed(() => state.accountRole),
225
263
  dark: computed(() => state.dark),
226
264
  lang: computed(() => state.lang),
265
+ site,
227
266
  loginUrl,
228
267
  login,
229
268
  logout,
@@ -232,6 +271,7 @@ export async function getSession (initOptions) {
232
271
  asAdmin,
233
272
  cancelDeletion,
234
273
  keepalive,
274
+ refreshSiteInfo,
235
275
  switchDark,
236
276
  switchLang,
237
277
  topLocation,