feat(api): add account workspaces and signed content events
This commit is contained in:
1 parent
937b774a93
commit
7b8d61d1ba
63 files changed
+5407
-370
No files matched your search
@@ -3,14 +3,15 @@ import { supportsApiContract } from './contract';
|
||||
|
||||
describe('API contract compatibility', () => {
|
||||
it('accepts compatible SemVer releases in the supported major', () => {
|
||||
expect(supportsApiContract('2.0.0')).toBe(true);
|
||||
expect(supportsApiContract('2.8.2')).toBe(true);
|
||||
expect(supportsApiContract('2.2.0-rc.1')).toBe(true);
|
||||
expect(supportsApiContract('6.0.0')).toBe(true);
|
||||
expect(supportsApiContract('6.8.2')).toBe(true);
|
||||
expect(supportsApiContract('6.2.0-rc.1')).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects a breaking major and malformed versions', () => {
|
||||
expect(supportsApiContract('1.0.0')).toBe(false);
|
||||
expect(supportsApiContract('3.0.0')).toBe(false);
|
||||
expect(supportsApiContract('4.0.0')).toBe(false);
|
||||
expect(supportsApiContract('5.0.0')).toBe(false);
|
||||
expect(supportsApiContract('01.0.0')).toBe(false);
|
||||
expect(supportsApiContract(undefined)).toBe(false);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const SUPPORTED_API_CONTRACT_MAJOR = 2;
|
||||
export const SUPPORTED_API_CONTRACT_MAJOR = 6;
|
||||
|
||||
const semverPattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
||||
|
||||
|
||||
@@ -0,0 +1,422 @@
|
||||
// Generated from backend DTOs; do not edit.
|
||||
// backend-contract-sha256: 654ec761e3481b423296b402497a97f4092ce167e7a2ca51a47d25f0fca4466e
|
||||
|
||||
export interface AccountChannelBody {
|
||||
channel_id: string;
|
||||
created_at: number;
|
||||
payload: string;
|
||||
role: string;
|
||||
}
|
||||
export interface AccountDeviceBody {
|
||||
created_at: number;
|
||||
current: boolean;
|
||||
id: string;
|
||||
last_seen: number;
|
||||
name: string;
|
||||
}
|
||||
export interface AccountDeviceNameRequest {
|
||||
name: string;
|
||||
}
|
||||
export interface AccountDeviceSigningRequest {
|
||||
device_id: string;
|
||||
public_key: string;
|
||||
signature: string;
|
||||
}
|
||||
export interface AccountProfile {
|
||||
avatar?: string | null;
|
||||
displayName: string;
|
||||
modifiedAt: number;
|
||||
username: string;
|
||||
}
|
||||
export interface AccountProfileRequest {
|
||||
avatar?: string | null;
|
||||
displayName: string;
|
||||
}
|
||||
export interface AccountRegisterRequest {
|
||||
public_key: string;
|
||||
salt: string;
|
||||
signing_public_key: string;
|
||||
username: string;
|
||||
vault: string;
|
||||
verifier: string;
|
||||
}
|
||||
export interface AccountSaltBody {
|
||||
salt: string;
|
||||
}
|
||||
export interface AccountSaltRequest {
|
||||
username: string;
|
||||
}
|
||||
export interface AccountSessionBody {
|
||||
token: string;
|
||||
username: string;
|
||||
}
|
||||
export interface AccountSessionRequest {
|
||||
device_id?: string | null;
|
||||
device_name?: string | null;
|
||||
username: string;
|
||||
verifier: string;
|
||||
}
|
||||
export interface AccountVaultBody {
|
||||
revision: number;
|
||||
vault: string;
|
||||
}
|
||||
export interface AccountVaultRequest {
|
||||
revision: number;
|
||||
vault: string;
|
||||
}
|
||||
export interface AppendResult {
|
||||
seq: number;
|
||||
}
|
||||
export interface ChannelHistoryRequest {
|
||||
chatHistory: boolean;
|
||||
filesHistory: boolean;
|
||||
}
|
||||
export interface ChannelInfo {
|
||||
can_manage_structure: boolean;
|
||||
can_upload: boolean;
|
||||
created_at: number;
|
||||
creator_account?: string | null;
|
||||
expires_at: number;
|
||||
id: string;
|
||||
last_seq: number;
|
||||
mode: ChannelMode;
|
||||
oldest_seq: number;
|
||||
password_required: boolean;
|
||||
seats_used: number;
|
||||
settings: ChannelSettings;
|
||||
}
|
||||
export type ChannelMode = 'enc' | 'raw';
|
||||
|
||||
export interface ChannelSettings {
|
||||
allow_guests: boolean;
|
||||
delete_policy: DeletePolicy;
|
||||
file_max_downloads: number;
|
||||
join_policy: JoinPolicy;
|
||||
max_items: number;
|
||||
members_can_upload: boolean;
|
||||
name?: string | null;
|
||||
seats: number;
|
||||
ttl_seconds: number;
|
||||
ui_restrict_copy_download: boolean;
|
||||
}
|
||||
export interface ChannelSettingsPatch {
|
||||
allow_guests?: boolean | null;
|
||||
delete_policy?: DeletePolicy | null;
|
||||
file_max_downloads?: number | null;
|
||||
join_password?: string | null;
|
||||
join_policy?: JoinPolicy | null;
|
||||
max_items?: number | null;
|
||||
members_can_upload?: boolean | null;
|
||||
name?: string | null;
|
||||
seats?: number | null;
|
||||
ttl_seconds?: number | null;
|
||||
ui_restrict_copy_download?: boolean | null;
|
||||
}
|
||||
export interface CreateChannelRequest {
|
||||
id?: string | null;
|
||||
join_password?: string | null;
|
||||
key_package?: string | null;
|
||||
mode?: ChannelMode | null;
|
||||
owner_hash?: string | null;
|
||||
read_hash?: string | null;
|
||||
settings?: ChannelSettings | null;
|
||||
write_hash?: string | null;
|
||||
}
|
||||
export interface CreateShareRequest {
|
||||
id?: string | null;
|
||||
owner_hash?: string | null;
|
||||
read_hash?: string | null;
|
||||
settings?: ShareSettings | null;
|
||||
}
|
||||
export interface CryptoState {
|
||||
access: ChannelAccess[];
|
||||
envelopes: DomainEnvelope[];
|
||||
objects: ObjectEnvelope[];
|
||||
revision: number;
|
||||
}
|
||||
export interface ChannelAccess {
|
||||
channelId: string;
|
||||
chatFromRevision: number;
|
||||
chatRead: boolean;
|
||||
chatWrite: boolean;
|
||||
filesFromRevision: number;
|
||||
filesRead: boolean;
|
||||
filesUpload: boolean;
|
||||
username: string;
|
||||
}
|
||||
export type DeletePolicy = 'owner_only' | 'own' | 'anyone';
|
||||
|
||||
export interface DomainEnvelope {
|
||||
channelId: string;
|
||||
domain: CryptoDomain;
|
||||
epoch: number;
|
||||
payload: string;
|
||||
username: string;
|
||||
}
|
||||
export type CryptoDomain = 'chat' | 'files';
|
||||
|
||||
export interface DomainPublishRequest {
|
||||
channelId: string;
|
||||
domain: CryptoDomain;
|
||||
envelopes: DomainEnvelope[];
|
||||
epoch: number;
|
||||
}
|
||||
export interface ErrorBody {
|
||||
error: string;
|
||||
}
|
||||
export interface FinishShareRequest {
|
||||
manifest_item_id?: string | null;
|
||||
}
|
||||
export interface GoneLogBody {
|
||||
error: string;
|
||||
oldest_seq: number;
|
||||
}
|
||||
export interface InvitationBody {
|
||||
channel_id: string;
|
||||
created_at: number;
|
||||
payload: string;
|
||||
username: string;
|
||||
}
|
||||
export interface InvitationCryptoPlan {
|
||||
batches: CryptoGrantBatch[];
|
||||
channelId: string;
|
||||
username: string;
|
||||
}
|
||||
export interface CryptoGrantBatch {
|
||||
domain: CryptoDomain;
|
||||
epoch: number;
|
||||
recipients: string[];
|
||||
rotate: boolean;
|
||||
}
|
||||
export interface InvitationRequest {
|
||||
domain_envelopes?: DomainEnvelope[] | null;
|
||||
payload: string;
|
||||
username: string;
|
||||
}
|
||||
export type JoinPolicy = 'open' | 'invite';
|
||||
|
||||
export interface JoinRequest {
|
||||
password?: string | null;
|
||||
}
|
||||
export interface KeyPackageRequest {
|
||||
payload: string;
|
||||
}
|
||||
export interface MemberBody {
|
||||
joined_at: number;
|
||||
role: string;
|
||||
username: string;
|
||||
}
|
||||
export interface ObjectEnvelope {
|
||||
authorAccount?: string | null;
|
||||
channelId: string;
|
||||
createdRevision: number;
|
||||
deleteEvent?: SignedContentEventRecord | null;
|
||||
domain: CryptoDomain;
|
||||
epoch: number;
|
||||
event?: SignedContentEventRecord | null;
|
||||
itemId: string;
|
||||
keyEnvelope: string;
|
||||
}
|
||||
export interface PasskeyBeginRequest {
|
||||
username: string;
|
||||
verifier?: string | null;
|
||||
}
|
||||
export interface PasskeyEnvelopeRequest {
|
||||
credential: string;
|
||||
id: string;
|
||||
key_envelope: string;
|
||||
username: string;
|
||||
}
|
||||
export interface PasskeyFinishRequest {
|
||||
credential: string;
|
||||
device_id?: string | null;
|
||||
device_name?: string | null;
|
||||
id: string;
|
||||
key_envelope?: string | null;
|
||||
name?: string | null;
|
||||
username: string;
|
||||
}
|
||||
export interface PasskeyInfoBody {
|
||||
can_recover: boolean;
|
||||
created_at: number;
|
||||
id: string;
|
||||
name: string;
|
||||
prf_salt: string;
|
||||
}
|
||||
export interface PasskeyOptionsBody {
|
||||
credentials: PasskeyInfoBody[];
|
||||
id: string;
|
||||
options: string;
|
||||
prf_salt?: string | null;
|
||||
}
|
||||
export interface PasskeySessionBody {
|
||||
credential_id: string;
|
||||
key_envelope?: string | null;
|
||||
prf_salt: string;
|
||||
token: string;
|
||||
username: string;
|
||||
}
|
||||
export interface PublicKeyBody {
|
||||
public_key: string;
|
||||
signing_public_key?: string | null;
|
||||
}
|
||||
export interface SeatInfo {
|
||||
account_name?: string | null;
|
||||
endpoint_hash: string;
|
||||
joined_at: number;
|
||||
label?: string | null;
|
||||
}
|
||||
export interface SeatJoined {
|
||||
endpoint_hash: string;
|
||||
joined_at: number;
|
||||
seats_max: number;
|
||||
seats_used: number;
|
||||
}
|
||||
export interface ServerInfoBody {
|
||||
captcha: CaptchaBody;
|
||||
choices: SettingChoicesBody;
|
||||
contract_version: string;
|
||||
defaults: DefaultsBody;
|
||||
limits: LimitsBody;
|
||||
protocol: number;
|
||||
transports: TransportsBody;
|
||||
}
|
||||
export interface CaptchaBody {
|
||||
api_endpoint?: string | null;
|
||||
enabled: boolean;
|
||||
provider: string;
|
||||
}
|
||||
export interface DefaultsBody {
|
||||
channel: ChannelSettings;
|
||||
channel_mode: ChannelMode;
|
||||
share: ShareSettings;
|
||||
}
|
||||
export interface LimitsBody {
|
||||
max_channel_bytes: number;
|
||||
max_frames_per_item: number;
|
||||
max_items: number;
|
||||
max_payload_bytes: number;
|
||||
max_seats: number;
|
||||
max_ttl_seconds: number;
|
||||
max_visitors: number;
|
||||
min_ttl_seconds: number;
|
||||
}
|
||||
export interface SettingChoicesBody {
|
||||
file_max_downloads: number[];
|
||||
max_visitors: number[];
|
||||
seats: number[];
|
||||
ttl_seconds: number[];
|
||||
}
|
||||
export interface ShareInfo {
|
||||
created_at: number;
|
||||
expires_at: number;
|
||||
id: string;
|
||||
items: ShareItemInfo[];
|
||||
manifest_item_id?: string | null;
|
||||
sealed_at?: number | null;
|
||||
settings: ShareSettings;
|
||||
state: string;
|
||||
}
|
||||
export interface ShareItemInfo {
|
||||
bytes: number;
|
||||
complete: boolean;
|
||||
downloads_left: number | null;
|
||||
frames: number;
|
||||
item_id: string;
|
||||
max_downloads: number;
|
||||
}
|
||||
export interface ShareSettings {
|
||||
file_max_downloads: number;
|
||||
max_visitors: number;
|
||||
ttl_seconds: number;
|
||||
}
|
||||
export interface ShareSettingsPatch {
|
||||
file_max_downloads?: number | null;
|
||||
max_visitors?: number | null;
|
||||
ttl_seconds?: number | null;
|
||||
}
|
||||
export interface SignedContentEventRecord {
|
||||
deviceCertificateSignature: string;
|
||||
eventId: string;
|
||||
frameCount: number;
|
||||
kind: string;
|
||||
manifestHash: string;
|
||||
signature: string;
|
||||
signerDeviceId: string;
|
||||
signerDevicePublicKey: string;
|
||||
signerRootPublicKey: string;
|
||||
targetItemId?: string | null;
|
||||
}
|
||||
export interface TransportsBody {
|
||||
http3: boolean;
|
||||
sse: boolean;
|
||||
websocket: boolean;
|
||||
}
|
||||
export interface WorkspaceBody {
|
||||
people: AccountProfile[];
|
||||
structure: WorkspaceState;
|
||||
}
|
||||
export interface WorkspaceChannelCreatedBody {
|
||||
channel: WorkspaceChannel;
|
||||
workspace: WorkspaceBody;
|
||||
}
|
||||
export interface WorkspaceChannel {
|
||||
chatEpoch: number;
|
||||
chatHistory: boolean;
|
||||
createdAt: number;
|
||||
createdBy: string;
|
||||
filesEpoch: number;
|
||||
filesHistory: boolean;
|
||||
groupId?: string | null;
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
export interface WorkspaceChannelRequest {
|
||||
groupId?: string | null;
|
||||
name: string;
|
||||
}
|
||||
export interface WorkspaceFolderRequest {
|
||||
name: string;
|
||||
parentId?: string | null;
|
||||
threadId: string;
|
||||
}
|
||||
export interface WorkspaceGroupCreatedBody {
|
||||
group: WorkspaceGroup;
|
||||
workspace: WorkspaceBody;
|
||||
}
|
||||
export interface WorkspaceGroup {
|
||||
createdAt: number;
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
export interface WorkspaceGroupRequest {
|
||||
groupId?: string | null;
|
||||
}
|
||||
export interface WorkspaceMoveRequest {
|
||||
folderId?: string | null;
|
||||
itemIds: string[];
|
||||
threadId: string;
|
||||
}
|
||||
export interface WorkspaceNameRequest {
|
||||
name: string;
|
||||
}
|
||||
export interface WorkspaceState {
|
||||
channels: WorkspaceChannel[];
|
||||
defaultChannelId?: string | null;
|
||||
folders: WorkspaceFolder[];
|
||||
groups: WorkspaceGroup[];
|
||||
locations: WorkspaceLocation[];
|
||||
revision: number;
|
||||
}
|
||||
export interface WorkspaceFolder {
|
||||
id: string;
|
||||
name: string;
|
||||
parentId?: string | null;
|
||||
threadId: string;
|
||||
}
|
||||
export interface WorkspaceLocation {
|
||||
authorAccount?: string | null;
|
||||
folderId?: string | null;
|
||||
itemId: string;
|
||||
threadId: string;
|
||||
}
|
||||
@@ -8,7 +8,7 @@ describe('API contract preflight', () => {
|
||||
});
|
||||
|
||||
it('blocks API requests when the server has a newer breaking major', async () => {
|
||||
const fetchMock = vi.fn(async () => new Response(JSON.stringify({ contract_version: '3.0.0' }), { status: 200 }));
|
||||
const fetchMock = vi.fn(async () => new Response(JSON.stringify({ contract_version: '7.0.0' }), { status: 200 }));
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
setApiBase('http://api.test/api/v1');
|
||||
|
||||
@@ -19,7 +19,7 @@ describe('API contract preflight', () => {
|
||||
|
||||
it('checks the contract once and then sends the API request', async () => {
|
||||
const fetchMock = vi.fn()
|
||||
.mockResolvedValueOnce(new Response(JSON.stringify({ contract_version: '2.2.0' }), { status: 200 }))
|
||||
.mockResolvedValueOnce(new Response(JSON.stringify({ contract_version: '6.0.0' }), { status: 200 }))
|
||||
.mockResolvedValueOnce(new Response('{}', { status: 200 }))
|
||||
.mockResolvedValueOnce(new Response('{}', { status: 200 }));
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
Reference in new issue
Block a user