@oxy.so/core 1.10.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/crypto/deviceBackup.js +75 -0
- package/dist/cjs/crypto/keyManager.js +285 -12
- package/dist/cjs/mixins/OxyServices.linkedAccounts.js +79 -0
- package/dist/cjs/mixins/OxyServices.user.js +5 -1
- package/dist/cjs/mixins/index.js +4 -0
- package/dist/cjs/server/securityHeaders.js +3 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/crypto/deviceBackup.js +70 -0
- package/dist/esm/crypto/keyManager.js +252 -12
- package/dist/esm/mixins/OxyServices.linkedAccounts.js +76 -0
- package/dist/esm/mixins/OxyServices.user.js +5 -1
- package/dist/esm/mixins/index.js +4 -0
- package/dist/esm/server/securityHeaders.js +3 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/crypto/deviceBackup.d.ts +60 -0
- package/dist/types/crypto/keyManager.d.ts +79 -12
- package/dist/types/index.d.ts +1 -0
- package/dist/types/mixins/OxyServices.linkedAccounts.d.ts +107 -0
- package/dist/types/mixins/OxyServices.user.d.ts +7 -2
- package/dist/types/mixins/index.d.ts +2 -1
- package/dist/types/models/interfaces.d.ts +30 -0
- package/dist/types/server/securityHeaders.d.ts +5 -0
- package/package.json +2 -2
- package/src/crypto/__tests__/keyManager.deviceBackup.test.ts +310 -0
- package/src/crypto/deviceBackup.ts +108 -0
- package/src/crypto/keyManager.ts +278 -13
- package/src/index.ts +1 -0
- package/src/mixins/OxyServices.linkedAccounts.ts +129 -0
- package/src/mixins/OxyServices.user.ts +7 -2
- package/src/mixins/__tests__/linkedAccounts.test.ts +109 -0
- package/src/mixins/index.ts +6 -0
- package/src/models/interfaces.ts +30 -1
- package/src/server/__tests__/securityHeaders.test.ts +7 -0
- package/src/server/securityHeaders.ts +8 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The device backup of the self-custody identity: a copy that lives OUTSIDE this
|
|
3
|
+
* app's own keystore, so a keystore wipe cannot take it along with the keys.
|
|
4
|
+
*
|
|
5
|
+
* Why it exists (OxyHQ/oxy#1388): on Android every Oxy app shares the Linux UID
|
|
6
|
+
* `so.oxy.shared`, and the Android Keystore is per UID. Clearing the storage of
|
|
7
|
+
* ANY sibling app (`pm clear`, or Settings › Apps › Storage › Clear storage)
|
|
8
|
+
* wipes the Keystore of the whole UID. Every copy the SDK keeps in
|
|
9
|
+
* expo-secure-store (primary, backup slot, phrase slot) and in the androidx
|
|
10
|
+
* encrypted prefs (the cross-app shared slot) is wrapped by a key in that
|
|
11
|
+
* Keystore, so they all die together, and until now the only way back was the
|
|
12
|
+
* recovery phrase.
|
|
13
|
+
*
|
|
14
|
+
* `@oxy.so/core` never imports a native module, so the store is injected by the
|
|
15
|
+
* app that owns the identity ({@link KeyManager.setDeviceBackupStore}). Commons
|
|
16
|
+
* registers Android Block Store; every other app registers nothing, which keeps
|
|
17
|
+
* every code path below a no-op for them. The design and the threat model are in
|
|
18
|
+
* `docs/identity/device-backup.md`.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Where the device backup is kept. Implementations must hold the value outside
|
|
22
|
+
* the calling app's keystore (that is the whole point) and must never send it
|
|
23
|
+
* anywhere Oxy can read it.
|
|
24
|
+
*
|
|
25
|
+
* `read` resolves `null` when nothing is stored AND when the store cannot be
|
|
26
|
+
* reached (no Google Play services, an old binary without the native module):
|
|
27
|
+
* the caller treats both as "no device backup" and falls back to the phrase.
|
|
28
|
+
*/
|
|
29
|
+
export interface IdentityDeviceBackupStore {
|
|
30
|
+
/** Short name for logs, e.g. `android-block-store`. Never contains secrets. */
|
|
31
|
+
readonly name: string;
|
|
32
|
+
read(): Promise<string | null>;
|
|
33
|
+
write(value: string): Promise<void>;
|
|
34
|
+
clear(): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
/** The record format. Bump {@link DEVICE_BACKUP_VERSION} on a breaking change. */
|
|
37
|
+
export declare const DEVICE_BACKUP_VERSION = 1;
|
|
38
|
+
export interface DeviceBackupRecord {
|
|
39
|
+
version: typeof DEVICE_BACKUP_VERSION;
|
|
40
|
+
/** Canonical lowercase 64-hex secp256k1 private key. */
|
|
41
|
+
privateKey: string;
|
|
42
|
+
/** Canonical lowercase public key. */
|
|
43
|
+
publicKey: string;
|
|
44
|
+
/**
|
|
45
|
+
* The BIP-39 phrase, present only when it was verified to derive
|
|
46
|
+
* {@link publicKey}. Carried so a restored identity can still reveal its
|
|
47
|
+
* phrase in Settings and create the phrase-keyed Oxy backup; the phrase is
|
|
48
|
+
* never more powerful than the private key already in the record.
|
|
49
|
+
*/
|
|
50
|
+
mnemonic?: string;
|
|
51
|
+
/** ISO-8601 time of the last write. */
|
|
52
|
+
updatedAt: string;
|
|
53
|
+
}
|
|
54
|
+
export declare function serializeDeviceBackup(record: DeviceBackupRecord): string;
|
|
55
|
+
/**
|
|
56
|
+
* Parse a stored record, or `null` for anything that is not a well-formed
|
|
57
|
+
* version-1 record. Checks SHAPE only; the caller still proves the key pair is
|
|
58
|
+
* healthy (well-formed, public derives from private) before trusting it.
|
|
59
|
+
*/
|
|
60
|
+
export declare function parseDeviceBackup(raw: string | null | undefined): DeviceBackupRecord | null;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Private keys are stored securely using expo-secure-store and never leave the device.
|
|
6
6
|
*/
|
|
7
7
|
import { type IdentityMarker } from './identityMarker';
|
|
8
|
+
import { type IdentityDeviceBackupStore } from './deviceBackup';
|
|
8
9
|
/**
|
|
9
10
|
* Thrown when an identity-mutating operation (createIdentity / importKeyPair)
|
|
10
11
|
* is invoked while a valid identity already exists on the device.
|
|
@@ -79,10 +80,14 @@ export type IdentityStatus = {
|
|
|
79
80
|
* which independent, `key_v1`-surviving source restored the identity. On failure
|
|
80
81
|
* `reason` distinguishes "wasn't lost", "no surviving source", "a source held a
|
|
81
82
|
* DIFFERENT account" (never silently switched), and "storage unavailable".
|
|
83
|
+
*
|
|
84
|
+
* `device-backup` is the copy held outside this app's keystore (Android Block
|
|
85
|
+
* Store in Commons, see {@link KeyManager.setDeviceBackupStore}); it is the only
|
|
86
|
+
* source that survives a wipe of the whole shared-UID keystore.
|
|
82
87
|
*/
|
|
83
88
|
export type IdentityRecoveryResult = {
|
|
84
89
|
recovered: true;
|
|
85
|
-
source: 'backup' | 'shared';
|
|
90
|
+
source: 'backup' | 'shared' | 'device-backup';
|
|
86
91
|
publicKey: string;
|
|
87
92
|
} | {
|
|
88
93
|
recovered: false;
|
|
@@ -114,6 +119,23 @@ export declare class KeyManager {
|
|
|
114
119
|
*/
|
|
115
120
|
private static slotMigrationPromise;
|
|
116
121
|
private static slotMigrationResult;
|
|
122
|
+
/**
|
|
123
|
+
* The keystore-independent device backup, registered by the app that owns the
|
|
124
|
+
* identity (Commons). `null` in every other app, which makes every device
|
|
125
|
+
* backup path a no-op there. See `./deviceBackup`.
|
|
126
|
+
*/
|
|
127
|
+
private static deviceBackupStore;
|
|
128
|
+
/**
|
|
129
|
+
* Register (or, with `null`, unregister) where the identity's device backup is
|
|
130
|
+
* kept. Call once at startup, before the first identity read, ONLY from the
|
|
131
|
+
* app that holds the self-custody identity: the backup is a full copy of the
|
|
132
|
+
* private key, and it belongs to exactly one app.
|
|
133
|
+
*
|
|
134
|
+
* From then on every create, import, rotation and restore refreshes the
|
|
135
|
+
* backup, a forced delete clears it, and {@link attemptIdentityRecovery}
|
|
136
|
+
* restores from it when the keystore copies are gone.
|
|
137
|
+
*/
|
|
138
|
+
static setDeviceBackupStore(store: IdentityDeviceBackupStore | null): void;
|
|
117
139
|
/**
|
|
118
140
|
* Invalidate cached identity state
|
|
119
141
|
* Called internally when identity is created/deleted/imported
|
|
@@ -347,6 +369,33 @@ export declare class KeyManager {
|
|
|
347
369
|
* @internal
|
|
348
370
|
*/
|
|
349
371
|
private static _persistIdentityAtomic;
|
|
372
|
+
/**
|
|
373
|
+
* Write the device backup for `(privateKey, publicKey)`, keeping the phrase an
|
|
374
|
+
* existing record already carries for the SAME identity. `mnemonic` replaces
|
|
375
|
+
* it, and must already be verified against `publicKey` by the caller.
|
|
376
|
+
* Best-effort: never throws, returns whether the store now holds this pair.
|
|
377
|
+
*
|
|
378
|
+
* @internal
|
|
379
|
+
*/
|
|
380
|
+
private static _writeDeviceBackup;
|
|
381
|
+
/** Remove the device backup. Best-effort: never throws. @internal */
|
|
382
|
+
private static _clearDeviceBackup;
|
|
383
|
+
/** True when `phrase` is a valid BIP-39 phrase that derives `publicKey`. @internal */
|
|
384
|
+
private static _phraseDerivesPublicKey;
|
|
385
|
+
/** The primary pair when it reads back healthy, else `null`. Never throws. @internal */
|
|
386
|
+
private static _readHealthyPrimary;
|
|
387
|
+
/**
|
|
388
|
+
* Make sure the device backup holds the identity on this device, writing it
|
|
389
|
+
* when it is missing or stale. Commons calls this once per launch once the
|
|
390
|
+
* identity reads back healthy: it backfills identities created before the
|
|
391
|
+
* device backup existed, and repairs a backup whose last write failed (a
|
|
392
|
+
* failed write after a key rotation would otherwise leave the OLD key there).
|
|
393
|
+
*
|
|
394
|
+
* Carries the stored recovery phrase along only when it provably derives the
|
|
395
|
+
* current key. Resolves `true` when the backup is current, `false` when there
|
|
396
|
+
* is no store, no healthy identity, or the write failed. Never throws.
|
|
397
|
+
*/
|
|
398
|
+
static ensureDeviceBackup(): Promise<boolean>;
|
|
350
399
|
/**
|
|
351
400
|
* Write/refresh the identity marker after a successful persist. A same-identity
|
|
352
401
|
* re-persist (e.g. backup refresh, idempotent re-import) preserves `createdAt`
|
|
@@ -534,25 +583,43 @@ export declare class KeyManager {
|
|
|
534
583
|
*/
|
|
535
584
|
static restoreIdentityFromBackup(): Promise<boolean>;
|
|
536
585
|
/**
|
|
537
|
-
* Recovery ladder — restore
|
|
538
|
-
*
|
|
586
|
+
* Recovery ladder — restore an identity whose keys are gone from an
|
|
587
|
+
* independent source WITHOUT the user re-entering their recovery phrase.
|
|
539
588
|
*
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
* `publicKey === marker.publicKey`, so a source holding a DIFFERENT account is
|
|
546
|
-
* SKIPPED, never restored):
|
|
547
|
-
* 1. the v2 backup slot (independent keychain key from the primary), then
|
|
589
|
+
* For a `lost` verdict (marker present, keys empty) the rungs are tried in
|
|
590
|
+
* order, each fully validated (well-formed + derive-match + `publicKey ===
|
|
591
|
+
* marker.publicKey`, so a source holding a DIFFERENT account is SKIPPED, never
|
|
592
|
+
* restored):
|
|
593
|
+
* 1. the v2 backup slot (independent keychain key from the primary),
|
|
548
594
|
* 2. the cross-app shared slot (Android bridge `getShared` / iOS keychain
|
|
549
|
-
* group) — the copy that survives a primary+backup `key_v1` death
|
|
595
|
+
* group) — the copy that survives a primary+backup `key_v1` death,
|
|
596
|
+
* 3. the device backup ({@link setDeviceBackupStore}; Android Block Store in
|
|
597
|
+
* Commons) — the only copy that survives a wipe of the whole shared-UID
|
|
598
|
+
* Android Keystore, which takes rungs 1 and 2 with it (oxy#1388).
|
|
599
|
+
*
|
|
600
|
+
* For an `absent` verdict (no keys AND no marker: this app's own data was
|
|
601
|
+
* cleared, or it was reinstalled) only rung 3 applies: a device backup there
|
|
602
|
+
* means an identity lived on this device and was never deleted (every delete
|
|
603
|
+
* clears the backup), so it is restored instead of letting onboarding create a
|
|
604
|
+
* new identity over it. Without a registered store the result stays
|
|
605
|
+
* `not-lost`, exactly as before.
|
|
606
|
+
*
|
|
607
|
+
* `present` → `not-lost`; `unavailable` (keychain locked) → `unavailable`.
|
|
608
|
+
* Everything here is on-device: no network, so it works offline.
|
|
550
609
|
*
|
|
551
610
|
* On success it re-persists via {@link _persistIdentityAtomic} (origin
|
|
552
611
|
* `'restore'`) and invalidates the cache so routing re-reads `present`. When no
|
|
553
612
|
* rung matches, the UI proceeds to recovery-phrase entry.
|
|
554
613
|
*/
|
|
555
614
|
static attemptIdentityRecovery(): Promise<IdentityRecoveryResult>;
|
|
615
|
+
/** Read the device backup as a healthy, canonical candidate, or null. Never throws. @internal */
|
|
616
|
+
private static _readDeviceBackupCandidate;
|
|
617
|
+
/**
|
|
618
|
+
* Persist a device-backup candidate, then put its phrase back in the
|
|
619
|
+
* re-reveal slot (which died with the keystore) when it derives the key.
|
|
620
|
+
* @internal
|
|
621
|
+
*/
|
|
622
|
+
private static _commitDeviceBackupRecovery;
|
|
556
623
|
/** Read the active-layout backup slot as a healthy candidate, or null. @internal */
|
|
557
624
|
private static _readBackupCandidate;
|
|
558
625
|
/** Read the cross-app shared slot as a healthy candidate, or null. @internal */
|
package/dist/types/index.d.ts
CHANGED
|
@@ -61,6 +61,7 @@ export { KeyManager, IdentityAlreadyExistsError, IdentityPersistError, IdentityU
|
|
|
61
61
|
export type { KeyPair, IdentityStatus, IdentityRecoveryResult } from './crypto/keyManager';
|
|
62
62
|
export { readIdentityMarker, updateIdentityMarker, } from './crypto/identityMarker';
|
|
63
63
|
export type { IdentityMarker } from './crypto/identityMarker';
|
|
64
|
+
export type { IdentityDeviceBackupStore } from './crypto/deviceBackup';
|
|
64
65
|
export { SignatureService } from './crypto/signatureService';
|
|
65
66
|
export type { SignedMessage, AuthChallenge } from './crypto/signatureService';
|
|
66
67
|
export { RecoveryPhraseService } from './crypto/recoveryPhrase';
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linked Accounts Methods Mixin
|
|
3
|
+
*
|
|
4
|
+
* External accounts (any Mastodon-API server, Bluesky) the current user has
|
|
5
|
+
* PROVEN they own by completing an OAuth authorization there. Oxy uses that
|
|
6
|
+
* authorization only to learn which account granted it and then discards the
|
|
7
|
+
* token — nothing here ever carries a third-party token.
|
|
8
|
+
*
|
|
9
|
+
* The flow is a top-level browser navigation: {@link startLinkedAccount} returns
|
|
10
|
+
* the other network's `authorizeUrl`; open it (a new tab, or
|
|
11
|
+
* `expo-web-browser`'s auth session on native); the network sends the browser
|
|
12
|
+
* to Oxy's callback, and Oxy sends it to `returnTo` with `?link_code=<code>` or
|
|
13
|
+
* `?link_error=<code>`. Pass the code to {@link completeLinkedAccount} with the
|
|
14
|
+
* same user signed in: that call, not the callback, creates the link, so an
|
|
15
|
+
* authorization someone else started can never link to them. `returnTo` must
|
|
16
|
+
* be a redirect URI registered on the first-party application `clientId` names.
|
|
17
|
+
*
|
|
18
|
+
* Wire types live in `@oxy.so/contracts` (`linkedAccounts.ts`).
|
|
19
|
+
*/
|
|
20
|
+
import type { LinkedAccount, LinkedAccountNetwork, ServiceLinkedAccountListResponse, StartLinkedAccountRequest, StartLinkedAccountResponse } from '@oxy.so/contracts';
|
|
21
|
+
import type { OxyServicesBase } from '../OxyServices.base';
|
|
22
|
+
export declare function OxyServicesLinkedAccountsMixin<T extends typeof OxyServicesBase>(Base: T): {
|
|
23
|
+
new (...args: any[]): {
|
|
24
|
+
/** Inherited from the auth mixin at runtime. */
|
|
25
|
+
makeServiceRequest: <R = unknown>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: unknown, userId?: string) => Promise<R>;
|
|
26
|
+
/**
|
|
27
|
+
* Begin linking an external account for the current user.
|
|
28
|
+
*
|
|
29
|
+
* @param network - `'activitypub'` (any Mastodon-API server) or `'atproto'` (Bluesky).
|
|
30
|
+
* @param options - `instance` for ActivityPub (`mastodon.social`, or
|
|
31
|
+
* `@user@mastodon.social`), `handle` for atproto; `clientId` + `returnTo`
|
|
32
|
+
* to be sent back to your app afterwards.
|
|
33
|
+
* @returns The URL to open, and when the attempt expires (ten minutes).
|
|
34
|
+
*/
|
|
35
|
+
startLinkedAccount(network: LinkedAccountNetwork, options: StartLinkedAccountRequest): Promise<StartLinkedAccountResponse>;
|
|
36
|
+
/**
|
|
37
|
+
* Finish a link with the `link_code` the callback appended to `returnTo`.
|
|
38
|
+
* Must be the user who started the flow: for anyone else the API answers
|
|
39
|
+
* 403 and burns the code. 409 when the account is already someone else's
|
|
40
|
+
* link. Repeating it within the code's five minutes returns the same link.
|
|
41
|
+
*/
|
|
42
|
+
completeLinkedAccount(code: string): Promise<LinkedAccount>;
|
|
43
|
+
/** The current user's live linked accounts, oldest first. */
|
|
44
|
+
listLinkedAccounts(): Promise<LinkedAccount[]>;
|
|
45
|
+
/**
|
|
46
|
+
* Unlink one of the current user's accounts. An ActivityPub link stops being
|
|
47
|
+
* published as an `alsoKnownAs` alias, and the external account can then be
|
|
48
|
+
* linked to another Oxy account.
|
|
49
|
+
*/
|
|
50
|
+
revokeLinkedAccount(linkedAccountId: string): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* SERVICE: a user's live linked accounts, each with the federated shadow
|
|
53
|
+
* user Oxy already holds for it (`federatedUserId`). Requires a service
|
|
54
|
+
* credential whose application holds the privileged `linked-accounts:read`.
|
|
55
|
+
*/
|
|
56
|
+
getLinkedAccountsForUser(userId: string): Promise<ServiceLinkedAccountListResponse>;
|
|
57
|
+
httpService: import("../HttpService").HttpService;
|
|
58
|
+
cloudURL: string;
|
|
59
|
+
config: import("../OxyServices.base").OxyConfig;
|
|
60
|
+
__resetTokensForTests(): void;
|
|
61
|
+
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
62
|
+
getBaseURL(): string;
|
|
63
|
+
getClient(): import("../HttpService").HttpService;
|
|
64
|
+
createLinkedClient(config: import("../OxyServices.base").OxyConfig): import("..").LinkedHttpClient;
|
|
65
|
+
getMetrics(): {
|
|
66
|
+
totalRequests: number;
|
|
67
|
+
successfulRequests: number;
|
|
68
|
+
failedRequests: number;
|
|
69
|
+
cacheHits: number;
|
|
70
|
+
cacheMisses: number;
|
|
71
|
+
averageResponseTime: number;
|
|
72
|
+
};
|
|
73
|
+
clearCache(): void;
|
|
74
|
+
clearCacheEntry(key: string): void;
|
|
75
|
+
clearCacheByPrefix(prefix: string): number;
|
|
76
|
+
getCacheStats(): {
|
|
77
|
+
size: number;
|
|
78
|
+
hits: number;
|
|
79
|
+
misses: number;
|
|
80
|
+
hitRate: number;
|
|
81
|
+
};
|
|
82
|
+
getCloudURL(): string;
|
|
83
|
+
setTokens(accessToken: string): void;
|
|
84
|
+
clearTokens(): void;
|
|
85
|
+
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
86
|
+
_cachedUserId: string | null | undefined;
|
|
87
|
+
_cachedAccessToken: string | null;
|
|
88
|
+
getCurrentUserId(): string | null;
|
|
89
|
+
hasValidToken(): boolean;
|
|
90
|
+
getAccessToken(): string | null;
|
|
91
|
+
getAccessTokenExpiry(): number | null;
|
|
92
|
+
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
93
|
+
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
94
|
+
maxRetries?: number;
|
|
95
|
+
retryDelay?: number;
|
|
96
|
+
authTimeoutMs?: number;
|
|
97
|
+
}): Promise<T_1>;
|
|
98
|
+
validate(): Promise<boolean>;
|
|
99
|
+
handleError(error: unknown): Error;
|
|
100
|
+
healthCheck(): Promise<{
|
|
101
|
+
status: string;
|
|
102
|
+
users?: number;
|
|
103
|
+
timestamp?: string;
|
|
104
|
+
[key: string]: any;
|
|
105
|
+
}>;
|
|
106
|
+
};
|
|
107
|
+
} & T;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* User Management Methods Mixin
|
|
3
3
|
*/
|
|
4
|
+
import type { CreateOxyNotificationRequest } from '@oxy.so/contracts';
|
|
4
5
|
import type { User, Notification, NotificationPreferences, UserPreferences, SearchProfilesResponse, PrivacySettings } from '../models/interfaces';
|
|
5
6
|
import type { UserNameResponse, UserProfileUpdate, RecommendationRequest, RecommendationItem, ThemePreference } from '@oxy.so/contracts';
|
|
6
7
|
import type { OxyServicesBase } from '../OxyServices.base';
|
|
@@ -495,9 +496,13 @@ export declare function OxyServicesUserMixin<T extends typeof OxyServicesBase>(B
|
|
|
495
496
|
*/
|
|
496
497
|
getUnreadCount(): Promise<number>;
|
|
497
498
|
/**
|
|
498
|
-
* Create notification
|
|
499
|
+
* Create a notification (`POST /notifications`; the API requires a service
|
|
500
|
+
* token whose application holds `notifications:write`). `type` is one of
|
|
501
|
+
* `OXY_NOTIFICATION_TYPES` from `@oxy.so/contracts` — `system` for a message
|
|
502
|
+
* from an Oxy service about the recipient's own account, with the recipient
|
|
503
|
+
* as actor and their profile as the entity.
|
|
499
504
|
*/
|
|
500
|
-
createNotification(data: Partial<Notification>): Promise<Notification>;
|
|
505
|
+
createNotification(data: CreateOxyNotificationRequest | Partial<Notification>): Promise<Notification>;
|
|
501
506
|
/**
|
|
502
507
|
* Mark notification as read
|
|
503
508
|
*/
|
|
@@ -34,6 +34,7 @@ import { OxyServicesNodesMixin } from './OxyServices.nodes';
|
|
|
34
34
|
import { OxyServicesFollowGraphMixin } from './OxyServices.followGraph';
|
|
35
35
|
import { OxyServicesInferenceMixin } from './OxyServices.inference';
|
|
36
36
|
import { OxyServicesDeviceBootMixin } from './OxyServices.deviceBoot';
|
|
37
|
+
import { OxyServicesLinkedAccountsMixin } from './OxyServices.linkedAccounts';
|
|
37
38
|
/**
|
|
38
39
|
* Instance shape of every mixin in the pipeline, intersected. The runtime
|
|
39
40
|
* `composeOxyServices()` produces a class whose instances expose all of
|
|
@@ -43,7 +44,7 @@ import { OxyServicesDeviceBootMixin } from './OxyServices.deviceBoot';
|
|
|
43
44
|
* If you add a new mixin to `MIXIN_PIPELINE`, add it here too so its methods
|
|
44
45
|
* are visible without a cast.
|
|
45
46
|
*/
|
|
46
|
-
type AllMixinInstances = InstanceType<ReturnType<typeof OxyServicesAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUserMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesIdentityMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesIdentityBackupMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPrivacyMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLanguageMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPaymentMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesReputationMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAssetsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAccountsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesConnectedAppsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAgencyMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesStoreMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLocationMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAnalyticsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesDevicesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesSecurityMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFeaturesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesTopicsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesContactsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesNotificationsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAppDataMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesCivicMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesChainsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesNodesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFollowGraphMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesInferenceMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesDeviceBootMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUtilityMixin<typeof OxyServicesBase>>>;
|
|
47
|
+
type AllMixinInstances = InstanceType<ReturnType<typeof OxyServicesAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUserMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesIdentityMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesIdentityBackupMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPrivacyMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLanguageMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPaymentMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesReputationMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAssetsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAccountsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesConnectedAppsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAgencyMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesStoreMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLocationMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAnalyticsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesDevicesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesSecurityMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFeaturesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesTopicsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesContactsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesNotificationsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAppDataMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesCivicMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesChainsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesNodesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFollowGraphMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesInferenceMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesDeviceBootMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLinkedAccountsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUtilityMixin<typeof OxyServicesBase>>>;
|
|
47
48
|
/**
|
|
48
49
|
* Constructor type for the fully composed mixin pipeline. Each mixin returns
|
|
49
50
|
* a new constructor that augments its input; reducing across the pipeline
|
|
@@ -99,6 +99,13 @@ export interface User {
|
|
|
99
99
|
* lists, profile search) — the two are separate fields on the User document.
|
|
100
100
|
*/
|
|
101
101
|
description?: string;
|
|
102
|
+
/**
|
|
103
|
+
* ActivityPub actor URIs this account is also known as — its live,
|
|
104
|
+
* OAuth-proven linked Mastodon-API accounts. Emitted by
|
|
105
|
+
* `GET /profiles/username/:username`; a relying app passes it to the shared
|
|
106
|
+
* actor builder (`@oxy.so/federation`) so its actor publishes it.
|
|
107
|
+
*/
|
|
108
|
+
alsoKnownAs?: string[];
|
|
102
109
|
phone?: string;
|
|
103
110
|
address?: string;
|
|
104
111
|
/** Legacy free-text birthday, kept for backward compatibility. Prefer `dateOfBirth`. */
|
|
@@ -237,6 +244,22 @@ export interface LoginResponse {
|
|
|
237
244
|
export interface Notification {
|
|
238
245
|
id: string;
|
|
239
246
|
message: string;
|
|
247
|
+
/** One of `OXY_NOTIFICATION_TYPES` (`@oxy.so/contracts`). */
|
|
248
|
+
type?: string;
|
|
249
|
+
/**
|
|
250
|
+
* `system` notifications only (with `message`): the text an Oxy service sent
|
|
251
|
+
* about the recipient's own account. Render these two instead of composing a
|
|
252
|
+
* sentence from actor + entity.
|
|
253
|
+
*/
|
|
254
|
+
title?: string;
|
|
255
|
+
/** `system` notifications only: an optional deep link (https or the sending app's scheme). */
|
|
256
|
+
url?: string;
|
|
257
|
+
/**
|
|
258
|
+
* What `entityId` names: `post` | `reply` | `profile`, or `app` — an opaque id
|
|
259
|
+
* in the notifying application's namespace (`system` notifications only).
|
|
260
|
+
*/
|
|
261
|
+
entityType?: string;
|
|
262
|
+
entityId?: string;
|
|
240
263
|
}
|
|
241
264
|
export interface Wallet {
|
|
242
265
|
id: string;
|
|
@@ -594,6 +617,13 @@ export interface ServiceAssetMetadata {
|
|
|
594
617
|
* play. Treat absence as "no adaptive stream, play the progressive original".
|
|
595
618
|
*/
|
|
596
619
|
hlsReadyAt?: string;
|
|
620
|
+
/**
|
|
621
|
+
* The Oxy user who owns the file (`null` for a system-owned file such as the
|
|
622
|
+
* federated media cache). PRESENT ONLY for Oxy's own applications (service
|
|
623
|
+
* tokens with `tier: 'internal'`), so a first-party service can check that an
|
|
624
|
+
* asset a user attached is really that user's. Absent for every other caller.
|
|
625
|
+
*/
|
|
626
|
+
ownerUserId?: string | null;
|
|
597
627
|
}
|
|
598
628
|
/**
|
|
599
629
|
* Reverse-lookup asset metadata returned by `POST /assets/service/by-sha256`.
|
|
@@ -161,6 +161,11 @@ export interface OxyPagesHeadersOptions {
|
|
|
161
161
|
* the app — is REMOVED from `script-src` and `connect-src`, so third-party
|
|
162
162
|
* measurement cannot run there even when the zone has Web Analytics enabled.
|
|
163
163
|
* A structural block in the policy the page is served with, not a build flag.
|
|
164
|
+
*
|
|
165
|
+
* It also sends `Cache-Control: no-transform`, which stops the edge from
|
|
166
|
+
* injecting the beacon at all — otherwise every page load logs a CSP
|
|
167
|
+
* violation for a script we never asked for. The rest of the value is Pages'
|
|
168
|
+
* own default, so caching is unchanged.
|
|
164
169
|
*/
|
|
165
170
|
sensitive?: boolean;
|
|
166
171
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxy.so/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
"@oxy.so/telemetry": "^1.1.1",
|
|
119
119
|
"@noble/ciphers": "^1.3.0",
|
|
120
120
|
"@noble/hashes": "^1.8.0",
|
|
121
|
-
"@oxy.so/contracts": "^1.
|
|
121
|
+
"@oxy.so/contracts": "^1.5.0",
|
|
122
122
|
"@oxy.so/protocol": "^1.1.1",
|
|
123
123
|
"@scure/bip39": "^1.6.0",
|
|
124
124
|
"buffer": "^6.0.3",
|