generated types, lots of simplification

dev
Anton Franzluebbers 2024-01-19 19:42:37 -05:00
parent 2e553732dc
commit 00227e9b79
8 changed files with 2954 additions and 264 deletions

1
.github/my.secrets vendored Normal file
View File

@ -0,0 +1 @@
NPM_TOKEN="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"

View File

@ -0,0 +1,25 @@
name: Publish Package to npmjs
on:
push:
branches: ["main"]
paths: ["velconnect-svelte-npm/**"]
defaults:
run:
working-directory: velconnect-svelte-npm
jobs:
pub_npmjs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v3
with:
node-version: "18"
registry-url: "https://registry.npmjs.org"
- run: npm install
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

13
velconnect-npm/.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Build Pocketbase Types",
"type": "shell",
"command": "npx pocketbase-typegen --json src/types/exported_schema.json -o src/types/pocketbase-types.ts",
"problemMatcher": []
}
]
}

File diff suppressed because it is too large Load Diff

View File

@ -8,14 +8,15 @@
"src" "src"
], ],
"scripts": { "scripts": {
"build": "tsc --module commonjs" "build": "tsc --module commonjs",
"generate-types": "npx pocketbase-typegen --json src/types/exported_schema.json -o src/types/pocketbase-types.ts"
}, },
"author": "VEL", "author": "VEL",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"pocketbase": "^0.19.0" "pocketbase": "^0.20.1"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.3.3" "typescript": "^5.3.3"
} }
} }

View File

@ -1,272 +1,76 @@
import PocketBase from "pocketbase"; import PocketBase from "pocketbase";
import type { Record } from "pocketbase"; import { TypedPocketBase } from "./types/pocketbase-types";
type VelConnectOptions = {
url?: string;
debugLog?: boolean;
};
export interface Device extends Record { export let pb: TypedPocketBase = new PocketBase(
os_info: string; "https://velconnect-v4.ugavel.com"
friendly_name: string; ) as TypedPocketBase;
current_room: string; let debugLog = false;
current_app: string; let initialized = false;
pairing_code: string;
data: string;
expand: { data?: DataBlock };
}
export interface DataBlock extends Record {
block_id: string;
owner: string;
data: { [key: string]: string };
}
export class VELConnect { export function initVelConnect(options: VelConnectOptions = {}) {
if (options.debugLog) {
pb: PocketBase; debugLog = true;
debugLog = false;
constructor() {
this.pb = new PocketBase();
this.pb.authStore.onChange((auth) => {
console.log("authStore changed", auth);
currentUser.set(pb.authStore.model);
if (pb.authStore.isValid) {
}
});
} }
if (!initialized) {
export const currentUser = writable(pb.authStore.model); pb = new PocketBase(
options.url ? options.url : "https://velconnect-v4.ugavel.com"
) as TypedPocketBase;
log(`Initialized velconnect on ${pb.baseUrl}`);
export const pairedDevices = writable<string[]>([]);
export const currentDeviceId = writable("");
// const device = get(currentDevice);
// if (device == '' && device.length > 0) {
// currentDevice.set(device[0]);
// }
let unsubscribeDeviceFields: () => void;
let unsubscribeDeviceData: () => void;
let unsubscribeRoomData: () => void;
let unsubscribeCurrentDevice: () => void;
let unsubscribeCurrentUser: () => void;
export let deviceFields = writable<Device | null>(null);
export let deviceData = writable<DataBlock | null>(null);
export let roomData = writable<DataBlock | null>(null);
export let sending = false;
export async function startListening(baseUrl: string) {
pb.baseUrl = baseUrl;
if (get(currentDeviceId) != "") {
const d = (await pb.collection("Device").getOne(get(currentDeviceId), {
expand: "data",
})) as Device;
deviceData.set(d.expand.data as DataBlock);
// we don't need expand anymore, since it doesn't work in subscribe()
d.expand = {};
deviceFields.set(d);
} }
// pb.authStore.onChange((auth) => {
unsubscribeCurrentDevice = currentDeviceId.subscribe(async (val) => { // console.log("authStore changed", auth);
log("currentDeviceId subscribe change event"); // });
unsubscribeDeviceFields?.(); initialized = true;
unsubscribeDeviceData?.();
if (val != "") {
const d = (await pb
.collection("Device")
.getOne(get(currentDeviceId), { expand: "data" })) as Device;
deviceData.set(d.expand.data as DataBlock);
// we don't need expand anymore, since it doesn't work in subscribe()
d.expand = {};
deviceFields.set(d);
unsubscribeDeviceData = await pb
.collection("DataBlock")
.subscribe(d.data, async (data) => {
log("deviceData subscribe change event");
deviceData.set(data.record as DataBlock);
});
unsubscribeDeviceFields = await pb
.collection("Device")
.subscribe(val, async (data) => {
log("deviceFields subscribe change event");
const d = data.record as Device;
deviceFields.set(d);
// if the devie changes, the devicedata could change, so we need to resubscribe
unsubscribeDeviceData?.();
unsubscribeDeviceData = await pb
.collection("DataBlock")
.subscribe(d.data, async (data) => {
log("deviceData subscribe change event");
deviceData.set(data.record as DataBlock);
});
getRoomData(d);
});
if (d != null) getRoomData(d);
} else {
deviceFields.set(null);
deviceData.set(null);
roomData.set(null);
}
});
unsubscribeCurrentUser = currentUser.subscribe((user) => {
log(`currentUser changed ${user}`);
pairedDevices.set(user?.["devices"] ?? []);
currentDeviceId.set(get(pairedDevices)[0] ?? "");
});
} }
export function stopListening() { export function signOut() {
unsubscribeCurrentDevice?.(); pb.authStore.clear();
unsubscribeDeviceFields?.();
unsubscribeDeviceData?.();
unsubscribeRoomData?.();
unsubscribeCurrentUser?.();
console.log("Stop listening");
} }
async function getRoomData(device: Device) { export async function pair(pairingCode: string) {
unsubscribeRoomData?.();
// create or just fetch room by name
let r: DataBlock | null = null;
try { try {
r = (await pb log("Pairing...");
.collection("DataBlock")
.getFirstListItem(
`block_id="${device.current_app}_${device.current_room}"`
)) as DataBlock;
} catch (e: any) {
r = (await pb.collection("DataBlock").create({
block_id: `${device.current_app}_${device.current_room}`,
category: "room",
data: {},
})) as DataBlock;
}
roomData.set(r);
if (r != null) {
unsubscribeRoomData = await pb
.collection("DataBlock")
.subscribe(r.id, (data) => {
log("roomData subscribe change event");
roomData.set(data.record as DataBlock);
});
} else {
console.error("Failed to get or create room");
}
}
let abortController = new AbortController();
export function delayedSend() {
console.log("fn: delayedSend()");
// abort the previous send
abortController.abort();
const newAbortController = new AbortController();
abortController = newAbortController;
setTimeout(() => {
if (!newAbortController.signal.aborted) {
send();
} else {
console.log("aborted");
}
}, 1000);
}
export function send() {
console.log("sending...");
sending = true;
let promises: Promise<any>[] = [];
const device = get(deviceFields);
const data = get(deviceData);
const room = get(roomData);
// TODO send changes only
if (device) {
promises.push(pb.collection("Device").update(device.id, device));
}
if (data) {
promises.push(pb.collection("DataBlock").update(data.id, data));
}
if (room) {
promises.push(pb.collection("DataBlock").update(room.id, room));
}
Promise.all(promises).then(() => {
sending = false;
});
}
export function removeDevice(d: string) {
pairedDevices.set(get(pairedDevices).filter((i) => i != d));
if (get(currentDeviceId) == d) {
console.log("Removed current device");
// if there are still devices left
if (get(pairedDevices).length > 0) {
currentDeviceId.set(get(pairedDevices)[0] ?? "");
} else {
currentDeviceId.set("");
}
}
const user = get(currentUser);
if (user) {
user["devices"] = user["devices"].filter((i: string) => i != d);
pb.collection("Users").update(user.id, user);
}
}
async function pair(pairingCode: string) {
try {
// find the device by pairing code // find the device by pairing code
const device = (await pb const device = await pb
.collection("Device") .collection("Device")
.getFirstListItem(`pairing_code="${pairingCode}"`)) as Device; .getFirstListItem(`pairing_code="${pairingCode}"`);
// add it to the local data
currentDeviceId.set(device.id);
if (!get(pairedDevices).includes(device.id)) {
pairedDevices.set([...get(pairedDevices), device.id]);
}
// add it to my account if logged in // add it to my account if logged in
const u = get(currentUser); const u = pb.authStore.model;
if (u) { if (u) {
// add the device to the user's devices // add the device to the user's devices
u["devices"].push(device.id); u["devices"].push(device.id);
// add the account data to the device // add the account data to the device
if ( if (
u.user_data == null || u["user_data"] == null ||
u.user_data == undefined || u["user_data"] == undefined ||
u.user_data == "" u["user_data"] == ""
) { ) {
// create a new user data block if it doesn't exist on the user already // create a new user data block if it doesn't exist on the user already
const userDataBlock = await pb.collection("DataBlock").create({ const userDataBlock = await pb.collection("DataBlock").create({
category: "device", category: "device",
data: {}, data: {},
owner: u.id, owner: u["id"],
}); });
u.user_data = userDataBlock.id; u["user_data"] = userDataBlock.id;
} }
device["data"] = u.user_data; device["data"] = u["user_data"];
device["owner"] = u.id; device["owner"] = u["id"];
device["past_owners"] = [...device["past_owners"], u.id]; device["past_owners"] = [...device["past_owners"], u["id"]];
await pb.collection("Device").update(device.id, device); await pb.collection("Device").update(device.id, device);
await pb.collection("Users").update(u.id, u); await pb.collection("Users").update(u["id"], u);
} }
return { error: null }; return { error: null, deviceId: device.id };
} catch (e) { } catch (e) {
console.error("Not found: " + e); console.error("Not found: " + e);
if (e == "ClientResponseError 404: The requested resource wasn't found.") { if (e == "ClientResponseError 404: The requested resource wasn't found.") {
@ -282,10 +86,12 @@ async function pair(pairingCode: string) {
export async function login(username: string, password: string) { export async function login(username: string, password: string) {
try { try {
await pb.collection("Users").authWithPassword(username, password); const ret = await pb
return {}; .collection("Users")
} catch (err: any) { .authWithPassword(username, password);
return err; return { ret };
} catch (error: any) {
return { error };
} }
} }
@ -303,15 +109,8 @@ export async function signUp(username: string, password: string) {
} }
} }
export function signOut() { function log(msg: any) {
pb.authStore.clear();
}
function log(msg: string) {
if (debugLog) { if (debugLog) {
console.log(msg); console.log(msg);
} }
} }
}

View File

@ -0,0 +1,396 @@
[
{
"id": "ve85cwsj7syqvxu",
"name": "UserCount",
"type": "base",
"system": false,
"schema": [
{
"id": "pnhtdbcx",
"name": "app_id",
"type": "text",
"system": false,
"required": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "wkf3zyyb",
"name": "room_id",
"type": "text",
"system": false,
"required": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "f7k9hdoc",
"name": "total_users",
"type": "number",
"system": false,
"required": false,
"options": {
"min": null,
"max": null
}
},
{
"id": "uevek8os",
"name": "room_users",
"type": "number",
"system": false,
"required": false,
"options": {
"min": null,
"max": null
}
},
{
"id": "coilxuep",
"name": "version",
"type": "text",
"system": false,
"required": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "zee0a2yb",
"name": "platform",
"type": "text",
"system": false,
"required": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
}
],
"indexes": [],
"listRule": null,
"viewRule": null,
"createRule": "",
"updateRule": null,
"deleteRule": null,
"options": {}
},
{
"id": "fupstz47c55s69f",
"name": "Device",
"type": "base",
"system": false,
"schema": [
{
"id": "1tkrnxqf",
"name": "os_info",
"type": "text",
"system": false,
"required": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "knspamfx",
"name": "friendly_name",
"type": "text",
"system": false,
"required": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "qfalwg3c",
"name": "modified_by",
"type": "text",
"system": false,
"required": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "x0zlup7v",
"name": "current_app",
"type": "text",
"system": false,
"required": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "vpzen2th",
"name": "current_room",
"type": "text",
"system": false,
"required": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "d0ckgjhm",
"name": "pairing_code",
"type": "text",
"system": false,
"required": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "hglbl7da",
"name": "last_online",
"type": "date",
"system": false,
"required": false,
"options": {
"min": "",
"max": ""
}
},
{
"id": "qxsvm1rf",
"name": "data",
"type": "relation",
"system": false,
"required": true,
"options": {
"collectionId": "3qwwkz4wb0lyi78",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": [
"data"
]
}
},
{
"id": "nfernq2q",
"name": "owner",
"type": "relation",
"system": false,
"required": false,
"options": {
"collectionId": "_pb_users_auth_",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": [
"username"
]
}
},
{
"id": "p1aruqz5",
"name": "past_owners",
"type": "relation",
"system": false,
"required": false,
"options": {
"collectionId": "_pb_users_auth_",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": null,
"displayFields": [
"username"
]
}
}
],
"indexes": [],
"listRule": "",
"viewRule": "",
"createRule": null,
"updateRule": "",
"deleteRule": null,
"options": {}
},
{
"id": "3qwwkz4wb0lyi78",
"name": "DataBlock",
"type": "base",
"system": false,
"schema": [
{
"id": "wbifl8pv",
"name": "category",
"type": "text",
"system": false,
"required": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "5a3nwg7m",
"name": "modified_by",
"type": "text",
"system": false,
"required": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "mkzyfsng",
"name": "data",
"type": "json",
"system": false,
"required": false,
"options": {}
},
{
"id": "a3d7pkoh",
"name": "owner",
"type": "relation",
"system": false,
"required": false,
"options": {
"collectionId": "_pb_users_auth_",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": [
"username"
]
}
},
{
"id": "80tmi6fm",
"name": "block_id",
"type": "text",
"system": false,
"required": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
}
],
"indexes": [
"CREATE INDEX `idx_aYVfg1q` ON `DataBlock` (`block_id`)"
],
"listRule": "",
"viewRule": "",
"createRule": "",
"updateRule": "",
"deleteRule": null,
"options": {}
},
{
"id": "_pb_users_auth_",
"name": "Users",
"type": "auth",
"system": false,
"schema": [
{
"id": "users_name",
"name": "name",
"type": "text",
"system": false,
"required": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "users_avatar",
"name": "avatar",
"type": "file",
"system": false,
"required": false,
"options": {
"maxSelect": 1,
"maxSize": 5242880,
"mimeTypes": [
"image/jpeg",
"image/png",
"image/svg+xml",
"image/gif",
"image/webp"
],
"thumbs": null,
"protected": false
}
},
{
"id": "1hwaooub",
"name": "devices",
"type": "relation",
"system": false,
"required": false,
"options": {
"collectionId": "fupstz47c55s69f",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": null,
"displayFields": []
}
},
{
"id": "xvw8arlm",
"name": "profiles",
"type": "relation",
"system": false,
"required": false,
"options": {
"collectionId": "3qwwkz4wb0lyi78",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": null,
"displayFields": [
"data"
]
}
}
],
"indexes": [],
"listRule": "id = @request.auth.id",
"viewRule": "id = @request.auth.id",
"createRule": "",
"updateRule": "id = @request.auth.id",
"deleteRule": "id = @request.auth.id",
"options": {
"allowEmailAuth": true,
"allowOAuth2Auth": true,
"allowUsernameAuth": true,
"exceptEmailDomains": null,
"manageRule": null,
"minPasswordLength": 6,
"onlyEmailDomains": null,
"requireEmail": false
}
}
]

View File

@ -0,0 +1,106 @@
/**
* This file was @generated using pocketbase-typegen
*/
import type PocketBase from 'pocketbase'
import type { RecordService } from 'pocketbase'
export enum Collections {
DataBlock = "DataBlock",
Device = "Device",
UserCount = "UserCount",
Users = "Users",
}
// Alias types for improved usability
export type IsoDateString = string
export type RecordIdString = string
export type HTMLString = string
// System fields
export type BaseSystemFields<T = never> = {
id: RecordIdString
created: IsoDateString
updated: IsoDateString
collectionId: string
collectionName: Collections
expand?: T
}
export type AuthSystemFields<T = never> = {
email: string
emailVisibility: boolean
username: string
verified: boolean
} & BaseSystemFields<T>
// Record types for each collection
export type DataBlockRecord<Tdata = unknown> = {
block_id?: string
category?: string
data?: null | Tdata
modified_by?: string
owner?: RecordIdString
}
export type DeviceRecord = {
current_app?: string
current_room?: string
data: RecordIdString
friendly_name?: string
last_online?: IsoDateString
modified_by?: string
os_info?: string
owner?: RecordIdString
pairing_code?: string
past_owners?: RecordIdString[]
}
export type UserCountRecord = {
app_id?: string
platform?: string
room_id?: string
room_users?: number
total_users?: number
version?: string
}
export type UsersRecord = {
avatar?: string
devices?: RecordIdString[]
name?: string
profiles?: RecordIdString[]
}
// Response types include system fields and match responses from the PocketBase API
export type DataBlockResponse<Tdata = unknown, Texpand = unknown> = Required<DataBlockRecord<Tdata>> & BaseSystemFields<Texpand>
export type DeviceResponse<Texpand = unknown> = Required<DeviceRecord> & BaseSystemFields<Texpand>
export type UserCountResponse<Texpand = unknown> = Required<UserCountRecord> & BaseSystemFields<Texpand>
export type UsersResponse<Texpand = unknown> = Required<UsersRecord> & AuthSystemFields<Texpand>
// Types containing all Records and Responses, useful for creating typing helper functions
export type CollectionRecords = {
DataBlock: DataBlockRecord
Device: DeviceRecord
UserCount: UserCountRecord
Users: UsersRecord
}
export type CollectionResponses = {
DataBlock: DataBlockResponse
Device: DeviceResponse
UserCount: UserCountResponse
Users: UsersResponse
}
// Type for usage with type asserted PocketBase instance
// https://github.com/pocketbase/js-sdk#specify-typescript-definitions
export type TypedPocketBase = PocketBase & {
collection(idOrName: 'DataBlock'): RecordService<DataBlockResponse>
collection(idOrName: 'Device'): RecordService<DeviceResponse>
collection(idOrName: 'UserCount'): RecordService<UserCountResponse>
collection(idOrName: 'Users'): RecordService<UsersResponse>
}