Mario Bruestle 3 years ago
parent 43cc610f9b
commit def2fe48e0

8
.idea/.gitignore vendored

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="postgres@localhost" uuid="f9baa73b-e699-47b1-b121-ae82bb675ce1">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://localhost:5432/postgres</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

@ -0,0 +1,7 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TsLint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.pnpm-store" />
<excludeFolder url="file://$MODULE_DIR$/.svelte-kit" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/mariochat.iml" filepath="$PROJECT_DIR$/.idea/mariochat.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PrettierConfiguration">
<option name="myRunOnSave" value="true" />
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

@ -0,0 +1,58 @@
// Update with your config settings.
/**
* @type { Object.<string, import("knex").Knex.Config> }
*/
module.exports = {
development: {
client: 'pg',
connection: {
host: 'localhost',
port: 5432,
user: 'postgres',
password: 'mariochatpgpw',
database: 'postgres',
},
pool: {
min: 2,
max: 10,
},
migrations: {
tableName: 'knex_migrations',
},
},
staging: {
client: 'postgresql',
connection: {
database: 'my_db',
user: 'username',
password: 'password'
},
pool: {
min: 2,
max: 10
},
migrations: {
tableName: 'knex_migrations'
}
},
production: {
client: 'postgresql',
connection: {
database: 'my_db',
user: 'username',
password: 'password'
},
pool: {
min: 2,
max: 10
},
migrations: {
tableName: 'knex_migrations'
}
}
};

@ -0,0 +1,22 @@
exports.up = async (knex) => {
await knex.schema.createTable('user', (table) => {
table.string('public_key').primary()
table.datetime('last_seen').notNullable()
table.binary('name').nullable()
table.binary('avatar').nullable()
})
await knex.schema.createTable('room', (table) => {
table.integer('id').primary()
table.binary('name').notNullable()
table.binary('room_key').notNullable()
table.binary('avatar').nullable()
})
await knex.schema.createTable('user_to_room', (table) => {
table.string('user_public_key').references('public_key').inTable('user').notNullable()
table.integer('room_id').references('id').inTable('room').notNullable()
})
}
exports.down = (knex) => knex.schema.dropTable('user')

@ -11,34 +11,39 @@
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"test:unit": "vitest", "test:unit": "vitest",
"lint": "prettier --plugin-search-dir . --check . && eslint .", "lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ." "format": "prettier --plugin-search-dir . --write .",
"knex": "knex --esm --knexfile knexfile.cjs"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.28.1", "@playwright/test": "^1.31.2",
"@sveltejs/adapter-node": "^1.2.3", "@sveltejs/adapter-node": "^1.2.3",
"@sveltejs/kit": "^1.5.0", "@sveltejs/kit": "^1.12.0",
"@tailwindcss/typography": "^0.5.9", "@tailwindcss/typography": "^0.5.9",
"@types/multicast-dns": "^7.2.1", "@types/node": "^18.15.3",
"@typescript-eslint/eslint-plugin": "^5.45.0", "@types/pg": "^8.6.6",
"@typescript-eslint/parser": "^5.45.0", "@types/web-push": "^3.3.2",
"@typescript-eslint/eslint-plugin": "^5.55.0",
"@typescript-eslint/parser": "^5.55.0",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"daisyui": "^2.51.4", "daisyui": "^2.51.4",
"eslint": "^8.28.0", "eslint": "^8.36.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.7.0",
"eslint-plugin-svelte3": "^4.0.0", "eslint-plugin-svelte3": "^4.0.0",
"firebase": "^9.18.0",
"idb": "^7.1.1", "idb": "^7.1.1",
"multicast-dns": "^7.2.5",
"postcss": "^8.4.21", "postcss": "^8.4.21",
"prettier": "^2.8.0", "prettier": "^2.8.4",
"prettier-plugin-svelte": "^2.8.1", "prettier-plugin-svelte": "^2.9.0",
"svelte": "^3.54.0", "svelte": "^3.57.0",
"svelte-check": "^3.0.1", "svelte-check": "^3.1.4",
"tailwindcss": "^3.2.7", "tailwindcss": "^3.2.7",
"tslib": "^2.4.1", "ts-node": "^10.9.1",
"tslib": "^2.5.0",
"tweetnacl": "^1.0.3", "tweetnacl": "^1.0.3",
"typescript": "^4.9.3", "typescript": "^4.9.5",
"vite": "^4.0.0", "vite": "^4.2.0",
"vitest": "^0.25.3" "vitest": "^0.25.8",
"web-push": "^3.5.0"
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {

File diff suppressed because it is too large Load Diff

@ -1,19 +1,28 @@
import makeMdns from 'multicast-dns' import Knex from 'knex'
const mdns = makeMdns() import { Model } from 'objection'
import { setVapidDetails } from 'web-push'
mdns.on('response', (response) => { const knex = Knex({
console.log('got a response packet:', response) client: 'pg',
connection: {
host: process.env.DB_URL ?? 'localhost',
port: 5432,
user: 'postgres',
password: 'mariochatpgpw',
database: 'postgres',
},
}) })
mdns.on('query', function (query) { Model.knex(knex)
console.log('got a query packet:', query)
})
mdns.query({ const vapidKeys = {
questions: [ publicKey:
{ 'BEovcVTe3AmMJ8fpMahQqjr2shW74zBW6Imvih274_03nJg9m4hhUIAPc2Ur0_2aryAOXCA9eEulplz2y0CLfwY',
name: 'brunhilde.local', privateKey: 'DUT4Fn5r293-e5gTvpgiQtpU5yhjgwASulFZZclFaJg',
type: 'A', }
},
], setVapidDetails(
}) 'mailto:mario.bruestle@pm.me',
vapidKeys.publicKey,
vapidKeys.privateKey,
)

@ -0,0 +1,39 @@
import { Model } from 'objection'
class BaseModel extends Model {}
export class User extends BaseModel {
static tableName = 'user'
public_key!: string
last_seen!: Date
name?: Uint8Array
avatar?: Uint8Array
}
export class Room extends BaseModel {
static tableName = 'room'
id!: number
room_key!: Uint8Array
avatar?: Uint8Array
static relationMappings = {
users: {
relation: Model.ManyToManyRelation,
modelClass: User,
join: {
from: 'room.id',
through: {
from: 'user_to_room.room_id',
to: 'user_to_room.user_public_key',
},
to: 'user.public_key',
},
},
}
}
export class UserToRoom extends BaseModel {
static tableName = 'user_to_room'
user_public_key!: string
room_id!: number
}

@ -0,0 +1,154 @@
/// <reference types="@sveltejs/kit" />
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />
import { build, files, version } from '$service-worker'
const sw = self as unknown as ServiceWorkerGlobalScope
// Create a unique cache name for this deployment
const CACHE = `cache-${version}`
const ASSETS = [
...build, // the app itself
...files, // everything in `static`
'/',
]
function installListener(event: ExtendableEvent) {
console.log('installing service worker')
// Create a new cache and add all files to it
async function addFilesToCache() {
const cache = await caches.open(CACHE)
await cache.addAll(ASSETS)
}
event.waitUntil(addFilesToCache())
sw.skipWaiting()
}
function activateListener(event: ExtendableEvent) {
console.log('activating service worker')
// Remove previous cached data from disk
async function deleteOldCaches() {
let cachesWereDeleted = false
for (const key of await caches.keys()) {
if (key !== CACHE) {
await caches.delete(key)
cachesWereDeleted = true
}
}
sw.clients.claim()
if (cachesWereDeleted) {
sw.clients.matchAll().then((clients) => {
clients.forEach((client) => {
if ('navigate' in client) {
console.log('refreshing on sw update')
return (client as { navigate: (url: string) => void }).navigate(
client.url,
)
}
})
})
}
}
event.waitUntil(deleteOldCaches())
}
function fetchListener(event: FetchEvent) {
// ignore POST requests etc
if (event.request.method !== 'GET' || event.request.headers.has('range'))
return
async function respond() {
const url = new URL(event.request.url)
const cache = await caches.open(CACHE)
// `build`/`files` can always be served from the cache
if (ASSETS.includes(url.pathname)) {
return (await cache.match(event.request)) ?? Response.error()
}
// for everything else, try the network first, but
// fall back to the cache if we're offline
try {
const response = await fetch(event.request)
if (response.status === 200) {
cache.put(event.request, response.clone())
}
return response
} catch {
// prerendering and dynamic routes don't work all that well together
// so just redirect to MAD start page if we're offline
if (url.pathname !== '/') {
return Response.redirect('/', 302)
}
return (await cache.match(event.request)) || Response.error()
}
}
event.respondWith(respond())
}
function backgroundFetchListener(event: BackgroundFetchEvent) {
const bgFetch = event.registration
event.waitUntil(
(async () => {
try {
console.log('backgorund fetch called!')
} catch (error) {
await bgFetch.abort()
console.error('Background fetch failed:', error)
}
})(),
)
}
sw.addEventListener('install', installListener)
sw.addEventListener('activate', activateListener)
sw.addEventListener('fetch', fetchListener)
sw.addEventListener('backgroundfetch', backgroundFetchListener)
async function requestNotificationPermission() {
const permission = await Notification.requestPermission()
if (permission !== 'granted') {
console.error('Notification permission not granted')
return false
}
return true
}
requestNotificationPermission()
import { initializeApp } from 'firebase/app'
import firebase from 'firebase/compat'
// Your web app's Firebase configuration
const firebaseConfig = {
apiKey: 'AIzaSyBjO3BntXN4pi_Is8qB-AgFrTsssP8u0Hc',
authDomain: 'mariochat-42e86.firebaseapp.com',
projectId: 'mariochat-42e86',
storageBucket: 'mariochat-42e86.appspot.com',
messagingSenderId: '650934549094',
appId: '1:650934549094:web:48a55d718de5b3c7a64ba7',
}
const app = initializeApp(firebaseConfig)
const messaging = firebase.messaging()
async function requestNotificationPermissionAndToken(): Promise<string | null> {
const permissionGranted = await requestNotificationPermission()
if (!permissionGranted) return null
try {
const token = await messaging.getToken({
vapidKey:
'BEovcVTe3AmMJ8fpMahQqjr2shW74zBW6Imvih274_03nJg9m4hhUIAPc2Ur0_2aryAOXCA9eEulplz2y0CLfwY',
})
return token
} catch (error) {
console.error('Failed to get the registration token:', error)
return null
}
}
Loading…
Cancel
Save