parent
43cc610f9b
commit
def2fe48e0
@ -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')
|
||||||
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…
Reference in new issue