Add automatic nearby discovery and simplify device presence UI
This commit is contained in:
@@ -3,6 +3,7 @@ import type { Auth } from './auth.js';
|
||||
import type { Accounts } from './accounts.js';
|
||||
import { Storage } from './storage.js';
|
||||
import { ResumableUploads } from './resumable.js';
|
||||
import { NearbyLocations } from './nearby.js';
|
||||
import { Vault } from './vault.js';
|
||||
import { randomInt, randomUUID } from 'node:crypto';
|
||||
import { and, eq, gt, isNull, or, desc, lt, inArray, sql } from 'drizzle-orm';
|
||||
@@ -25,6 +26,8 @@ const day = 86400000;
|
||||
export class Service {
|
||||
readonly limiter = new Limiter();
|
||||
presence!: Presence;
|
||||
nearby: NearbyLocations;
|
||||
readonly locationAcceptances = new Map<string, { guestId: string; until: number }>();
|
||||
auth!: Auth;
|
||||
accounts!: Accounts;
|
||||
io?: Server;
|
||||
@@ -37,6 +40,9 @@ export class Service {
|
||||
) {
|
||||
this.storage = new Storage(this);
|
||||
this.resumable = new ResumableUploads(this);
|
||||
this.nearby = new NearbyLocations(cfg.locationRadiusMeters, (ids: string[]) =>
|
||||
this.refresh(ids),
|
||||
);
|
||||
this.vault = new Vault(this);
|
||||
}
|
||||
guest(token: string | undefined) {
|
||||
@@ -56,7 +62,13 @@ export class Service {
|
||||
device(guestId: string): Device {
|
||||
const d = this.db.select().from(s.devices).where(eq(s.devices.guestId, guestId)).get();
|
||||
if (!d) fail(401, 'SESSION_EXPIRED', '기기 세션이 만료되었습니다.');
|
||||
return { id: d!.id, name: d!.name, code: d!.code, discoverable: d!.discoverable };
|
||||
return {
|
||||
id: d!.id,
|
||||
name: d!.name,
|
||||
code: d!.code,
|
||||
discoverable: d!.discoverable,
|
||||
info: this.presence?.info(guestId),
|
||||
};
|
||||
}
|
||||
deviceGuest(id: string) {
|
||||
return this.db.select().from(s.devices).where(eq(s.devices.id, id)).get();
|
||||
@@ -111,6 +123,7 @@ export class Service {
|
||||
.set({ ...input, lastSeen: Date.now() })
|
||||
.where(eq(s.devices.guestId, g.id))
|
||||
.run();
|
||||
if (input.discoverable === false) this.nearby.clearGuest(g.id);
|
||||
this.refresh();
|
||||
}
|
||||
reset(g: Guest) {
|
||||
@@ -215,7 +228,10 @@ export class Service {
|
||||
scope(a: string, b: string) {
|
||||
if (this.isBlocked(a, b)) return false;
|
||||
if (this.activeRoom(a) || this.activeRoom(b)) return this.sharedSpace(a, b);
|
||||
return this.presence.sameNetwork(a, b);
|
||||
return (
|
||||
this.presence.sameNetwork(a, b) ||
|
||||
(this.cfg.locationDiscoveryEnabled && this.nearby.nearby(a, b))
|
||||
);
|
||||
}
|
||||
restrictTransfers(leaving: string[] = []) {
|
||||
const active = this.db
|
||||
@@ -274,12 +290,18 @@ export class Service {
|
||||
? ['network' as const]
|
||||
: []),
|
||||
...(this.sharedSpace(g.id, id) ? ['space' as const] : []),
|
||||
...(!this.activeRoom(g.id) &&
|
||||
this.cfg.locationDiscoveryEnabled &&
|
||||
this.nearby.nearby(g.id, id)
|
||||
? ['nearby' as const]
|
||||
: []),
|
||||
],
|
||||
}));
|
||||
}
|
||||
refresh() {
|
||||
refresh(subset?: string[]) {
|
||||
if (!this.presence) return;
|
||||
for (const id of this.presence.ids()) {
|
||||
for (const id of subset ?? this.presence.ids()) {
|
||||
if (!this.presence.online(id)) continue;
|
||||
const g = this.db.select().from(s.guests).where(eq(s.guests.id, id)).get();
|
||||
if (!g || g.revokedAt || g.expiresAt <= Date.now()) {
|
||||
this.presence.revoke(id);
|
||||
@@ -384,6 +406,7 @@ export class Service {
|
||||
})
|
||||
.run();
|
||||
});
|
||||
this.nearby.clearGuest(g.id);
|
||||
this.restrictTransfers();
|
||||
this.refresh();
|
||||
return { space: this.space(g, id), code, token };
|
||||
@@ -431,6 +454,7 @@ export class Service {
|
||||
})
|
||||
.run();
|
||||
});
|
||||
this.nearby.clearGuest(g.id);
|
||||
this.restrictTransfers();
|
||||
this.refresh();
|
||||
return { space: this.space(g, row!.id) };
|
||||
@@ -638,7 +662,20 @@ export class Service {
|
||||
if (row.recipientGuestId !== g.id) fail(403, 'FORBIDDEN', '수신 기기만 수락할 수 있습니다.');
|
||||
if (row.status !== 'pending' || row.expiresAt <= Date.now())
|
||||
fail(409, 'REQUEST_STATE', '수락할 수 없는 요청입니다.');
|
||||
if (!this.canReceive(row.senderGuestId, g.id))
|
||||
const permission = this.locationAcceptances.get(id);
|
||||
const reserved =
|
||||
this.cfg.locationDiscoveryEnabled &&
|
||||
permission?.guestId === g.id &&
|
||||
permission.until > Date.now() &&
|
||||
!this.activeRoom(g.id) &&
|
||||
!this.activeRoom(row.senderGuestId) &&
|
||||
this.presence.active(g.id) &&
|
||||
this.presence.active(row.senderGuestId) &&
|
||||
this.device(g.id).discoverable &&
|
||||
this.device(row.senderGuestId).discoverable &&
|
||||
!this.isBlocked(row.senderGuestId, g.id);
|
||||
this.locationAcceptances.delete(id);
|
||||
if (!this.canReceive(row.senderGuestId, g.id) && !reserved)
|
||||
fail(409, 'TARGET_UNAVAILABLE', '현재 연결 상태에서 수락할 수 없습니다.');
|
||||
if (row.kind === 'file' && !this.vault.source(id)) {
|
||||
const sender = this.db
|
||||
|
||||
Reference in New Issue
Block a user