@data-fair/lib-vue 1.7.0 → 1.9.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 +23 -0
  3. package/session.js +58 -17
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.9.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
@@ -12,11 +12,30 @@ interface GenericCookies {
12
12
  export interface SessionOptions {
13
13
  sitePath: string;
14
14
  directoryUrl: string;
15
+ defaultLang: string;
15
16
  route?: RouteLocation;
16
17
  logoutRedirectUrl?: string;
17
18
  req?: IncomingMessage;
18
19
  cookies?: GenericCookies;
19
20
  customFetch?: typeof fetch;
21
+ siteInfo?: boolean;
22
+ }
23
+ export interface Colors {
24
+ background: string;
25
+ surface: string;
26
+ primary: string;
27
+ secondary: string;
28
+ accent: string;
29
+ error: string;
30
+ info: string;
31
+ success: string;
32
+ warning: string;
33
+ admin: string;
34
+ }
35
+ export interface SiteInfo {
36
+ main?: boolean;
37
+ logo?: string;
38
+ colors: Colors;
20
39
  }
21
40
  export interface Session {
22
41
  state: SessionState;
@@ -26,6 +45,7 @@ export interface Session {
26
45
  accountRole: ComputedRef<SessionState['accountRole']>;
27
46
  lang: ComputedRef<SessionState['lang']>;
28
47
  dark: ComputedRef<SessionState['dark']>;
48
+ site: Ref<SiteInfo | null>;
29
49
  loginUrl: (redirect?: string, extraParams?: Record<string, string>, immediateRedirect?: true) => string;
30
50
  login: (redirect?: string, extraParams?: Record<string, string>, immediateRedirect?: true) => void;
31
51
  logout: (redirect?: string) => Promise<void>;
@@ -34,6 +54,7 @@ export interface Session {
34
54
  asAdmin: (user: any | null) => Promise<void>;
35
55
  cancelDeletion: () => Promise<void>;
36
56
  keepalive: () => Promise<void>;
57
+ refreshSiteInfo: () => Promise<void>;
37
58
  switchDark: (value: boolean) => void;
38
59
  switchLang: (value: string) => void;
39
60
  topLocation: Ref<Location | undefined>;
@@ -56,6 +77,7 @@ export declare function createSession(initOptions: Partial<SessionOptions>): Pro
56
77
  accountRole: ComputedRef<SessionState["accountRole"]>;
57
78
  lang: ComputedRef<SessionState["lang"]>;
58
79
  dark: ComputedRef<SessionState["dark"]>;
80
+ site: Ref<SiteInfo | null>;
59
81
  loginUrl: (redirect?: string, extraParams?: Record<string, string>, immediateRedirect?: true) => string;
60
82
  login: (redirect?: string, extraParams?: Record<string, string>, immediateRedirect?: true) => void;
61
83
  logout: (redirect?: string) => Promise<void>;
@@ -64,6 +86,7 @@ export declare function createSession(initOptions: Partial<SessionOptions>): Pro
64
86
  asAdmin: (user: any | null) => Promise<void>;
65
87
  cancelDeletion: () => Promise<void>;
66
88
  keepalive: () => Promise<void>;
89
+ refreshSiteInfo: () => Promise<void>;
67
90
  switchDark: (value: boolean) => void;
68
91
  switchLang: (value: string) => void;
69
92
  topLocation: Ref<Location | undefined>;
package/session.js CHANGED
@@ -1,8 +1,9 @@
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'
5
5
  import Debug from 'debug'
6
+ import inIframe from '@data-fair/lib-utils/in-iframe.js'
6
7
  const Cookies = cookiesModule
7
8
  const debug = Debug('session')
8
9
  debug.log = console.log.bind(console)
@@ -36,7 +37,7 @@ const goTo = (url) => {
36
37
  }
37
38
  if (url) { topLocation.href = url } else { topLocation.reload() }
38
39
  }
39
- const defaultOptions = { directoryUrl: '/simple-directory', sitePath: '' }
40
+ const defaultOptions = { directoryUrl: '/simple-directory', sitePath: '', defaultLang: 'fr' }
40
41
  export async function getSession (initOptions) {
41
42
  const options = { ...defaultOptions, ...initOptions }
42
43
  const cookiesPath = options.sitePath + '/'
@@ -55,13 +56,14 @@ export async function getSession (initOptions) {
55
56
  })
56
57
  // the core state of the session that is filled by reading cookies
57
58
  const state = reactive({})
59
+ const site = ref(null)
58
60
  // cookies are the source of truth and this information is transformed into the state reactive object
59
61
  const cookies = initOptions?.cookies ?? new Cookies(options.req?.headers.cookie)
60
- const readCookies = () => {
62
+ const readState = () => {
61
63
  const darkCookie = cookies.get('theme_dark')
62
64
  state.dark = darkCookie === '1' || darkCookie === 'true'
63
65
  const langCookie = cookies.get('i18n_lang')
64
- if (langCookie) { state.lang = langCookie } else { delete state.lang }
66
+ state.lang = langCookie ?? options.defaultLang
65
67
  const idToken = cookies.get('id_token')
66
68
  const user = jwtDecodeAlive(idToken)
67
69
  if (!user) {
@@ -107,16 +109,22 @@ export async function getSession (initOptions) {
107
109
  }
108
110
  state.accountRole = 'admin'
109
111
  }
112
+ if (!ssr) {
113
+ const siteInfoStorage = getSiteInfoStorage()
114
+ site.value = siteInfoStorage ? siteInfoStorage.info : null
115
+ }
110
116
  }
111
- readCookies()
117
+ readState()
112
118
  debug('initial state', state)
113
119
  if (!ssr) {
114
120
  // sessionData is also stored in localStorage as a way to access it in simpler pages that do not require use-session
115
121
  // 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() }
122
+ if (!ssr) {
123
+ const storageListener = (event) => {
124
+ if (event.key === 'sd-session' + options.sitePath) { readState() }
125
+ }
126
+ window.addEventListener('storage', storageListener)
118
127
  }
119
- window.addEventListener('storage', storageListener)
120
128
  // we cannot use onUnmounted here or we get warnings "onUnmounted is called when there is no active component instance to be associated with. "
121
129
  // TODO: should we have another cleanup mechanism ?
122
130
  // onUnmounted(() => { window.removeEventListener('storage', storageListener) })
@@ -166,7 +174,7 @@ export async function getSession (initOptions) {
166
174
  const switchOrganization = (org, dep) => {
167
175
  if (org) { cookies.set('id_token_org', org, { path: cookiesPath }) } else { cookies.remove('id_token_org') }
168
176
  if (dep) { cookies.set('id_token_dep', dep, { path: cookiesPath }) } else { cookies.remove('id_token_dep') }
169
- readCookies()
177
+ readState()
170
178
  }
171
179
  const setAdminMode = async (adminMode, redirect) => {
172
180
  if (adminMode) {
@@ -185,36 +193,67 @@ export async function getSession (initOptions) {
185
193
  } else {
186
194
  await customFetch(`${options.directoryUrl}/api/auth/asadmin`, { method: 'DELETE' })
187
195
  }
188
- readCookies()
196
+ readState()
189
197
  }
190
198
  const cancelDeletion = async () => {
191
199
  if (state.user == null) { return }
192
200
  await customFetch(`${options.directoryUrl}/api/users/${state.user.id}`, { method: 'PATCH', body: ({ plannedDeletion: null }) })
193
- readCookies()
201
+ readState()
194
202
  }
195
203
  const switchDark = (value) => {
196
204
  const maxAge = 60 * 60 * 24 * 365 // 1 year
197
205
  cookies.set('theme_dark', `${value}`, { maxAge, path: cookiesPath })
198
- readCookies()
206
+ readState()
199
207
  }
200
208
  const switchLang = (value) => {
201
209
  const maxAge = 60 * 60 * 24 * 365 // 1 year
202
210
  cookies.set('i18n_lang', value, { maxAge, path: cookiesPath })
203
- readCookies()
211
+ readState()
204
212
  }
205
213
  const keepalive = async () => {
206
214
  if (state.user == null) { return }
207
- window.localStorage.setItem('sd-keepalive' + options.sitePath, `${new Date().getTime()}`)
215
+ if (!ssr) {
216
+ window.localStorage.setItem('sd-keepalive' + options.sitePath, `${new Date().getTime()}`)
217
+ }
208
218
  await customFetch(`${options.directoryUrl}/api/auth/keepalive`, { method: 'POST' })
209
- readCookies()
219
+ readState()
220
+ }
221
+ const getSiteInfoStorage = () => {
222
+ const siteInfoStorageStr = window.localStorage.getItem('sd-site-info' + options.sitePath)
223
+ return siteInfoStorageStr ? JSON.parse(siteInfoStorageStr) : null
224
+ }
225
+ const setSiteInfoStorage = (siteInfo) => {
226
+ const siteInfoStorage = { info: siteInfo, updatedAt: new Date().getTime() }
227
+ window.localStorage.setItem('sd-site-info' + options.sitePath, JSON.stringify(siteInfoStorage))
228
+ }
229
+ const refreshSiteInfo = async () => {
230
+ const siteInfo = await customFetch(`${options.directoryUrl}/api/sites/_public`) ?? null
231
+ site.value = siteInfo
232
+ if (!ssr) {
233
+ setSiteInfoStorage(siteInfo)
234
+ }
210
235
  }
211
236
  // immediately performs a keepalive, but only on top windows (not iframes or popups)
212
237
  // and only if it was not done very recently (maybe from a refreshed page next to this one)
213
- if (!ssr && window.top === window.self) {
238
+ // also run an auto-refresh loop
239
+ if (!ssr && !inIframe) {
214
240
  const lastKeepalive = window.localStorage.getItem('sd-keepalive' + options.sitePath)
215
- if (!lastKeepalive || (new Date().getTime() - Number(lastKeepalive)) > 10000) {
241
+ if (state.user && (!lastKeepalive || (new Date().getTime() - Number(lastKeepalive)) > 10000)) {
216
242
  await keepalive()
217
243
  }
244
+ if (options.siteInfo) {
245
+ const lastSiteInfoStorage = getSiteInfoStorage()
246
+ if (!lastSiteInfoStorage || (new Date().getTime() - Number(lastSiteInfoStorage.updatedAt)) > 10000) {
247
+ await refreshSiteInfo()
248
+ }
249
+ }
250
+ const refreshLoopDelay = 10 * 60 * 1000 // 10 minutes
251
+ setInterval(() => {
252
+ const lastKeepalive = window.localStorage.getItem('sd-keepalive' + options.sitePath)
253
+ if (!lastKeepalive || (new Date().getTime() - Number(lastKeepalive)) > refreshLoopDelay / 2) {
254
+ keepalive().catch(err => console.error(err))
255
+ }
256
+ }, refreshLoopDelay)
218
257
  }
219
258
  const session = {
220
259
  state,
@@ -224,6 +263,7 @@ export async function getSession (initOptions) {
224
263
  accountRole: computed(() => state.accountRole),
225
264
  dark: computed(() => state.dark),
226
265
  lang: computed(() => state.lang),
266
+ site,
227
267
  loginUrl,
228
268
  login,
229
269
  logout,
@@ -232,6 +272,7 @@ export async function getSession (initOptions) {
232
272
  asAdmin,
233
273
  cancelDeletion,
234
274
  keepalive,
275
+ refreshSiteInfo,
235
276
  switchDark,
236
277
  switchLang,
237
278
  topLocation,