diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..19d7665 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +static/ +vendor/ +.env* +*.local +*.log +*.tsbuildinfo +.idea/ diff --git a/lib/Module.php b/lib/Module.php new file mode 100644 index 0000000..170681f --- /dev/null +++ b/lib/Module.php @@ -0,0 +1,22 @@ +registerModule($this, 'ProviderEditorOffice', 'static/module.mjs'); + } + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..2632736 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "provider_editor_office", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "provider_editor_office", + "version": "0.0.1", + "license": "AGPL-3.0-or-later" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c3770ea --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "provider_editor_office", + "version": "0.0.1", + "private": true, + "type": "module", + "license": "AGPL-3.0-or-later", + "scripts": { + "build": "vite build --mode production --config vite.config.ts", + "dev": "vite build --mode development --config vite.config.ts", + "watch": "vite build --mode development --watch --config vite.config.ts", + "typecheck": "vue-tsc --noEmit", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", + "test": "vitest run --config tests/js/vitest.config.ts", + "test:unit": "vitest run --config tests/js/vitest.config.ts", + "test:watch": "vitest watch --config tests/js/vitest.config.ts", + "test:coverage": "vitest run --coverage --config tests/js/vitest.config.ts" + } +} diff --git a/src/api.ts b/src/api.ts new file mode 100644 index 0000000..56aa32f --- /dev/null +++ b/src/api.ts @@ -0,0 +1,21 @@ +import { createFetchWrapper } from '@KTXC' + +export interface LaunchSession { + actionUrl: string + accessToken: string + accessTokenTtl: number + officeOrigin: string + mode: 'view' +} + +const api = createFetchWrapper() +export const launch = (resource: string): Promise => api.post('/m/service_wopi/sessions', { resource }) + +export function validateSession(session: LaunchSession): void { + const action = new URL(session.actionUrl) + if (action.protocol !== 'https:' || action.origin !== session.officeOrigin || action.username || action.password + || session.mode !== 'view' || !/^[a-f0-9]{64}$/.test(session.accessToken) + || !Number.isSafeInteger(session.accessTokenTtl) || session.accessTokenTtl <= Date.now()) { + throw new Error('Invalid office session.') + } +} diff --git a/src/components/OfficeViewer.vue b/src/components/OfficeViewer.vue new file mode 100644 index 0000000..6f07473 --- /dev/null +++ b/src/components/OfficeViewer.vue @@ -0,0 +1,76 @@ + + +