Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sse enhancements #10709

Merged
merged 9 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Enhancement: Implement Server-Sent Events (SSE) for File Creation

We've implemented Server-Sent Events (SSE) to notify users in real-time when a file is uploaded,
a new folder is created, or a file is created (e.g., a text file).
With this enhancement, users will see new files automatically appear in another browser tab if they have one open or
when collaborating with others in the same space.

https://github.com/owncloud/web/pull/10709
https://github.com/owncloud/web/issues/9782
4 changes: 3 additions & 1 deletion packages/web-client/src/sse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ export enum MESSAGE_TYPE {
POSTPROCESSING_FINISHED = 'postprocessing-finished',
FILE_LOCKED = 'file-locked',
FILE_UNLOCKED = 'file-unlocked',
FILE_TOUCHED = 'file-touched',
ITEM_RENAMED = 'item-renamed',
ITEM_TRASHED = 'item-trashed',
ITEM_RESTORED = 'item-restored'
ITEM_RESTORED = 'item-restored',
FOLDER_CREATED = 'folder-created'
}

export class RetriableError extends Error {
Expand Down
6 changes: 5 additions & 1 deletion packages/web-client/src/webdav/client/dav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface DAVOptions {
accessToken: Ref<string>
baseUrl: string
language: Ref<string>
clientInitiatorId: Ref<string>
}

interface DavResult {
Expand All @@ -31,12 +32,14 @@ export class DAV {
private client: WebDAVClient
private davPath: string
private language: Ref<string>
private clientInitiatorId: Ref<string>

constructor({ accessToken, baseUrl, language }: DAVOptions) {
constructor({ accessToken, baseUrl, language, clientInitiatorId }: DAVOptions) {
this.davPath = urlJoin(baseUrl, 'remote.php/dav')
this.accessToken = accessToken
this.client = createClient(this.davPath, {})
this.language = language
this.clientInitiatorId = clientInitiatorId
}

public mkcol(path: string, { headers = {} }: { headers?: Headers } = {}) {
Expand Down Expand Up @@ -166,6 +169,7 @@ export class DAV {
return {
'Accept-Language': unref(this.language),
'Content-Type': 'application/xml; charset=utf-8',
'Initiator-ID': unref(this.clientInitiatorId),
'X-Requested-With': 'XMLHttpRequest',
'X-Request-ID': uuidV4(),
...headers
Expand Down
3 changes: 2 additions & 1 deletion packages/web-client/src/webdav/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export const webdav = (options: WebDavOptions): WebDAV => {
const dav = new DAV({
accessToken: options.accessToken,
baseUrl: options.baseUrl,
language: options.language
language: options.language,
clientInitiatorId: options.clientInitiatorId
})

const pathForFileIdFactory = GetPathForFileIdFactory(dav, options)
Expand Down
1 change: 1 addition & 0 deletions packages/web-client/src/webdav/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface WebDavOptions {
clientService: any
language: Ref<string>
user: Ref<User>
clientInitiatorId: Ref<string>
}

export interface WebDAV {
Expand Down
14 changes: 12 additions & 2 deletions packages/web-pkg/src/components/FilesList/ResourceTile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@
>
<div class="oc-tile-card-hover"></div>
<slot name="imageField" :item="resource">
<oc-img v-if="resource.thumbnail" class="tile-preview" :src="resource.thumbnail" />
<oc-img
v-if="shouldDisplayThumbnails(resource)"
class="tile-preview"
:src="resource.thumbnail"
/>
<resource-icon
v-else
:resource="resource"
Expand Down Expand Up @@ -82,6 +86,7 @@ import ResourceLink from './ResourceLink.vue'
import { Resource } from '@ownclouders/web-client'
import { useGettext } from 'vue3-gettext'
import { isSpaceResource } from '@ownclouders/web-client/src/helpers'
import { isResourceTxtFileAlmostEmpty } from '../../helpers'

export default defineComponent({
name: 'ResourceTile',
Expand Down Expand Up @@ -158,12 +163,17 @@ export default defineComponent({
return ''
})

const shouldDisplayThumbnails = (resource: Resource) => {
return resource.thumbnail && !isResourceTxtFileAlmostEmpty(resource)
}

return {
statusIconAttrs,
showStatusIcon,
tooltipLabelIcon,
resourceDisabled,
resourceDescription
resourceDescription,
shouldDisplayThumbnails
}
}
})
Expand Down
16 changes: 16 additions & 0 deletions packages/web-pkg/src/composables/piniaStores/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useClientStore = defineStore('client', () => {
const clientInitiatorId = ref<string>()
const setClientInitiatorId = (id: string) => {
clientInitiatorId.value = id
}

return {
clientInitiatorId,
setClientInitiatorId
}
})

export type ClientStore = ReturnType<typeof useClientStore>
1 change: 1 addition & 0 deletions packages/web-pkg/src/composables/piniaStores/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './shares'
export * from './spaces'
export * from './theme'
export * from './user'
export * from './client'
9 changes: 7 additions & 2 deletions packages/web-pkg/src/composables/upload/useUpload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { computed, unref, watch } from 'vue'
import { UppyService } from '../../services/uppy/uppyService'
import { v4 as uuidV4 } from 'uuid'
import { useGettext } from 'vue3-gettext'
import { useAuthStore, useCapabilityStore, useConfigStore } from '../piniaStores'
import { useAuthStore, useCapabilityStore, useClientStore, useConfigStore } from '../piniaStores'

interface UploadOptions {
uppyService: UppyService
Expand All @@ -11,11 +11,15 @@ interface UploadOptions {
export function useUpload(options: UploadOptions) {
const configStore = useConfigStore()
const capabilityStore = useCapabilityStore()
const clientStore = useClientStore()
const { current: currentLanguage } = useGettext()
const authStore = useAuthStore()

const headers = computed((): { [key: string]: string } => {
const headers = { 'Accept-Language': currentLanguage }
const headers = {
'Accept-Language': currentLanguage,
'Initiator-ID': clientStore.clientInitiatorId
}
if (authStore.publicLinkContextReady) {
const password = authStore.publicLinkPassword
if (password) {
Expand All @@ -42,6 +46,7 @@ export function useUpload(options: UploadOptions) {
req.setHeader('Authorization', unref(headers).Authorization)
req.setHeader('X-Request-ID', uuidV4())
req.setHeader('Accept-Language', unref(headers)['Accept-Language'])
req.setHeader('Initiator-ID', unref(headers)['Initiator-ID'])
},
headers: (file) =>
!!file.xhrUpload || file?.isRemote
Expand Down
8 changes: 6 additions & 2 deletions packages/web-pkg/src/services/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { WebDAV } from '@ownclouders/web-client/src/webdav'
import { Language } from 'vue3-gettext'
import { FetchEventSourceInit } from '@microsoft/fetch-event-source'
import { sse } from '@ownclouders/web-client/src/sse'
import { AuthStore, ConfigStore, UserStore } from '../../composables'
import { AuthStore, ClientStore, ConfigStore, UserStore } from '../../composables'
import { computed } from 'vue'

interface OcClient {
Expand Down Expand Up @@ -54,13 +54,15 @@ export interface ClientServiceOptions {
language: Language
authStore: AuthStore
userStore: UserStore
clientStore: ClientStore
}

export class ClientService {
private configStore: ConfigStore
private language: Language
private authStore: AuthStore
private userStore: UserStore
private clientStore: ClientStore

private httpAuthenticatedClient: HttpClient
private httpUnAuthenticatedClient: HttpClient
Expand All @@ -75,6 +77,7 @@ export class ClientService {
this.language = options.language
this.authStore = options.authStore
this.userStore = options.userStore
this.clientStore = options.clientStore
}

public get httpAuthenticated(): _HttpClient {
Expand Down Expand Up @@ -132,7 +135,8 @@ export class ClientService {
'Accept-Language': this.currentLanguage,
...(!!authenticated && { Authorization: 'Bearer ' + this.authStore.accessToken }),
'X-Requested-With': 'XMLHttpRequest',
'X-Request-ID': uuidV4()
'X-Request-ID': uuidV4(),
'Initiator-ID': this.clientStore.clientInitiatorId
}
})
}
Expand Down
12 changes: 10 additions & 2 deletions packages/web-pkg/tests/unit/services/client.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { HttpClient } from '../../../src/http'
import { ClientService, useAuthStore, useConfigStore, useUserStore } from '../../../src/'
import {
ClientService,
useAuthStore,
useClientStore,
useConfigStore,
useUserStore
} from '../../../src/'
import { Language } from 'vue3-gettext'
import { Graph, OCS, client as _client } from '@ownclouders/web-client'
import { createTestingPinia, writable } from 'web-test-helpers'
Expand All @@ -13,14 +19,16 @@ const getClientServiceMock = () => {
const authStore = useAuthStore()
const configStore = useConfigStore()
const userStore = useUserStore()
const clientStore = useClientStore()
writable(configStore).serverUrl = serverUrl

return {
clientService: new ClientService({
configStore,
language: language as Language,
authStore,
userStore
userStore,
clientStore
}),
authStore
}
Expand Down
52 changes: 46 additions & 6 deletions packages/web-runtime/src/container/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@ import {
useResourcesStore,
ResourcesStore,
SpacesStore,
MessageStore
MessageStore,
useClientStore
} from '@ownclouders/web-pkg'
import { authService } from '../services/auth'
import {
ClientService,
LoadingService,
PasswordPolicyService,
PreviewService,
UppyService
UppyService,
ClientStore
} from '@ownclouders/web-pkg'
import { init as sentryInit } from '@sentry/vue'
import { webdav } from '@ownclouders/web-client/src/webdav'
Expand All @@ -60,7 +62,9 @@ import {
onSSEItemRenamedEvent,
onSSEProcessingFinishedEvent,
onSSEItemRestoredEvent,
onSSEItemTrashedEvent
onSSEItemTrashedEvent,
onSSEFolderCreatedEvent,
onSSEFileTouchedEvent
} from './sse'

const getEmbedConfigFromQuery = (
Expand Down Expand Up @@ -331,6 +335,7 @@ export const announcePiniaStores = () => {
const sharesStore = useSharesStore()
const spacesStore = useSpacesStore()
const userStore = useUserStore()
const clientStore = useClientStore()

return {
appsStore,
Expand All @@ -343,7 +348,8 @@ export const announcePiniaStores = () => {
modalStore,
sharesStore,
spacesStore,
userStore
userStore,
clientStore
}
}

Expand Down Expand Up @@ -388,19 +394,24 @@ export const announceClientService = ({
configStore,
userStore,
authStore,
capabilityStore
capabilityStore,
clientStore
}: {
app: App
configStore: ConfigStore
userStore: UserStore
authStore: AuthStore
capabilityStore: CapabilityStore
clientStore: ClientStore
}): void => {
clientStore.setClientInitiatorId(uuidV4())

const clientService = new ClientService({
configStore,
language: app.config.globalProperties.$language,
authStore,
userStore
userStore,
clientStore
})
app.config.globalProperties.$clientService = clientService
app.config.globalProperties.$clientService.webdav = webdav({
Expand All @@ -409,6 +420,7 @@ export const announceClientService = ({
capabilities: computed(() => capabilityStore.capabilities),
clientService: app.config.globalProperties.$clientService,
language: computed(() => app.config.globalProperties.$language.current),
clientInitiatorId: computed(() => clientStore.clientInitiatorId),
user: computed(() => userStore.user)
})

Expand Down Expand Up @@ -644,6 +656,7 @@ export const registerSSEEventListeners = ({
language,
resourcesStore,
spacesStore,
clientStore,
messageStore,
clientService,
previewService,
Expand All @@ -653,6 +666,7 @@ export const registerSSEEventListeners = ({
language: Language
resourcesStore: ResourcesStore
spacesStore: SpacesStore
clientStore: ClientStore
messageStore: MessageStore
clientService: ClientService
previewService: PreviewService
Expand All @@ -675,6 +689,7 @@ export const registerSSEEventListeners = ({
topic: MESSAGE_TYPE.ITEM_RENAMED,
resourcesStore,
spacesStore,
clientStore,
msg,
clientService,
router
Expand All @@ -686,6 +701,7 @@ export const registerSSEEventListeners = ({
topic: MESSAGE_TYPE.POSTPROCESSING_FINISHED,
resourcesStore,
spacesStore,
clientStore,
msg,
clientService,
previewService,
Expand Down Expand Up @@ -718,6 +734,7 @@ export const registerSSEEventListeners = ({
topic: MESSAGE_TYPE.ITEM_TRASHED,
language,
resourcesStore,
clientStore,
messageStore,
msg
})
Expand All @@ -728,6 +745,29 @@ export const registerSSEEventListeners = ({
topic: MESSAGE_TYPE.ITEM_RESTORED,
resourcesStore,
spacesStore,
clientStore,
msg,
clientService
})
)

clientService.sseAuthenticated.addEventListener(MESSAGE_TYPE.FOLDER_CREATED, (msg) =>
onSSEFolderCreatedEvent({
topic: MESSAGE_TYPE.FOLDER_CREATED,
resourcesStore,
spacesStore,
clientStore,
msg,
clientService
})
)

clientService.sseAuthenticated.addEventListener(MESSAGE_TYPE.FILE_TOUCHED, (msg) =>
onSSEFileTouchedEvent({
topic: MESSAGE_TYPE.FILE_TOUCHED,
resourcesStore,
spacesStore,
clientStore,
msg,
clientService
})
Expand Down
Loading