Skip to content

Commit 010f265

Browse files
committed
Add doctor-side Contact support: CollectorInvite.toContactSource, AppManagingAccount.getContacts
- CollectorInvite: patientUsername getter (from apiEndpoint URL), toContactSource() - Contact: invites[] array + addInvite() for doctor-side collector+invite pairs - ContactInvite type exported - AppManagingAccount.getContacts(): groups invites by patient username across all collectors - Error-tolerant: skips collectors that fail to load
1 parent cde9a67 commit 010f265

4 files changed

Lines changed: 93 additions & 1 deletion

File tree

ts/appTemplates/AppManagingAccount.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import ShortUniqueId from 'short-unique-id';
22
import { Application } from './Application.ts';
33
import { Collector } from './Collector.ts';
4+
import { Contact } from './Contact.ts';
5+
import type { ContactSource } from './interfaces.ts';
46

57
const collectorIdGenerator = new ShortUniqueId({ dictionary: 'alphanum_lower', length: 7 });
68

@@ -62,6 +64,46 @@ export class AppManagingAccount extends Application {
6264
this.cache.collectorsMap = collectorsMap;
6365
}
6466

67+
/**
68+
* Get all patient contacts grouped by username, across all collectors.
69+
* Each Contact may have invites from multiple forms.
70+
*/
71+
async getContacts (forceRefresh: boolean = false): Promise<Contact[]> {
72+
const collectors = await this.getCollectors(forceRefresh);
73+
const sources: ContactSource[] = [];
74+
75+
// Collect all invites from all collectors (with error tolerance)
76+
const allInvitePairs: Array<{ collector: any; invite: any }> = [];
77+
for (const collector of collectors) {
78+
try {
79+
await collector.init(forceRefresh);
80+
const invites = await collector.getInvites(forceRefresh);
81+
for (const invite of invites) {
82+
sources.push(invite.toContactSource());
83+
allInvitePairs.push({ collector, invite });
84+
}
85+
} catch (e) {
86+
// Skip collectors that fail to load (e.g. network issues)
87+
console.error('Contact: failed loading collector', collector.name, e);
88+
}
89+
}
90+
91+
// Group by patient username
92+
const contacts = Contact.groupByContact(sources);
93+
94+
// Enrich contacts with collector+invite references (no extra API calls — reuse cached invites)
95+
for (const contact of contacts) {
96+
for (const { collector, invite } of allInvitePairs) {
97+
const username = invite.patientUsername;
98+
if (username && username === contact.remoteUsername) {
99+
contact.addInvite(collector, invite);
100+
}
101+
}
102+
}
103+
104+
return contacts;
105+
}
106+
65107
/**
66108
* Create an initialized Collector
67109
*/

ts/appTemplates/CollectorInvite.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { pryv } from '../patchedPryv.ts';
22
import { HDSLibError } from '../errors.ts';
3+
import type { ContactSource } from './interfaces.ts';
34

45
/**
56
* Collector Invite
@@ -105,6 +106,36 @@ export class CollectorInvite {
105106
return this.eventData.content.name;
106107
}
107108

109+
/** Extract patient username from apiEndpoint (only available for active invites) */
110+
get patientUsername (): string | null {
111+
if (this.status !== 'active') return null;
112+
try {
113+
const endpoint = this.eventData.content.apiEndpoint;
114+
if (!endpoint) return null;
115+
// apiEndpoint format: https://token@host/username/
116+
const url = new URL(endpoint.replace(/\/\/[^@]+@/, '//'));
117+
const path = url.pathname.replace(/^\/|\/$/g, '');
118+
return path || null;
119+
} catch {
120+
return null;
121+
}
122+
}
123+
124+
/** Convert to ContactSource for Contact grouping (doctor side) */
125+
toContactSource (): ContactSource {
126+
const chat = this.hasChat ? this.chatSettings : null;
127+
return {
128+
remoteUsername: this.patientUsername,
129+
displayName: this.displayName || this.patientUsername || 'Unknown',
130+
chatStreams: chat ? { main: chat.streamRead, incoming: chat.streamWrite } : null,
131+
appStreamId: null,
132+
permissions: [],
133+
status: this.status,
134+
type: 'collector',
135+
accessId: this.key
136+
};
137+
}
138+
108139
constructor (collector: any, eventData: any) {
109140
if (eventData.type !== 'invite/collector-v1') throw new HDSLibError('Wrong type of event', eventData);
110141
this.collector = collector;

ts/appTemplates/Contact.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
import type { ContactSource, ChatStreams, Permission, CollectorSectionInterface } from './interfaces.ts';
22
import type { CollectorClient } from './CollectorClient.ts';
3+
import type { CollectorInvite } from './CollectorInvite.ts';
4+
import type { Collector } from './Collector.ts';
35
import { HDSModelAppStreams } from '../HDSModel/HDSModel-AppStreams.ts';
46
import { getStreamIdAndChildrenIds } from '../toolkit/StreamsTools.ts';
57
import { pryv } from '../patchedPryv.ts';
68

9+
/** Doctor-side: a form invite pair (which collector + which invite) */
10+
export interface ContactInvite {
11+
collector: Collector;
12+
invite: CollectorInvite;
13+
}
14+
715
/**
816
* Groups all accesses/relationships from the same remote user (or service).
917
*
@@ -19,8 +27,10 @@ export class Contact {
1927
displayName: string;
2028
/** All access sources grouped into this contact */
2129
sources: ContactSource[];
22-
/** CollectorClient instances for collector sources (matched by accessId) */
30+
/** CollectorClient instances for collector sources — patient side */
2331
collectorClients: CollectorClient[];
32+
/** Doctor-side: collector+invite pairs for this patient */
33+
invites: ContactInvite[];
2434
/** Raw Pryv access objects for all sources */
2535
accessObjects: any[];
2636

@@ -32,6 +42,7 @@ export class Contact {
3242
this.displayName = displayName;
3343
this.sources = [];
3444
this.collectorClients = [];
45+
this.invites = [];
3546
this.accessObjects = [];
3647
this.#accessibleStreamIds = null;
3748
}
@@ -57,6 +68,13 @@ export class Contact {
5768
}
5869
}
5970

71+
/** Associate a collector+invite pair (doctor side) */
72+
addInvite (collector: Collector, invite: CollectorInvite): void {
73+
if (!this.invites.find(i => i.invite.key === invite.key)) {
74+
this.invites.push({ collector, invite });
75+
}
76+
}
77+
6078
// ---- Stream cache & event filtering ---- //
6179

6280
/**

ts/appTemplates/appTemplates.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ import { CollectorClient } from './CollectorClient.ts';
66
import { CollectorInvite } from './CollectorInvite.ts';
77
import { CollectorRequest } from './CollectorRequest.ts';
88
import { Contact } from './Contact.ts';
9+
export type { ContactInvite } from './Contact.ts';
910
export { AppManagingAccount, AppClientAccount, Application, Collector, CollectorClient, CollectorInvite, CollectorRequest, Contact };

0 commit comments

Comments
 (0)