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

feat: Implement DataProxy #1

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Local
.DS_Store
*.local
*.log*

# Dist
node_modules
dist/

# IDE
.vscode/*
!.vscode/extensions.json
.idea

# COZY
build/*
31 changes: 29 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,29 @@
# cozy-web-data-proxy
Web app that can be used to mutualize data between cozy-apps
# Rsbuild Project

## Setup

Install the dependencies:

```bash
pnpm install
```

## Get Started

Start the dev server:

```bash
pnpm dev
```

Build the app for production:

```bash
pnpm build
```

Preview the production build locally:

```bash
pnpm preview
```
37 changes: 37 additions & 0 deletions manifest.webapp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "cozy-data-proxy",
"slug": "dataproxy",
"icon": "icon.svg",
"categories": [],
"version": "0.1.0",
"licence": "AGPL-3.0",
"editor": "",
"source": "https://github.com/cozy/cozy-web-data-proxy.git@build",
"developer": {
"name": "cozy",
"url": ""
},
"routes": {
"/": {
"folder": "/",
"index": "index.html",
"public": false
}
},
"permissions": {
"apps": {
"description": "Required by the cozy-bar to display the icons of the apps",
"type": "io.cozy.apps",
"verbs": ["GET"]
},
"settings": {
"description": "Required by the cozy-bar to display Claudy and know which applications are coming soon",
"type": "io.cozy.settings",
"verbs": ["GET"]
},
"mocks todos": {
"description": "TO REMOVE: only used as demonstration about Cozy App data interactions",
"type": "io.mocks.todos"
}
}
}
31 changes: 31 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "cozy-web-data-proxy",
"private": true,
"version": "1.0.0",
"scripts": {
"dev": "rsbuild dev --open",
"build": "rsbuild build",
"watch": "rsbuild build --watch",
"preview": "rsbuild preview"
},
"dependencies": {
"comlink": "^4.4.1",
"cozy-client": "^49.0.0",
"cozy-device-helper": "^3.1.0",
"cozy-flags": "^4.0.0",
"cozy-logger": "^1.10.4",
"lodash": "^4.17.21",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@rsbuild/core": "^1.0.1",
"@rsbuild/plugin-node-polyfill": "^1.0.4",
"@rsbuild/plugin-react": "^1.0.1",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@types/sharedworker": "^0.0.126",
"rsbuild-plugin-ejs": "^1.0.1",
"typescript": "^5.5.2"
}
}
Empty file added public/.gitkeep
Empty file.
55 changes: 55 additions & 0 deletions rsbuild.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { defineConfig } from '@rsbuild/core';
import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill';
import { pluginReact } from '@rsbuild/plugin-react';
import { pluginEjs } from "rsbuild-plugin-ejs";
import { rspack } from "@rspack/core";

export default defineConfig({
plugins: [pluginEjs(), pluginNodePolyfill(), pluginReact()],
output: {
cleanDistPath: true,
distPath: {
root: 'build'
}
},
html: {
template: './src/targets/browser/index.ejs',
title: 'Cozy DataProxy'
},
performance: {
chunkSplit: {
forceSplitting: {
cozy: /node_modules[\\/]cozy*/,
},
}
},
source: {
entry: {
index: './src/targets/browser/index.tsx'
}
},
tools: {
rspack: {
module: {
rules: [
{ test: /\.webapp$/i, type: "json" }
]
},
plugins: [
new rspack.CopyRspackPlugin({
patterns: [
{
from: 'manifest.webapp',
},
{
from: 'README.md'
},
{
from: 'LICENSE'
}
],
})
]
}
}
});
Empty file added src/App.css
Empty file.
37 changes: 37 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useClient } from 'cozy-client'

import './App.css';

import { useSharedWorker } from 'src/worker/useSharedWorker'
import { SharedWorkerProvider } from './worker/SharedWorkerProvider';
import { ParentWindowProvider } from './parent/ParentWindowProvider';

const App = () => {
const client = useClient()
const worker = useSharedWorker()

const search = async () => {
const resultt = await worker.search('aze')
console.log('result', resultt)
}

return (
<div className="content">
<h1>Cozy DataProxy</h1>
<p>{client?.getStackClient().uri}</p>
<button onClick={search}>Send message</button>
</div>
);
};

const WrappedApp = () => {
return (
<SharedWorkerProvider>
<ParentWindowProvider>
<App />
</ParentWindowProvider>
</SharedWorkerProvider>
)
}

export default WrappedApp;
9 changes: 9 additions & 0 deletions src/common/DataProxyInterface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface DataProxyWorker {
search: (query: string) => Promise<unknown>
setClient: (clientData: ClientData) => Promise<void>
}

export interface ClientData {
uri: string
token: string
}
15 changes: 15 additions & 0 deletions src/doctypes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// the documents schema, necessary for CozyClient
export default {
timetable: {
doctype: 'io.cozy.calendar.events'
},
homeworks: {
doctype: 'io.cozy.calendar.todos'
},
grades: {
doctype: 'io.cozy.timeseries.grades'
},
presence: {
doctype: 'io.cozy.calendar.presence'
}
}
1 change: 1 addition & 0 deletions src/env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="@rsbuild/core/types" />
35 changes: 35 additions & 0 deletions src/parent/ParentWindowProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import * as Comlink from 'comlink'
import React, { ReactNode } from 'react'

import { DataProxyWorker } from 'src/common/DataProxyInterface'
import { useSharedWorker } from 'src/worker/useSharedWorker'

export const ParentWindowContext = React.createContext<DataProxyWorker | undefined>(undefined)

interface ParentWindowProviderProps {
children: ReactNode
}

export const ParentWindowProvider = React.memo(({ children }: ParentWindowProviderProps) => {
const worker = useSharedWorker()

const iframeProxy = {
search: async (search: string) => {
console.log('RECEIVED SEARCH FROM PARENT')
const result = await worker.search(search)
console.log('INTERMEDIATE RESULT', result)
return result
}
}

Comlink.expose(iframeProxy, Comlink.windowEndpoint(parent));

if (!worker) return undefined

return (
<ParentWindowContext.Provider value={worker}>
{children}
</ParentWindowContext.Provider>
)
})
ParentWindowProvider.displayName = 'SharedWorkerProvider'
18 changes: 18 additions & 0 deletions src/targets/browser/index.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="{{.Locale}}">
<head>
<meta charset="utf-8">
<title><%= htmlPlugin.options.title %></title>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="/favicon-16x16.png" sizes="16x16">
<link rel="manifest" href="/manifest.json" crossOrigin="use-credentials">
<meta name="color-scheme" content="light dark" />
<meta name="theme-color" content="#ffffff">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="//{{.Domain}}/assets/fonts/fonts.css">
</head>
<div
role="application"
data-cozy="{{.CozyData}}"
></div>
22 changes: 22 additions & 0 deletions src/targets/browser/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import ReactDOM from 'react-dom/client';

import { CozyProvider } from 'cozy-client';

import App from 'src/App';
import { setupApp } from './setupApp';


const init = function () {
const { root, client } = setupApp()

root.render(
<React.StrictMode>
<CozyProvider client={client}>
<App />
</CozyProvider>
</React.StrictMode>,
);
}

init()
54 changes: 54 additions & 0 deletions src/targets/browser/setupApp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import memoize from 'lodash/memoize'
import { createRoot } from 'react-dom/client'

import CozyClient from 'cozy-client'
import flag from 'cozy-flags'

import schema from 'src/doctypes'

const manifest = require('manifest.webapp')

/**
* Make and returns cozy client instance
*/
const makeClient = (container: HTMLElement): CozyClient => {
if (!container.dataset.cozy) {
throw new Error('No data-cozy dataset found')
}

const data = JSON.parse(container.dataset.cozy)
const protocol = window.location.protocol
const cozyUrl = `${protocol}//${data.domain}`

const client = new CozyClient({
uri: cozyUrl,
token: data.token,
appMetadata: {
slug: manifest.name,
version: manifest.version
},
schema,
store: true
})

return client;
}

/**
* Setup cozy-client and retrieve app container
*
* This method is memoized in order to optimize hot-reloading
*/
export const setupApp = memoize(() => {
const container = document.querySelector<HTMLElement>('[role=application]')

if(!container) {
throw new Error('Failed to find [role=application] container')
}

const root = createRoot(container)
const client = makeClient(container)
client.registerPlugin(flag.plugin, null)

return { root, client }
})
Loading