feat: stateless federated identity resolver + federation UX improvements
Add identity.ts with isSelf() and resolveDisplayIdentity() — pure stateless functions that detect replicated-self using the immutable (username, homeInstance) composite key. No store lookups, no data mutation. Fixes wrong avatar gradient and missing edit/delete on own messages in remote channels. Also includes: optimistic message dedup fix for cross-instance messages (content-only matching), federation toast notifications, Username component with @domain display, invite parser, and deploy script simplification.
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Parse invite input into a code and optional remote origin.
|
||||
*
|
||||
* Supported formats:
|
||||
* - Bare code: "a3f1b2c4"
|
||||
* - Full URL: "https://remote.com/join/a3f1b2c4"
|
||||
* - Qualified code: "a3f1b2c4@remote.com"
|
||||
*/
|
||||
export function parseInviteInput(input: string): { code: string; origin?: string } {
|
||||
const trimmed = input.trim();
|
||||
if (!trimmed) throw new Error('Invite code is required');
|
||||
|
||||
// Full URL: starts with http:// or https://
|
||||
if (/^https?:\/\//i.test(trimmed)) {
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(trimmed);
|
||||
} catch {
|
||||
throw new Error('Invalid invite link');
|
||||
}
|
||||
|
||||
// Extract code from /join/{code} path
|
||||
const match = parsed.pathname.match(/^\/join\/([^/]+)$/);
|
||||
if (!match) {
|
||||
throw new Error('Invalid invite link — expected format: https://instance/join/CODE');
|
||||
}
|
||||
|
||||
const code = match[1]!;
|
||||
|
||||
// If the URL points at our own instance, treat as a bare code
|
||||
if (parsed.origin === window.location.origin) {
|
||||
return { code };
|
||||
}
|
||||
|
||||
return { code, origin: parsed.origin };
|
||||
}
|
||||
|
||||
// Qualified code: CODE@domain (contains @ but no spaces, no protocol)
|
||||
if (trimmed.includes('@') && !trimmed.includes(' ')) {
|
||||
const atIndex = trimmed.indexOf('@');
|
||||
const code = trimmed.slice(0, atIndex);
|
||||
const domain = trimmed.slice(atIndex + 1);
|
||||
|
||||
if (!code || !domain) {
|
||||
throw new Error('Invalid invite format — expected: CODE@domain');
|
||||
}
|
||||
|
||||
const origin = `https://${domain}`;
|
||||
|
||||
// If it resolves to our own instance, treat as bare code
|
||||
try {
|
||||
if (new URL(origin).origin === window.location.origin) {
|
||||
return { code };
|
||||
}
|
||||
} catch {
|
||||
throw new Error('Invalid domain in invite');
|
||||
}
|
||||
|
||||
return { code, origin };
|
||||
}
|
||||
|
||||
// Bare code
|
||||
return { code: trimmed };
|
||||
}
|
||||
Reference in New Issue
Block a user