@oxy.so/core 1.0.0 → 1.1.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.
Files changed (74) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/OxyServices.errors.js +28 -1
  3. package/dist/cjs/crypto/keyManager.js +5 -3
  4. package/dist/cjs/i18n/locales/en-US.json +18 -5
  5. package/dist/cjs/i18n/locales/es-ES.json +18 -5
  6. package/dist/cjs/i18n/locales/locales/en-US.json +18 -5
  7. package/dist/cjs/i18n/locales/locales/es-ES.json +18 -5
  8. package/dist/cjs/mixins/OxyServices.agency.js +113 -0
  9. package/dist/cjs/mixins/OxyServices.assets.js +90 -0
  10. package/dist/cjs/mixins/OxyServices.connectedApps.js +20 -3
  11. package/dist/cjs/mixins/OxyServices.utility.js +1 -21
  12. package/dist/cjs/mixins/index.js +3 -4
  13. package/dist/cjs/package.json +3 -0
  14. package/dist/cjs/server/index.js +6 -1
  15. package/dist/cjs/server/traffic.js +107 -0
  16. package/dist/cjs/server/trafficNodeHttp.js +86 -0
  17. package/dist/cjs/utils/textNormalization.js +1 -1
  18. package/dist/esm/.tsbuildinfo +1 -1
  19. package/dist/esm/OxyServices.errors.js +26 -0
  20. package/dist/esm/crypto/keyManager.js +5 -3
  21. package/dist/esm/i18n/locales/en-US.json +18 -5
  22. package/dist/esm/i18n/locales/es-ES.json +18 -5
  23. package/dist/esm/i18n/locales/locales/en-US.json +18 -5
  24. package/dist/esm/i18n/locales/locales/es-ES.json +18 -5
  25. package/dist/esm/mixins/OxyServices.agency.js +110 -0
  26. package/dist/esm/mixins/OxyServices.assets.js +91 -1
  27. package/dist/esm/mixins/OxyServices.connectedApps.js +20 -3
  28. package/dist/esm/mixins/OxyServices.utility.js +1 -21
  29. package/dist/esm/mixins/index.js +3 -4
  30. package/dist/esm/package.json +3 -0
  31. package/dist/esm/server/index.js +2 -0
  32. package/dist/esm/server/traffic.js +104 -0
  33. package/dist/esm/server/trafficNodeHttp.js +79 -0
  34. package/dist/esm/utils/textNormalization.js +1 -1
  35. package/dist/types/.tsbuildinfo +1 -1
  36. package/dist/types/OxyServices.errors.d.ts +24 -0
  37. package/dist/types/crypto/keyManager.d.ts +4 -2
  38. package/dist/types/index.d.ts +2 -1
  39. package/dist/types/mixins/OxyServices.agency.d.ts +135 -0
  40. package/dist/types/mixins/OxyServices.assets.d.ts +43 -1
  41. package/dist/types/mixins/OxyServices.connectedApps.d.ts +15 -0
  42. package/dist/types/mixins/OxyServices.utility.d.ts +0 -9
  43. package/dist/types/mixins/index.d.ts +2 -2
  44. package/dist/types/models/interfaces.d.ts +26 -0
  45. package/dist/types/server/index.d.ts +3 -0
  46. package/dist/types/server/traffic.d.ts +27 -0
  47. package/dist/types/server/trafficNodeHttp.d.ts +6 -0
  48. package/dist/types/utils/textNormalization.d.ts +1 -1
  49. package/package.json +7 -6
  50. package/src/OxyServices.errors.ts +36 -0
  51. package/src/crypto/keyManager.ts +5 -4
  52. package/src/i18n/locales/en-US.json +18 -5
  53. package/src/i18n/locales/es-ES.json +18 -5
  54. package/src/index.ts +15 -0
  55. package/src/mixins/OxyServices.agency.ts +277 -0
  56. package/src/mixins/OxyServices.assets.ts +105 -2
  57. package/src/mixins/OxyServices.connectedApps.ts +42 -4
  58. package/src/mixins/OxyServices.utility.ts +1 -26
  59. package/src/mixins/__tests__/OxyServices.serviceLinkedDownloadUrl.test.ts +139 -0
  60. package/src/mixins/__tests__/agency.test.ts +120 -0
  61. package/src/mixins/__tests__/connectedApps.test.ts +23 -0
  62. package/src/mixins/index.ts +4 -5
  63. package/src/models/interfaces.ts +27 -0
  64. package/src/server/__tests__/traffic.test.ts +49 -0
  65. package/src/server/__tests__/trafficNodeHttp.test.ts +38 -0
  66. package/src/server/index.ts +5 -0
  67. package/src/server/traffic.ts +116 -0
  68. package/src/server/trafficNodeHttp.ts +69 -0
  69. package/src/utils/textNormalization.ts +1 -1
  70. package/dist/cjs/mixins/OxyServices.links.js +0 -72
  71. package/dist/esm/mixins/OxyServices.links.js +0 -69
  72. package/dist/types/mixins/OxyServices.links.d.ts +0 -104
  73. package/src/mixins/OxyServices.links.ts +0 -107
  74. package/src/mixins/__tests__/OxyServices.links.test.ts +0 -154
@@ -80,6 +80,32 @@ export class ServiceAssetMetadataError extends Error {
80
80
  this.cause = cause;
81
81
  }
82
82
  }
83
+ /**
84
+ * A chunk of `POST /assets/service/linked-url` failed, so the SDK cannot say
85
+ * whether those ids have URLs.
86
+ *
87
+ * Its own class rather than a reuse of {@link ServiceAssetMetadataError}, and
88
+ * deliberately WITHOUT that one's `{ partial: true }` escape hatch, because the
89
+ * two mistakes cost different things. Reading a failed metadata lookup as
90
+ * "absent" embedded a media item with no hash. Reading a failed DOWNLOAD mint as
91
+ * "absent" tells a buyer who paid that the file they own is not there — and
92
+ * "not there" is already this route's answer for "not yours", so a best-effort
93
+ * mode would make a throttled request indistinguishable from a refusal. There is
94
+ * no download path that legitimately wants that, so there is no option to ask
95
+ * for it.
96
+ */
97
+ export class ServiceLinkedDownloadUrlError extends Error {
98
+ constructor(unresolvedIds, statuses, cause) {
99
+ const uniqueStatuses = Array.from(new Set(statuses)).sort((a, b) => a - b);
100
+ const statusSuffix = uniqueStatuses.length > 0 ? ` — status ${uniqueStatuses.join(', ')}` : '';
101
+ super(`Could not mint download URLs for ${unresolvedIds.length} id(s)${statusSuffix}. Treat this as unknown, never as "the caller holds no right".`);
102
+ this.code = 'SERVICE_LINKED_DOWNLOAD_URL_UNRESOLVED';
103
+ this.name = 'ServiceLinkedDownloadUrlError';
104
+ this.unresolvedIds = unresolvedIds;
105
+ this.statuses = uniqueStatuses;
106
+ this.cause = cause;
107
+ }
108
+ }
83
109
  export class OxyAuthenticationTimeoutError extends OxyAuthenticationError {
84
110
  constructor(operationName, timeoutMs) {
85
111
  super(`Authentication timeout (${timeoutMs}ms): ${operationName} requires user authentication. Please ensure the user is logged in before calling this method.`, 'AUTH_TIMEOUT', 408);
@@ -63,7 +63,7 @@ export class IdentityUnavailableError extends Error {
63
63
  /**
64
64
  * HKDF salt that domain-separates every identity-scoped seed produced by
65
65
  * {@link KeyManager.deriveScopedSeed}. Versioned so a future scheme change is a
66
- * new, non-colliding tag. The per-app domain (e.g. Oxy Pay's FairCoin wallet)
66
+ * new, non-colliding tag. The per-app domain (e.g. Peable's FairCoin wallet)
67
67
  * is carried by the caller's `info` string, not this salt.
68
68
  */
69
69
  const SCOPED_SEED_KDF_SALT = 'oxy-identity-scoped-seed-v1';
@@ -2168,12 +2168,14 @@ export class KeyManager {
2168
2168
  *
2169
2169
  * The domain separation is carried by `info` (e.g. `"oxypay/faircoin/v1"`),
2170
2170
  * so distinct apps/purposes get independent seeds from the same identity.
2171
+ * That legacy Peable tag is a cryptographic contract and must not be renamed:
2172
+ * changing it would derive a different wallet for every existing user.
2171
2173
  * The output is HKDF keying material, never the private key itself — a
2172
- * consumer (e.g. Oxy Pay's FairCoin HD wallet) can feed it straight into
2174
+ * consumer (e.g. Peable's FairCoin HD wallet) can feed it straight into
2173
2175
  * `HDKey.fromMasterSeed` and never touches the identity key.
2174
2176
  *
2175
2177
  * Key source (native only): prefers the shared ecosystem identity written to
2176
- * `group.so.oxy.shared` (what a Relying Party like Oxy Pay reads), then falls
2178
+ * `group.so.oxy.shared` (what a Relying Party like Peable reads), then falls
2177
2179
  * back to this device's primary identity (Commons/Accounts). Both reproduce
2178
2180
  * from the user's Oxy recovery phrase, so the derived seed is recoverable.
2179
2181
  *
@@ -1412,7 +1412,7 @@
1412
1412
  },
1413
1413
  "details": {
1414
1414
  "cardDetails": "Card Details",
1415
- "oxyPay": "Oxy Pay",
1415
+ "peable": "Peable",
1416
1416
  "faircoinPayment": "FairCoin Payment",
1417
1417
  "paymentDetails": "Payment Details",
1418
1418
  "creditCard": "Credit Card",
@@ -1438,7 +1438,7 @@
1438
1438
  "amount": "Amount",
1439
1439
  "paymentMethod": "Payment Method",
1440
1440
  "card": "Card",
1441
- "oxyPayAccount": "Oxy Pay Account",
1441
+ "peableAccount": "Peable Account",
1442
1442
  "faircoinWallet": "FairCoin Wallet",
1443
1443
  "paidViaQR": "Paid via QR",
1444
1444
  "processing": "Processing...",
@@ -1465,8 +1465,8 @@
1465
1465
  "description": "Pay securely with your credit or debit card."
1466
1466
  },
1467
1467
  "oxy": {
1468
- "label": "Oxy Pay",
1469
- "description": "Use your Oxy Pay in-app balance."
1468
+ "label": "Peable",
1469
+ "description": "Use your Peable in-app balance."
1470
1470
  },
1471
1471
  "faircoin": {
1472
1472
  "label": "FAIRWallet",
@@ -2127,6 +2127,18 @@
2127
2127
  "title": "Permissions requested",
2128
2128
  "basic": "Sign you in and read your basic profile"
2129
2129
  },
2130
+ "resource": {
2131
+ "title": "Oxy app and protected resource"
2132
+ },
2133
+ "writeActions": {
2134
+ "title": "Actions this connection can perform"
2135
+ },
2136
+ "effects": {
2137
+ "write": "Write",
2138
+ "external": "External",
2139
+ "financial": "Financial",
2140
+ "security": "Security"
2141
+ },
2130
2142
  "scopes": {
2131
2143
  "openid": "Confirm your identity",
2132
2144
  "profile": "Read your basic profile",
@@ -2147,7 +2159,8 @@
2147
2159
  "federationWrite": "Act across federated services"
2148
2160
  },
2149
2161
  "account": {
2150
- "title": "Signing in as"
2162
+ "title": "Signing in as",
2163
+ "id": "Account ID: {{id}}"
2151
2164
  },
2152
2165
  "links": {
2153
2166
  "website": "Website",
@@ -1412,7 +1412,7 @@
1412
1412
  },
1413
1413
  "details": {
1414
1414
  "cardDetails": "Detalles de la tarjeta",
1415
- "oxyPay": "Oxy Pay",
1415
+ "peable": "Peable",
1416
1416
  "faircoinPayment": "Pago con FairCoin",
1417
1417
  "paymentDetails": "Detalles del pago",
1418
1418
  "creditCard": "Tarjeta de crédito",
@@ -1438,7 +1438,7 @@
1438
1438
  "amount": "Monto",
1439
1439
  "paymentMethod": "Método de pago",
1440
1440
  "card": "Tarjeta",
1441
- "oxyPayAccount": "Cuenta Oxy Pay",
1441
+ "peableAccount": "Cuenta Peable",
1442
1442
  "faircoinWallet": "Billetera FairCoin",
1443
1443
  "paidViaQR": "Pagado vía QR",
1444
1444
  "processing": "Procesando...",
@@ -1465,8 +1465,8 @@
1465
1465
  "description": "Paga de forma segura con tu tarjeta de crédito o débito."
1466
1466
  },
1467
1467
  "oxy": {
1468
- "label": "Oxy Pay",
1469
- "description": "Usa tu saldo de Oxy Pay en la app."
1468
+ "label": "Peable",
1469
+ "description": "Usa tu saldo de Peable en la app."
1470
1470
  },
1471
1471
  "faircoin": {
1472
1472
  "label": "FAIRWallet",
@@ -2127,6 +2127,18 @@
2127
2127
  "title": "Permisos solicitados",
2128
2128
  "basic": "Iniciar sesión y leer tu perfil básico"
2129
2129
  },
2130
+ "resource": {
2131
+ "title": "App de Oxy y recurso protegido"
2132
+ },
2133
+ "writeActions": {
2134
+ "title": "Acciones que puede realizar esta conexión"
2135
+ },
2136
+ "effects": {
2137
+ "write": "Escritura",
2138
+ "external": "Externa",
2139
+ "financial": "Financiera",
2140
+ "security": "Seguridad"
2141
+ },
2130
2142
  "scopes": {
2131
2143
  "openid": "Confirmar tu identidad",
2132
2144
  "profile": "Leer tu perfil básico",
@@ -2147,7 +2159,8 @@
2147
2159
  "federationWrite": "Actuar en servicios federados"
2148
2160
  },
2149
2161
  "account": {
2150
- "title": "Iniciando sesión como"
2162
+ "title": "Iniciando sesión como",
2163
+ "id": "ID de cuenta: {{id}}"
2151
2164
  },
2152
2165
  "links": {
2153
2166
  "website": "Sitio web",
@@ -1412,7 +1412,7 @@
1412
1412
  },
1413
1413
  "details": {
1414
1414
  "cardDetails": "Card Details",
1415
- "oxyPay": "Oxy Pay",
1415
+ "peable": "Peable",
1416
1416
  "faircoinPayment": "FairCoin Payment",
1417
1417
  "paymentDetails": "Payment Details",
1418
1418
  "creditCard": "Credit Card",
@@ -1438,7 +1438,7 @@
1438
1438
  "amount": "Amount",
1439
1439
  "paymentMethod": "Payment Method",
1440
1440
  "card": "Card",
1441
- "oxyPayAccount": "Oxy Pay Account",
1441
+ "peableAccount": "Peable Account",
1442
1442
  "faircoinWallet": "FairCoin Wallet",
1443
1443
  "paidViaQR": "Paid via QR",
1444
1444
  "processing": "Processing...",
@@ -1465,8 +1465,8 @@
1465
1465
  "description": "Pay securely with your credit or debit card."
1466
1466
  },
1467
1467
  "oxy": {
1468
- "label": "Oxy Pay",
1469
- "description": "Use your Oxy Pay in-app balance."
1468
+ "label": "Peable",
1469
+ "description": "Use your Peable in-app balance."
1470
1470
  },
1471
1471
  "faircoin": {
1472
1472
  "label": "FAIRWallet",
@@ -2127,6 +2127,18 @@
2127
2127
  "title": "Permissions requested",
2128
2128
  "basic": "Sign you in and read your basic profile"
2129
2129
  },
2130
+ "resource": {
2131
+ "title": "Oxy app and protected resource"
2132
+ },
2133
+ "writeActions": {
2134
+ "title": "Actions this connection can perform"
2135
+ },
2136
+ "effects": {
2137
+ "write": "Write",
2138
+ "external": "External",
2139
+ "financial": "Financial",
2140
+ "security": "Security"
2141
+ },
2130
2142
  "scopes": {
2131
2143
  "openid": "Confirm your identity",
2132
2144
  "profile": "Read your basic profile",
@@ -2147,7 +2159,8 @@
2147
2159
  "federationWrite": "Act across federated services"
2148
2160
  },
2149
2161
  "account": {
2150
- "title": "Signing in as"
2162
+ "title": "Signing in as",
2163
+ "id": "Account ID: {{id}}"
2151
2164
  },
2152
2165
  "links": {
2153
2166
  "website": "Website",
@@ -1412,7 +1412,7 @@
1412
1412
  },
1413
1413
  "details": {
1414
1414
  "cardDetails": "Detalles de la tarjeta",
1415
- "oxyPay": "Oxy Pay",
1415
+ "peable": "Peable",
1416
1416
  "faircoinPayment": "Pago con FairCoin",
1417
1417
  "paymentDetails": "Detalles del pago",
1418
1418
  "creditCard": "Tarjeta de crédito",
@@ -1438,7 +1438,7 @@
1438
1438
  "amount": "Monto",
1439
1439
  "paymentMethod": "Método de pago",
1440
1440
  "card": "Tarjeta",
1441
- "oxyPayAccount": "Cuenta Oxy Pay",
1441
+ "peableAccount": "Cuenta Peable",
1442
1442
  "faircoinWallet": "Billetera FairCoin",
1443
1443
  "paidViaQR": "Pagado vía QR",
1444
1444
  "processing": "Procesando...",
@@ -1465,8 +1465,8 @@
1465
1465
  "description": "Paga de forma segura con tu tarjeta de crédito o débito."
1466
1466
  },
1467
1467
  "oxy": {
1468
- "label": "Oxy Pay",
1469
- "description": "Usa tu saldo de Oxy Pay en la app."
1468
+ "label": "Peable",
1469
+ "description": "Usa tu saldo de Peable en la app."
1470
1470
  },
1471
1471
  "faircoin": {
1472
1472
  "label": "FAIRWallet",
@@ -2127,6 +2127,18 @@
2127
2127
  "title": "Permisos solicitados",
2128
2128
  "basic": "Iniciar sesión y leer tu perfil básico"
2129
2129
  },
2130
+ "resource": {
2131
+ "title": "App de Oxy y recurso protegido"
2132
+ },
2133
+ "writeActions": {
2134
+ "title": "Acciones que puede realizar esta conexión"
2135
+ },
2136
+ "effects": {
2137
+ "write": "Escritura",
2138
+ "external": "Externa",
2139
+ "financial": "Financiera",
2140
+ "security": "Seguridad"
2141
+ },
2130
2142
  "scopes": {
2131
2143
  "openid": "Confirmar tu identidad",
2132
2144
  "profile": "Leer tu perfil básico",
@@ -2147,7 +2159,8 @@
2147
2159
  "federationWrite": "Actuar en servicios federados"
2148
2160
  },
2149
2161
  "account": {
2150
- "title": "Iniciando sesión como"
2162
+ "title": "Iniciando sesión como",
2163
+ "id": "ID de cuenta: {{id}}"
2151
2164
  },
2152
2165
  "links": {
2153
2166
  "website": "Sitio web",
@@ -0,0 +1,110 @@
1
+ import { CACHE_TIMES } from './mixinHelpers.js';
2
+ function query(path, key, value) {
3
+ return `${path}?${key}=${encodeURIComponent(value)}`;
4
+ }
5
+ export function OxyServicesAgencyMixin(Base) {
6
+ return class extends Base {
7
+ async listAvailableCapabilityCatalogs(accountId) {
8
+ try {
9
+ const response = await this.makeRequest('GET', query('/capabilities/catalogs/available', 'accountId', accountId), undefined, { cache: true, cacheTTL: CACHE_TIMES.MEDIUM });
10
+ return response.catalogs;
11
+ }
12
+ catch (error) {
13
+ throw this.handleError(error);
14
+ }
15
+ }
16
+ async listDelegationGrants(ownerAccountId) {
17
+ try {
18
+ const response = await this.makeRequest('GET', query('/capabilities/grants', 'ownerAccountId', ownerAccountId), undefined, { cache: true, cacheTTL: CACHE_TIMES.SHORT });
19
+ return response.grants;
20
+ }
21
+ catch (error) {
22
+ throw this.handleError(error);
23
+ }
24
+ }
25
+ async createDelegationGrant(input) {
26
+ try {
27
+ const response = await this.makeRequest('POST', '/capabilities/grants', input, { cache: false });
28
+ this.clearCacheEntry(`GET:/capabilities/grants?ownerAccountId=${encodeURIComponent(input.ownerAccountId)}`);
29
+ return response.grant;
30
+ }
31
+ catch (error) {
32
+ throw this.handleError(error);
33
+ }
34
+ }
35
+ async revokeDelegationGrant(grantId, ownerAccountId) {
36
+ try {
37
+ await this.makeRequest('DELETE', `/capabilities/grants/${encodeURIComponent(grantId)}`, undefined, { cache: false });
38
+ this.clearCacheEntry(`GET:/capabilities/grants?ownerAccountId=${encodeURIComponent(ownerAccountId)}`);
39
+ }
40
+ catch (error) {
41
+ throw this.handleError(error);
42
+ }
43
+ }
44
+ async updateDelegationGrant(grantId, ownerAccountId, input) {
45
+ try {
46
+ const response = await this.makeRequest('PUT', `/capabilities/grants/${encodeURIComponent(grantId)}`, input, { cache: false });
47
+ this.clearCacheEntry(`GET:/capabilities/grants?ownerAccountId=${encodeURIComponent(ownerAccountId)}`);
48
+ return response.grant;
49
+ }
50
+ catch (error) {
51
+ throw this.handleError(error);
52
+ }
53
+ }
54
+ async listAccountCapabilityPolicies(accountId) {
55
+ try {
56
+ const response = await this.makeRequest('GET', query('/capabilities/account-policies', 'accountId', accountId), undefined, { cache: true, cacheTTL: CACHE_TIMES.SHORT });
57
+ return response.policies;
58
+ }
59
+ catch (error) {
60
+ throw this.handleError(error);
61
+ }
62
+ }
63
+ async putAccountCapabilityPolicy(appId, input) {
64
+ try {
65
+ const response = await this.makeRequest('PUT', `/capabilities/account-policies/${encodeURIComponent(appId)}`, input, { cache: false });
66
+ this.clearCacheEntry(`GET:/capabilities/account-policies?accountId=${encodeURIComponent(input.accountId)}`);
67
+ return response.policy;
68
+ }
69
+ catch (error) {
70
+ throw this.handleError(error);
71
+ }
72
+ }
73
+ async deleteAccountCapabilityPolicy(appId, accountId) {
74
+ try {
75
+ await this.makeRequest('DELETE', `/capabilities/account-policies/${encodeURIComponent(appId)}?accountId=${encodeURIComponent(accountId)}`, undefined, { cache: false });
76
+ this.clearCacheEntry(`GET:/capabilities/account-policies?accountId=${encodeURIComponent(accountId)}`);
77
+ }
78
+ catch (error) {
79
+ throw this.handleError(error);
80
+ }
81
+ }
82
+ async listCapabilityExecutionAuthorizations(ownerAccountId) {
83
+ try {
84
+ const response = await this.makeRequest('GET', query('/capabilities/execution-authorizations', 'ownerAccountId', ownerAccountId), undefined, { cache: true, cacheTTL: CACHE_TIMES.SHORT });
85
+ return response.authorizations;
86
+ }
87
+ catch (error) {
88
+ throw this.handleError(error);
89
+ }
90
+ }
91
+ async revokeCapabilityExecutionAuthorization(authorizationId, ownerAccountId) {
92
+ try {
93
+ await this.makeRequest('DELETE', `/capabilities/execution-authorizations/${encodeURIComponent(authorizationId)}`, undefined, { cache: false });
94
+ this.clearCacheEntry(`GET:/capabilities/execution-authorizations?ownerAccountId=${encodeURIComponent(ownerAccountId)}`);
95
+ }
96
+ catch (error) {
97
+ throw this.handleError(error);
98
+ }
99
+ }
100
+ async listCapabilityAuditEvents(accountId) {
101
+ try {
102
+ const response = await this.makeRequest('GET', query('/capabilities/audit', 'accountId', accountId), undefined, { cache: true, cacheTTL: CACHE_TIMES.SHORT });
103
+ return response.events;
104
+ }
105
+ catch (error) {
106
+ throw this.handleError(error);
107
+ }
108
+ }
109
+ };
110
+ }
@@ -1,6 +1,6 @@
1
1
  import { isReactNative } from '@oxy.so/protocol';
2
2
  import { logger } from '../logger/index.js';
3
- import { AssetUrlResolutionError, ServiceAssetMetadataError } from '../OxyServices.errors.js';
3
+ import { AssetUrlResolutionError, ServiceAssetMetadataError, ServiceLinkedDownloadUrlError } from '../OxyServices.errors.js';
4
4
  import { extractErrorStatus } from '../utils/errorUtils.js';
5
5
  /**
6
6
  * Maximum number of ids sent per `POST /assets/service/by-ids` request. Matches
@@ -16,6 +16,13 @@ const SERVICE_ASSET_METADATA_CHUNK_SIZE = 100;
16
16
  * {@link SERVICE_ASSET_METADATA_CHUNK_SIZE} for the forward id lookup.
17
17
  */
18
18
  const SERVICE_ASSET_METADATA_BY_SHA_CHUNK_SIZE = 100;
19
+ /**
20
+ * Maximum number of ids sent per `POST /assets/service/linked-url` request.
21
+ * Matches that route's own cap, which is a QUARTER of the metadata routes' —
22
+ * see `MAX_ASSETS_LINKED_URL` in the API for why minting download credentials is
23
+ * budgeted more tightly than projecting rows.
24
+ */
25
+ const SERVICE_LINKED_DOWNLOAD_URL_CHUNK_SIZE = 25;
19
26
  /**
20
27
  * Lowercase hex SHA-256 digest matcher (exactly 64 hex chars). The reverse
21
28
  * lookup drops non-conforming hashes client-side so a single malformed value
@@ -358,6 +365,89 @@ export function OxyServicesAssetsMixin(Base) {
358
365
  }
359
366
  return settled.flat();
360
367
  }
368
+ /**
369
+ * Mint short-lived direct download URLs for files the calling application's
370
+ * own users attached to it, via `POST /assets/service/linked-url`.
371
+ *
372
+ * This is the ONLY path in this SDK by which a service token reaches file
373
+ * BYTES. {@link getServiceAssetMetadataByIds} is metadata-only, and every
374
+ * other download helper here — {@link getFileDownloadUrl},
375
+ * {@link getFileDownloadUrlAsync}, {@link getBatchFileAccess} — resolves a URL
376
+ * for the CURRENT USER through `makeRequest`, which is a different question: a
377
+ * relying service holds no user session, and the buyer whose entitlement it is
378
+ * enforcing is not a viewer Oxy knows anything about.
379
+ *
380
+ * **Service-token auth (required), plus the `files:linked:read` scope**, which
381
+ * is NOT implied by `files:read`. The calling client must be service-configured
382
+ * (`configureServiceAuth(apiKey, apiSecret)`); a user-session request is
383
+ * rejected by the route's service-auth guard.
384
+ *
385
+ * **What the server will and will not give you.** A URL comes back only for a
386
+ * file whose OWN OWNER linked it to this application (`file_links.created_by =
387
+ * files.owner_user_id`). Every other id is omitted: unknown, deleted,
388
+ * system-owned, linked by somebody else, or not linked here at all. So the
389
+ * result is routinely shorter than the input and must be mapped by `id`.
390
+ *
391
+ * ABSENCE IS A REFUSAL, NOT A FACT ABOUT THE FILE, and the caller is the only
392
+ * party that can tell the difference. The route deliberately cannot — it
393
+ * answers identically for "no such file" and "not yours" so it leaks nothing —
394
+ * which means a service must decide what to tell its user from its OWN
395
+ * entitlement record, never from this response. Do not surface "file not
396
+ * found" on the strength of an omission.
397
+ *
398
+ * FAILURE IS NOT ABSENCE EITHER, and here it cannot be opted out of: a failed
399
+ * chunk throws {@link ServiceLinkedDownloadUrlError} carrying its ids, with no
400
+ * `{ partial: true }` mode. Swallowing a 429 would tell a user who paid for a
401
+ * file that it is not there. An empty input resolves to `[]` with no network
402
+ * call.
403
+ *
404
+ * Never cached at the SDK layer, and that is a stronger statement than the
405
+ * sibling methods' "low hit rate": each entry is a live credential for the
406
+ * bytes, so caching one keeps it usable after the service has decided the
407
+ * caller's entitlement ended.
408
+ */
409
+ async getServiceLinkedDownloadUrls(ids) {
410
+ const uniqueIds = Array.from(new Set(ids.filter((id) => typeof id === 'string' && id.trim().length > 0)));
411
+ if (uniqueIds.length === 0) {
412
+ return [];
413
+ }
414
+ const chunks = [];
415
+ for (let i = 0; i < uniqueIds.length; i += SERVICE_LINKED_DOWNLOAD_URL_CHUNK_SIZE) {
416
+ chunks.push(uniqueIds.slice(i, i + SERVICE_LINKED_DOWNLOAD_URL_CHUNK_SIZE));
417
+ }
418
+ // Chunks stay independent so one failure never cancels work already in
419
+ // flight; the failures are collected and re-raised together below, exactly
420
+ // as in the metadata sibling.
421
+ const unresolvedIds = [];
422
+ const statuses = [];
423
+ let firstError;
424
+ const settled = await Promise.all(chunks.map(async (chunk) => {
425
+ try {
426
+ const entries = await this.makeServiceRequest('POST', '/assets/service/linked-url', { ids: chunk });
427
+ return Array.isArray(entries) ? entries : [];
428
+ }
429
+ catch (error) {
430
+ const status = extractErrorStatus(error);
431
+ // `chunkSize` and `status`, never the ids and never a `url`: a minted
432
+ // URL in a log line is a credential in a log line.
433
+ logger.warn('getServiceLinkedDownloadUrls: chunk failed', {
434
+ method: 'getServiceLinkedDownloadUrls',
435
+ chunkSize: chunk.length,
436
+ status,
437
+ error: error instanceof Error ? error.message : String(error),
438
+ });
439
+ unresolvedIds.push(...chunk);
440
+ if (typeof status === 'number')
441
+ statuses.push(status);
442
+ firstError ?? (firstError = error);
443
+ return [];
444
+ }
445
+ }));
446
+ if (unresolvedIds.length > 0) {
447
+ throw new ServiceLinkedDownloadUrlError(unresolvedIds, statuses, firstError);
448
+ }
449
+ return settled.flat();
450
+ }
361
451
  /**
362
452
  * Reverse content-address lookup: resolve many content `sha256` digests to
363
453
  * the servable Oxy asset holding each, in one round-trip per chunk via
@@ -1,9 +1,6 @@
1
1
  import { CACHE_TIMES } from './mixinHelpers.js';
2
2
  export function OxyServicesConnectedAppsMixin(Base) {
3
3
  return class extends Base {
4
- constructor(...args) {
5
- super(...args);
6
- }
7
4
  /**
8
5
  * Resolve an OAuth client identifier to the owning application's PUBLIC
9
6
  * identity. No authentication required — the API returns only sanitized,
@@ -71,5 +68,25 @@ export function OxyServicesConnectedAppsMixin(Base) {
71
68
  throw this.handleError(error);
72
69
  }
73
70
  }
71
+ /** List resource-bound external MCP connections for the active Oxy account. */
72
+ async listConnectedMcpClients() {
73
+ try {
74
+ const response = await this.makeRequest('GET', '/auth/mcp/oauth/grants', undefined, { cache: true, cacheTTL: CACHE_TIMES.SHORT });
75
+ return response.grants;
76
+ }
77
+ catch (error) {
78
+ throw this.handleError(error);
79
+ }
80
+ }
81
+ /** Revoke one external MCP connection and every token in its family. */
82
+ async revokeConnectedMcpClient(grantId) {
83
+ try {
84
+ await this.makeRequest('DELETE', `/auth/mcp/oauth/grants/${encodeURIComponent(grantId)}`, undefined, { cache: false });
85
+ this.clearCacheEntry('GET:/auth/mcp/oauth/grants');
86
+ }
87
+ catch (error) {
88
+ throw this.handleError(error);
89
+ }
90
+ }
74
91
  };
75
92
  }
@@ -1,12 +1,10 @@
1
1
  /**
2
2
  * Utility Methods Mixin
3
3
  *
4
- * Provides utility methods including link metadata fetching
5
- * and Express.js authentication middleware
4
+ * Provides utility methods including Express.js authentication middleware.
6
5
  */
7
6
  import { jwtDecode } from 'jwt-decode';
8
7
  import { loadNodeCrypto } from '@oxy.so/protocol';
9
- import { buildUrl } from '../utils/apiUtils.js';
10
8
  import { logger } from '../logger/index.js';
11
9
  import { OXY_SERVICE_ENVIRONMENTS } from '../utils/oxyServiceEnvironment.js';
12
10
  /**
@@ -135,24 +133,6 @@ export function OxyServicesUtilityMixin(Base) {
135
133
  return null;
136
134
  }
137
135
  }
138
- /**
139
- * Fetch link metadata
140
- */
141
- async fetchLinkMetadata(url) {
142
- try {
143
- const path = buildUrl('/links/preview', { url, wait: 1 });
144
- const preview = await this.makeRequest('GET', path, undefined, { cache: false });
145
- return {
146
- url: preview.url,
147
- title: preview.title?.trim() || preview.url.replace(/^https?:\/\//, '').replace(/\/$/, ''),
148
- description: preview.description?.trim() || 'Link',
149
- image: preview.image,
150
- };
151
- }
152
- catch (error) {
153
- throw this.handleError(error);
154
- }
155
- }
156
136
  /**
157
137
  * Express.js authentication middleware
158
138
  *
@@ -16,6 +16,7 @@ import { OxyServicesReputationMixin } from './OxyServices.reputation.js';
16
16
  import { OxyServicesAssetsMixin } from './OxyServices.assets.js';
17
17
  import { OxyServicesAccountsMixin } from './OxyServices.accounts.js';
18
18
  import { OxyServicesConnectedAppsMixin } from './OxyServices.connectedApps.js';
19
+ import { OxyServicesAgencyMixin } from './OxyServices.agency.js';
19
20
  import { OxyServicesStoreMixin } from './OxyServices.store.js';
20
21
  import { OxyServicesLocationMixin } from './OxyServices.location.js';
21
22
  import { OxyServicesAnalyticsMixin } from './OxyServices.analytics.js';
@@ -30,7 +31,6 @@ import { OxyServicesAppDataMixin } from './OxyServices.appData.js';
30
31
  import { OxyServicesCivicMixin } from './OxyServices.civic.js';
31
32
  import { OxyServicesChainsMixin } from './OxyServices.chains.js';
32
33
  import { OxyServicesNodesMixin } from './OxyServices.nodes.js';
33
- import { OxyServicesLinksMixin } from './OxyServices.links.js';
34
34
  import { OxyServicesFollowGraphMixin } from './OxyServices.followGraph.js';
35
35
  import { OxyServicesInferenceMixin } from './OxyServices.inference.js';
36
36
  import { OxyServicesDeviceBootMixin } from './OxyServices.deviceBoot.js';
@@ -69,6 +69,8 @@ const MIXIN_PIPELINE = [
69
69
  // OAuth-consent surface (public app identity + connected-app grants). Kept
70
70
  // separate from account ownership.
71
71
  OxyServicesConnectedAppsMixin,
72
+ // Oxy-owned delegated authority for Alia and digital-agent accounts.
73
+ OxyServicesAgencyMixin,
72
74
  // The app store: the public storefront, the reviews on it, and the listing a
73
75
  // publisher edits. A module OVER the platform — turn it off and OAuth still
74
76
  // works — so it is its own surface rather than more of `accounts`.
@@ -91,9 +93,6 @@ const MIXIN_PIPELINE = [
91
93
  // User nodes / decentralization (Fase 5): register/read/revoke/manage the
92
94
  // caller's personal data node + ingest hint.
93
95
  OxyServicesNodesMixin,
94
- // Link previews / unfurls: SDK-owned link-metadata resolution via oxy-api,
95
- // so apps stop scraping link metadata locally.
96
- OxyServicesLinksMixin,
97
96
  // The user-owned follow graph (#809). One relationship per user and target,
98
97
  // shared across applications, with per-application context on top.
99
98
  OxyServicesFollowGraphMixin,
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -45,3 +45,5 @@ export { evictOxyIdentityCache, oxyUserByIdCacheKey, OXY_IDENTITY_CACHE_PREFIXES
45
45
  // lets `@oxy.so/api` derive `auth.<apex>` without duplicating PSL logic.
46
46
  export { registrableApex } from '../utils/registrableApex.js';
47
47
  export { isOfficialWebOrigin } from '../utils/officialOrigins.js';
48
+ export { createEcosystemTraffic } from './traffic.js';
49
+ export { observeNodeHttp, withoutNodeHttpObservation } from './trafficNodeHttp.js';