@oxyhq/core 14.0.0 → 15.0.1
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/recoveryPhrase.js +32 -66
- package/dist/cjs/index.js +7 -0
- package/dist/cjs/mixins/OxyServices.deviceBoot.js +76 -0
- package/dist/cjs/mixins/OxyServices.reputation.js +47 -2
- package/dist/cjs/mixins/OxyServices.user.js +3 -4
- package/dist/cjs/session/accountProjection.js +4 -1
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/crypto/recoveryPhrase.js +32 -33
- package/dist/esm/index.js +7 -0
- package/dist/esm/mixins/OxyServices.deviceBoot.js +77 -1
- package/dist/esm/mixins/OxyServices.reputation.js +47 -2
- package/dist/esm/mixins/OxyServices.user.js +3 -4
- package/dist/esm/session/accountProjection.js +4 -1
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/crypto/recoveryPhrase.d.ts +6 -0
- package/dist/types/index.d.ts +0 -1
- package/dist/types/mixins/OxyServices.deviceBoot.d.ts +61 -1
- package/dist/types/mixins/OxyServices.identityBackup.d.ts +1 -1
- package/dist/types/mixins/OxyServices.reputation.d.ts +43 -276
- package/dist/types/mixins/OxyServices.user.d.ts +4 -1
- package/dist/types/models/interfaces.d.ts +6 -0
- package/package.json +3 -3
- package/src/crypto/__tests__/keyManager.test.ts +3 -2
- package/src/crypto/recoveryPhrase.ts +33 -34
- package/src/index.ts +5 -24
- package/src/mixins/OxyServices.deviceBoot.ts +85 -0
- package/src/mixins/OxyServices.identityBackup.ts +1 -1
- package/src/mixins/OxyServices.reputation.ts +88 -326
- package/src/mixins/OxyServices.user.ts +7 -3
- package/src/mixins/__tests__/OxyServices.deviceBoot.test.ts +59 -2
- package/src/mixins/__tests__/backgroundCredential.httpIntegration.test.ts +135 -0
- package/src/mixins/__tests__/followGraphPagination.test.ts +21 -0
- package/src/mixins/__tests__/reputation.test.ts +115 -1
- package/src/models/interfaces.ts +2 -0
- package/src/session/accountProjection.ts +5 -1
- package/src/types/bip39.d.ts +0 -32
|
@@ -6,43 +6,17 @@
|
|
|
6
6
|
* for backing up and restoring user identities.
|
|
7
7
|
*
|
|
8
8
|
* Note: This module requires the polyfill to be loaded first (done via crypto/index.ts)
|
|
9
|
+
*
|
|
10
|
+
* Oxy recovery phrases are English-only, so the English wordlist is imported by
|
|
11
|
+
* its own subpath. Never reach for a package that exposes its wordlists through
|
|
12
|
+
* a barrel: `bip39`'s `_wordlists` hard-requires all ten languages, which put
|
|
13
|
+
* ~265 KB of unreachable wordlists into the initial chunk of every consuming
|
|
14
|
+
* app. Adding another language means one more subpath import, ideally lazy.
|
|
9
15
|
*/
|
|
10
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
-
if (k2 === undefined) k2 = k;
|
|
12
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
-
}
|
|
16
|
-
Object.defineProperty(o, k2, desc);
|
|
17
|
-
}) : (function(o, m, k, k2) {
|
|
18
|
-
if (k2 === undefined) k2 = k;
|
|
19
|
-
o[k2] = m[k];
|
|
20
|
-
}));
|
|
21
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
-
}) : function(o, v) {
|
|
24
|
-
o["default"] = v;
|
|
25
|
-
});
|
|
26
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
-
var ownKeys = function(o) {
|
|
28
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
-
var ar = [];
|
|
30
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
-
return ar;
|
|
32
|
-
};
|
|
33
|
-
return ownKeys(o);
|
|
34
|
-
};
|
|
35
|
-
return function (mod) {
|
|
36
|
-
if (mod && mod.__esModule) return mod;
|
|
37
|
-
var result = {};
|
|
38
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
-
__setModuleDefault(result, mod);
|
|
40
|
-
return result;
|
|
41
|
-
};
|
|
42
|
-
})();
|
|
43
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
17
|
exports.RecoveryPhraseService = exports.BACKUP_MATERIAL_LENGTH = exports.BACKUP_KDF_LOOKUP_INFO = exports.BACKUP_KDF_ENCRYPTION_INFO = exports.BACKUP_KDF_SALT = void 0;
|
|
45
|
-
const
|
|
18
|
+
const bip39_1 = require("@scure/bip39");
|
|
19
|
+
const english_1 = require("@scure/bip39/wordlists/english");
|
|
46
20
|
const keyManager_1 = require("./keyManager");
|
|
47
21
|
const kdf_1 = require("./kdf");
|
|
48
22
|
/**
|
|
@@ -84,13 +58,12 @@ class RecoveryPhraseService {
|
|
|
84
58
|
*/
|
|
85
59
|
static async generateIdentityWithRecovery(options) {
|
|
86
60
|
// Generate 128-bit entropy for 12-word mnemonic
|
|
87
|
-
const mnemonic =
|
|
61
|
+
const mnemonic = (0, bip39_1.generateMnemonic)(english_1.wordlist, 128);
|
|
88
62
|
// Derive private key from mnemonic
|
|
89
63
|
// Using the seed directly as the private key (simplified approach)
|
|
90
|
-
const seed = await
|
|
64
|
+
const seed = await (0, bip39_1.mnemonicToSeed)(mnemonic);
|
|
91
65
|
// Use first 32 bytes of seed as private key
|
|
92
|
-
const
|
|
93
|
-
const privateKeyHex = toHex(seedSlice);
|
|
66
|
+
const privateKeyHex = toHex(seed.subarray(0, 32));
|
|
94
67
|
// Import the derived key pair. KeyManager.importKeyPair will refuse to
|
|
95
68
|
// clobber an existing identity unless overwrite is explicitly requested.
|
|
96
69
|
const publicKey = await keyManager_1.KeyManager.importKeyPair(privateKeyHex, {
|
|
@@ -109,10 +82,9 @@ class RecoveryPhraseService {
|
|
|
109
82
|
*/
|
|
110
83
|
static async generateIdentityWithRecovery24(options) {
|
|
111
84
|
// Generate 256-bit entropy for 24-word mnemonic
|
|
112
|
-
const mnemonic =
|
|
113
|
-
const seed = await
|
|
114
|
-
const
|
|
115
|
-
const privateKeyHex = toHex(seedSlice);
|
|
85
|
+
const mnemonic = (0, bip39_1.generateMnemonic)(english_1.wordlist, 256);
|
|
86
|
+
const seed = await (0, bip39_1.mnemonicToSeed)(mnemonic);
|
|
87
|
+
const privateKeyHex = toHex(seed.subarray(0, 32));
|
|
116
88
|
const publicKey = await keyManager_1.KeyManager.importKeyPair(privateKeyHex, {
|
|
117
89
|
overwrite: options?.overwrite === true,
|
|
118
90
|
});
|
|
@@ -136,10 +108,9 @@ class RecoveryPhraseService {
|
|
|
136
108
|
* committed anywhere — if it is lost the account becomes unrecoverable.
|
|
137
109
|
*/
|
|
138
110
|
static async derivePendingIdentity() {
|
|
139
|
-
const mnemonic =
|
|
140
|
-
const seed = await
|
|
141
|
-
const
|
|
142
|
-
const privateKey = toHex(seedSlice);
|
|
111
|
+
const mnemonic = (0, bip39_1.generateMnemonic)(english_1.wordlist, 128);
|
|
112
|
+
const seed = await (0, bip39_1.mnemonicToSeed)(mnemonic);
|
|
113
|
+
const privateKey = toHex(seed.subarray(0, 32));
|
|
143
114
|
const publicKey = keyManager_1.KeyManager.derivePublicKey(privateKey);
|
|
144
115
|
return {
|
|
145
116
|
phrase: mnemonic,
|
|
@@ -158,12 +129,11 @@ class RecoveryPhraseService {
|
|
|
158
129
|
*/
|
|
159
130
|
static async derivePrivateKeyFromPhrase(phrase) {
|
|
160
131
|
const normalizedPhrase = phrase.trim().toLowerCase();
|
|
161
|
-
if (!
|
|
132
|
+
if (!(0, bip39_1.validateMnemonic)(normalizedPhrase, english_1.wordlist)) {
|
|
162
133
|
throw new Error('Invalid recovery phrase');
|
|
163
134
|
}
|
|
164
|
-
const seed = await
|
|
165
|
-
|
|
166
|
-
return toHex(seedSlice);
|
|
135
|
+
const seed = await (0, bip39_1.mnemonicToSeed)(normalizedPhrase);
|
|
136
|
+
return toHex(seed.subarray(0, 32));
|
|
167
137
|
}
|
|
168
138
|
/**
|
|
169
139
|
* Derive the encrypted-backup key material from a recovery phrase (b3 Feature
|
|
@@ -184,10 +154,10 @@ class RecoveryPhraseService {
|
|
|
184
154
|
*/
|
|
185
155
|
static async deriveBackupMaterial(phrase) {
|
|
186
156
|
const normalizedPhrase = phrase.trim().toLowerCase();
|
|
187
|
-
if (!
|
|
157
|
+
if (!(0, bip39_1.validateMnemonic)(normalizedPhrase, english_1.wordlist)) {
|
|
188
158
|
throw new Error('Invalid recovery phrase. Please check the words and try again.');
|
|
189
159
|
}
|
|
190
|
-
const seed = await
|
|
160
|
+
const seed = await (0, bip39_1.mnemonicToSeed)(normalizedPhrase);
|
|
191
161
|
const salt = utf8(exports.BACKUP_KDF_SALT);
|
|
192
162
|
const backupKey = (0, kdf_1.hkdfSha256)(seed, salt, utf8(exports.BACKUP_KDF_ENCRYPTION_INFO), exports.BACKUP_MATERIAL_LENGTH);
|
|
193
163
|
const lookupId = toHex((0, kdf_1.hkdfSha256)(seed, salt, utf8(exports.BACKUP_KDF_LOOKUP_INFO), exports.BACKUP_MATERIAL_LENGTH));
|
|
@@ -204,13 +174,12 @@ class RecoveryPhraseService {
|
|
|
204
174
|
static async restoreFromPhrase(phrase, options) {
|
|
205
175
|
// Normalize and validate the phrase
|
|
206
176
|
const normalizedPhrase = phrase.trim().toLowerCase();
|
|
207
|
-
if (!
|
|
177
|
+
if (!(0, bip39_1.validateMnemonic)(normalizedPhrase, english_1.wordlist)) {
|
|
208
178
|
throw new Error('Invalid recovery phrase. Please check the words and try again.');
|
|
209
179
|
}
|
|
210
180
|
// Derive the same private key from the mnemonic
|
|
211
|
-
const seed = await
|
|
212
|
-
const
|
|
213
|
-
const privateKeyHex = toHex(seedSlice);
|
|
181
|
+
const seed = await (0, bip39_1.mnemonicToSeed)(normalizedPhrase);
|
|
182
|
+
const privateKeyHex = toHex(seed.subarray(0, 32));
|
|
214
183
|
// Import and store the key pair
|
|
215
184
|
const publicKey = await keyManager_1.KeyManager.importKeyPair(privateKeyHex, {
|
|
216
185
|
overwrite: options?.overwrite === true,
|
|
@@ -222,28 +191,26 @@ class RecoveryPhraseService {
|
|
|
222
191
|
*/
|
|
223
192
|
static validatePhrase(phrase) {
|
|
224
193
|
const normalizedPhrase = phrase.trim().toLowerCase();
|
|
225
|
-
return
|
|
194
|
+
return (0, bip39_1.validateMnemonic)(normalizedPhrase, english_1.wordlist);
|
|
226
195
|
}
|
|
227
196
|
/**
|
|
228
197
|
* Get the word list for autocomplete/validation
|
|
229
198
|
*/
|
|
230
199
|
static getWordList() {
|
|
231
|
-
return
|
|
200
|
+
return english_1.wordlist;
|
|
232
201
|
}
|
|
233
202
|
/**
|
|
234
203
|
* Check if a word is valid in the BIP39 word list
|
|
235
204
|
*/
|
|
236
205
|
static isValidWord(word) {
|
|
237
|
-
return
|
|
206
|
+
return english_1.wordlist.includes(word.toLowerCase());
|
|
238
207
|
}
|
|
239
208
|
/**
|
|
240
209
|
* Get suggestions for a partial word
|
|
241
210
|
*/
|
|
242
211
|
static getSuggestions(partial, limit = 5) {
|
|
243
212
|
const lowerPartial = partial.toLowerCase();
|
|
244
|
-
return
|
|
245
|
-
.filter((word) => word.startsWith(lowerPartial))
|
|
246
|
-
.slice(0, limit);
|
|
213
|
+
return english_1.wordlist.filter((word) => word.startsWith(lowerPartial)).slice(0, limit);
|
|
247
214
|
}
|
|
248
215
|
/**
|
|
249
216
|
* Derive the public key from a phrase without storing
|
|
@@ -251,12 +218,11 @@ class RecoveryPhraseService {
|
|
|
251
218
|
*/
|
|
252
219
|
static async derivePublicKeyFromPhrase(phrase) {
|
|
253
220
|
const normalizedPhrase = phrase.trim().toLowerCase();
|
|
254
|
-
if (!
|
|
221
|
+
if (!(0, bip39_1.validateMnemonic)(normalizedPhrase, english_1.wordlist)) {
|
|
255
222
|
throw new Error('Invalid recovery phrase');
|
|
256
223
|
}
|
|
257
|
-
const seed = await
|
|
258
|
-
const
|
|
259
|
-
const privateKeyHex = toHex(seedSlice);
|
|
224
|
+
const seed = await (0, bip39_1.mnemonicToSeed)(normalizedPhrase);
|
|
225
|
+
const privateKeyHex = toHex(seed.subarray(0, 32));
|
|
260
226
|
return keyManager_1.KeyManager.derivePublicKey(privateKeyHex);
|
|
261
227
|
}
|
|
262
228
|
/**
|
package/dist/cjs/index.js
CHANGED
|
@@ -67,6 +67,13 @@ Object.defineProperty(exports, "normalizeProfileLinks", { enumerable: true, get:
|
|
|
67
67
|
var OxyServices_accounts_1 = require("./mixins/OxyServices.accounts");
|
|
68
68
|
Object.defineProperty(exports, "ORGANIZATION_CATEGORIES", { enumerable: true, get: function () { return OxyServices_accounts_1.ORGANIZATION_CATEGORIES; } });
|
|
69
69
|
// ---------------------------------------------------------------------------
|
|
70
|
+
// Reputation (Oxy Trust: ledger, balances, disputes, rules, influence).
|
|
71
|
+
// The whole type family — the closed value sets, the two balance views and the
|
|
72
|
+
// `isFullReputationBalance` narrowing guard, the ledger/dispute/rule/leaderboard
|
|
73
|
+
// shapes, and the write-endpoint inputs — is owned by `@oxyhq/contracts`, which
|
|
74
|
+
// the API's serializers are validated against. Import them from there.
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
70
77
|
// Self-sovereign identity (DID, signed records, auth-method ↔ VM mapping,
|
|
71
78
|
// verified domains). Wire shapes (DidDocument, SignedRecordEnvelope,
|
|
72
79
|
// AuthMethodsResponse, VerifiedDomain, DomainVerificationInstructions,
|
|
@@ -16,8 +16,14 @@ exports.OxyServicesDeviceBootMixin = OxyServicesDeviceBootMixin;
|
|
|
16
16
|
* This method carries NO persistence or token-planting side effects of its own;
|
|
17
17
|
* the cold boot / re-mint handler own persistence and `setTokens`, so the same
|
|
18
18
|
* primitive can be reused from either without double-planting.
|
|
19
|
+
*
|
|
20
|
+
* `provisionBackgroundCredential` is the mixin's second, adjacent call: it hands
|
|
21
|
+
* native background code (no JS runtime) its own non-rotating credential so that
|
|
22
|
+
* code never has to mint from — and therefore never rotates — the device secret
|
|
23
|
+
* JS depends on.
|
|
19
24
|
*/
|
|
20
25
|
const contracts_1 = require("@oxyhq/contracts");
|
|
26
|
+
const errorUtils_1 = require("../utils/errorUtils");
|
|
21
27
|
/**
|
|
22
28
|
* The server's `401 account_not_on_device` for a PINNED mint: the requested
|
|
23
29
|
* `accountId` is not (or is no longer) a live account of this device session.
|
|
@@ -110,5 +116,75 @@ function OxyServicesDeviceBootMixin(Base) {
|
|
|
110
116
|
throw normalized;
|
|
111
117
|
}
|
|
112
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Provision a NON-rotating background credential for the caller's account on
|
|
121
|
+
* this device — the credential native background code (an Android widget
|
|
122
|
+
* worker, which runs with no JS runtime) presents to mint its own access
|
|
123
|
+
* tokens without any JS involvement.
|
|
124
|
+
*
|
|
125
|
+
* It exists precisely so background code never touches the device secret:
|
|
126
|
+
* `POST /session/device/token` ROTATES that secret on every mint (the
|
|
127
|
+
* presented one stays valid only for a short grace), so a worker minting
|
|
128
|
+
* from it would become a second writer of the value JS depends on and could
|
|
129
|
+
* silently sign the user out. The background credential is a separate,
|
|
130
|
+
* non-rotating value minted server-side, so the two lanes never contend.
|
|
131
|
+
*
|
|
132
|
+
* Bearer required and NO body: the server derives both the `deviceId` and
|
|
133
|
+
* the account from the validated bearer. This is the only way a background
|
|
134
|
+
* credential comes into existence, so background code can EXTEND a session
|
|
135
|
+
* the user established in-app but can never bootstrap one from nothing.
|
|
136
|
+
*
|
|
137
|
+
* Unlike the mint above this is NOT a control-plane call — it runs while a
|
|
138
|
+
* session is already live — so it takes the normal authenticated path: no
|
|
139
|
+
* `skipAuth` (a 401 should go through the ordinary re-mint lane) and no
|
|
140
|
+
* `bypassQueue` (nothing in the auth lane is parked awaiting it).
|
|
141
|
+
*
|
|
142
|
+
* There is deliberately no JS counterpart that MINTS from the returned
|
|
143
|
+
* credential: the native side owns that call, and a symmetric-looking JS
|
|
144
|
+
* method would be dead code plus a second implementation of the failure
|
|
145
|
+
* rules. The asymmetry is the design.
|
|
146
|
+
*
|
|
147
|
+
* **Call this from NATIVE only.** There is no background worker on web to
|
|
148
|
+
* consume the credential, and handing a browser origin a long-lived
|
|
149
|
+
* non-rotating secret to persist is strictly weaker than the rotating device
|
|
150
|
+
* secret it already holds. The 404 degrade below is also native-shaped: a
|
|
151
|
+
* browser attaches `Origin`, which a server predating this route answers
|
|
152
|
+
* `403 BAD_ORIGIN` from its router-wide same-site guard rather than 404, so
|
|
153
|
+
* the quiet degrade would not fire there. A native client sends no `Origin`
|
|
154
|
+
* and gets the 404. Gate the caller by platform; do not widen the degrade to
|
|
155
|
+
* 403, which would also swallow a genuine origin misconfiguration.
|
|
156
|
+
*
|
|
157
|
+
* That paragraph is LOAD-BEARING, not belt-and-braces: the route sits above
|
|
158
|
+
* oxy-api's router-wide origin guard (deliberately, so a native client with
|
|
159
|
+
* no `Origin` is not rejected), so as of this writing NOTHING server-side
|
|
160
|
+
* refuses a browser caller that presents a valid bearer. Until a server-side
|
|
161
|
+
* check lands, caller discipline is the only control — which is also why a
|
|
162
|
+
* doc note cannot be the whole answer to browser XSS minting a long-lived
|
|
163
|
+
* credential with the victim's bearer.
|
|
164
|
+
*
|
|
165
|
+
* @returns the provisioned credential, or `null` when the endpoint is absent
|
|
166
|
+
* (404). The API deploy leads the SDK release, so a client on a newer SDK
|
|
167
|
+
* than the server degrades to "no background session" quietly instead of
|
|
168
|
+
* surfacing an error. The status is read off the RAW rejection because
|
|
169
|
+
* `handleError` only preserves it for errors carrying `HttpService`'s
|
|
170
|
+
* annotations.
|
|
171
|
+
* @throws if the response does not match {@link deviceBackgroundCredentialResponseSchema}.
|
|
172
|
+
*/
|
|
173
|
+
async provisionBackgroundCredential() {
|
|
174
|
+
try {
|
|
175
|
+
const res = await this.makeRequest('POST', '/session/device/background-credential', undefined, { cache: false });
|
|
176
|
+
const parsed = (0, contracts_1.safeParseContract)(contracts_1.deviceBackgroundCredentialResponseSchema, res);
|
|
177
|
+
if (!parsed) {
|
|
178
|
+
throw new Error('session/device/background-credential returned an unexpected response shape');
|
|
179
|
+
}
|
|
180
|
+
return parsed;
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
if ((0, errorUtils_1.extractErrorStatus)(error) === 404) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
throw this.handleError(error);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
113
189
|
};
|
|
114
190
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.OxyServicesReputationMixin = OxyServicesReputationMixin;
|
|
4
|
+
const contracts_1 = require("@oxyhq/contracts");
|
|
5
|
+
const OxyServices_errors_1 = require("../OxyServices.errors");
|
|
4
6
|
const mixinHelpers_1 = require("./mixinHelpers");
|
|
5
7
|
/** Cache-key prefix for every cached `GET /reputation/...` response. */
|
|
6
8
|
const REPUTATION_CACHE_PREFIX = 'GET:/reputation/';
|
|
@@ -10,8 +12,16 @@ function OxyServicesReputationMixin(Base) {
|
|
|
10
12
|
super(...args);
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
|
-
* Get
|
|
14
|
-
*
|
|
15
|
+
* Get ANY user's reputation balance, in whichever view the server serves the
|
|
16
|
+
* caller.
|
|
17
|
+
*
|
|
18
|
+
* A third party gets `userId`, `total` and `trustTier` and nothing else, so
|
|
19
|
+
* the return type is a {@link ReputationBalanceView} union: narrow it with
|
|
20
|
+
* {@link isFullReputationBalance} before touching `breakdown`, `influence`
|
|
21
|
+
* or `reliability`. To read your OWN balance, call
|
|
22
|
+
* {@link getMyReputationBalance} instead — it returns the full shape with no
|
|
23
|
+
* narrowing.
|
|
24
|
+
*
|
|
15
25
|
* @param userId - The subject user's `_id` or publicKey.
|
|
16
26
|
*/
|
|
17
27
|
async getReputationBalance(userId) {
|
|
@@ -22,6 +32,37 @@ function OxyServicesReputationMixin(Base) {
|
|
|
22
32
|
throw this.handleError(error);
|
|
23
33
|
}
|
|
24
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Get the SIGNED-IN user's own reputation balance, in full.
|
|
37
|
+
*
|
|
38
|
+
* The subject view is the only one carrying `breakdown`, `influence` and
|
|
39
|
+
* `reliability`, and the subject is the common caller, so this is the
|
|
40
|
+
* ergonomic path: no id to pass, no narrowing to do.
|
|
41
|
+
*
|
|
42
|
+
* Throws rather than returning a half-populated object when the request was
|
|
43
|
+
* not authenticated as the subject — with no signed-in user, and when the
|
|
44
|
+
* server answered `200` with the public view anyway (which it does for an
|
|
45
|
+
* absent or lapsed token, since the endpoint's auth is optional). Both mean
|
|
46
|
+
* the private blocks are simply absent, and a thrown error is the only
|
|
47
|
+
* honest report of that.
|
|
48
|
+
*/
|
|
49
|
+
async getMyReputationBalance() {
|
|
50
|
+
const userId = this.getCurrentUserId();
|
|
51
|
+
if (!userId) {
|
|
52
|
+
throw new OxyServices_errors_1.OxyAuthenticationError('Reading your own reputation balance requires a signed-in user');
|
|
53
|
+
}
|
|
54
|
+
let balance;
|
|
55
|
+
try {
|
|
56
|
+
balance = await this.makeRequest('GET', `/reputation/${encodeURIComponent(userId)}/balance`, undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.MEDIUM });
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
throw this.handleError(error);
|
|
60
|
+
}
|
|
61
|
+
if (!(0, contracts_1.isFullReputationBalance)(balance)) {
|
|
62
|
+
throw new OxyServices_errors_1.OxyAuthenticationError('The reputation balance came back as the public view — the request was not authenticated as its subject');
|
|
63
|
+
}
|
|
64
|
+
return balance;
|
|
65
|
+
}
|
|
25
66
|
/**
|
|
26
67
|
* Get the reputation leaderboard, ordered by lifetime total descending.
|
|
27
68
|
* @param limit - Page size (server-capped).
|
|
@@ -192,6 +233,10 @@ function OxyServicesReputationMixin(Base) {
|
|
|
192
233
|
/**
|
|
193
234
|
* Force a recompute of a user's balance snapshot from their active ledger
|
|
194
235
|
* (staff only). Invalidates cached reputation reads.
|
|
236
|
+
*
|
|
237
|
+
* Staff-gated, so the response is always the full subject view — no
|
|
238
|
+
* narrowing needed.
|
|
239
|
+
*
|
|
195
240
|
* @param userId - The subject user's `_id` or publicKey.
|
|
196
241
|
*/
|
|
197
242
|
async recalculateReputation(userId) {
|
|
@@ -220,10 +220,9 @@ function OxyServicesUserMixin(Base) {
|
|
|
220
220
|
/**
|
|
221
221
|
* Get profiles similar to a given user, based on co-follower overlap.
|
|
222
222
|
*/
|
|
223
|
-
async getSimilarProfiles(userId,
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
params.limit = String(limit);
|
|
223
|
+
async getSimilarProfiles(userId, limitOrParams) {
|
|
224
|
+
const pagination = typeof limitOrParams === 'number' ? { limit: limitOrParams } : limitOrParams ?? {};
|
|
225
|
+
const params = (0, apiUtils_1.buildQueryParams)(pagination);
|
|
227
226
|
const users = await this.makeRequest('GET', `/profiles/${userId}/similar`, params, {
|
|
228
227
|
cache: true,
|
|
229
228
|
cacheTTL: 5 * 60 * 1000, // 5 min cache
|
|
@@ -21,6 +21,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
21
21
|
exports.projectSwitchableAccounts = projectSwitchableAccounts;
|
|
22
22
|
exports.switchableAccountIds = switchableAccountIds;
|
|
23
23
|
const accountUtils_1 = require("../utils/accountUtils");
|
|
24
|
+
const userHandle_1 = require("../utils/userHandle");
|
|
24
25
|
/**
|
|
25
26
|
* Pure union of device sign-ins and account-graph nodes into the flat
|
|
26
27
|
* {@link SwitchableAccount}[] every switcher renders.
|
|
@@ -47,7 +48,9 @@ function projectSwitchableAccounts(input) {
|
|
|
47
48
|
kind: opts.kind,
|
|
48
49
|
parentAccountId: opts.parentAccountId,
|
|
49
50
|
callerMembership: opts.callerMembership,
|
|
50
|
-
displayName:
|
|
51
|
+
displayName: accountUser.name?.displayName ??
|
|
52
|
+
(0, userHandle_1.getNormalizedUserHandle)(accountUser) ??
|
|
53
|
+
(0, accountUtils_1.getAccountDisplayName)(null, locale),
|
|
51
54
|
// Real email, or the `@handle` fallback (NEVER synthesized).
|
|
52
55
|
email: accountUser.email ?? secondaryHandle,
|
|
53
56
|
avatarUrl: resolveAvatarUrl(accountUser.avatar),
|