@oxy.so/core 1.0.1 → 1.1.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/OxyServices.errors.js +28 -1
- package/dist/cjs/i18n/locales/en-US.json +14 -1
- package/dist/cjs/i18n/locales/es-ES.json +14 -1
- package/dist/cjs/i18n/locales/locales/en-US.json +14 -1
- package/dist/cjs/i18n/locales/locales/es-ES.json +14 -1
- package/dist/cjs/mixins/OxyServices.agency.js +113 -0
- package/dist/cjs/mixins/OxyServices.assets.js +90 -0
- package/dist/cjs/mixins/OxyServices.connectedApps.js +20 -3
- package/dist/cjs/mixins/OxyServices.utility.js +1 -21
- package/dist/cjs/mixins/index.js +3 -4
- package/dist/cjs/server/index.js +6 -1
- package/dist/cjs/server/traffic.js +107 -0
- package/dist/cjs/server/trafficNodeHttp.js +86 -0
- package/dist/cjs/utils/textNormalization.js +1 -1
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/OxyServices.errors.js +26 -0
- package/dist/esm/i18n/locales/en-US.json +14 -1
- package/dist/esm/i18n/locales/es-ES.json +14 -1
- package/dist/esm/i18n/locales/locales/en-US.json +14 -1
- package/dist/esm/i18n/locales/locales/es-ES.json +14 -1
- package/dist/esm/mixins/OxyServices.agency.js +110 -0
- package/dist/esm/mixins/OxyServices.assets.js +91 -1
- package/dist/esm/mixins/OxyServices.connectedApps.js +20 -3
- package/dist/esm/mixins/OxyServices.utility.js +1 -21
- package/dist/esm/mixins/index.js +3 -4
- package/dist/esm/server/index.js +2 -0
- package/dist/esm/server/traffic.js +104 -0
- package/dist/esm/server/trafficNodeHttp.js +79 -0
- package/dist/esm/utils/textNormalization.js +1 -1
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/OxyServices.errors.d.ts +24 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/mixins/OxyServices.agency.d.ts +135 -0
- package/dist/types/mixins/OxyServices.assets.d.ts +43 -1
- package/dist/types/mixins/OxyServices.connectedApps.d.ts +15 -0
- package/dist/types/mixins/OxyServices.links.d.ts +2 -2
- package/dist/types/mixins/OxyServices.utility.d.ts +0 -9
- package/dist/types/mixins/index.d.ts +2 -2
- package/dist/types/models/interfaces.d.ts +26 -0
- package/dist/types/server/index.d.ts +3 -0
- package/dist/types/server/traffic.d.ts +27 -0
- package/dist/types/server/trafficNodeHttp.d.ts +6 -0
- package/dist/types/utils/textNormalization.d.ts +1 -1
- package/package.json +4 -4
- package/src/OxyServices.errors.ts +36 -0
- package/src/i18n/locales/en-US.json +14 -1
- package/src/i18n/locales/es-ES.json +14 -1
- package/src/index.ts +15 -0
- package/src/mixins/OxyServices.agency.ts +277 -0
- package/src/mixins/OxyServices.assets.ts +105 -2
- package/src/mixins/OxyServices.connectedApps.ts +42 -4
- package/src/mixins/OxyServices.utility.ts +1 -26
- package/src/mixins/__tests__/OxyServices.serviceLinkedDownloadUrl.test.ts +139 -0
- package/src/mixins/__tests__/agency.test.ts +120 -0
- package/src/mixins/__tests__/connectedApps.test.ts +23 -0
- package/src/mixins/index.ts +4 -5
- package/src/models/interfaces.ts +27 -0
- package/src/server/__tests__/traffic.test.ts +49 -0
- package/src/server/__tests__/trafficNodeHttp.test.ts +38 -0
- package/src/server/index.ts +5 -0
- package/src/server/traffic.ts +116 -0
- package/src/server/trafficNodeHttp.ts +69 -0
- package/src/utils/textNormalization.ts +1 -1
- package/src/mixins/OxyServices.links.ts +0 -107
- package/src/mixins/__tests__/OxyServices.links.test.ts +0 -154
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.OxyAuthenticationTimeoutError = exports.ServiceAssetMetadataError = exports.AssetUrlResolutionError = exports.OxyAuthenticationError = void 0;
|
|
3
|
+
exports.OxyAuthenticationTimeoutError = exports.ServiceLinkedDownloadUrlError = exports.ServiceAssetMetadataError = exports.AssetUrlResolutionError = exports.OxyAuthenticationError = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Custom error types for better error handling
|
|
6
6
|
*/
|
|
@@ -86,6 +86,33 @@ class ServiceAssetMetadataError extends Error {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
exports.ServiceAssetMetadataError = ServiceAssetMetadataError;
|
|
89
|
+
/**
|
|
90
|
+
* A chunk of `POST /assets/service/linked-url` failed, so the SDK cannot say
|
|
91
|
+
* whether those ids have URLs.
|
|
92
|
+
*
|
|
93
|
+
* Its own class rather than a reuse of {@link ServiceAssetMetadataError}, and
|
|
94
|
+
* deliberately WITHOUT that one's `{ partial: true }` escape hatch, because the
|
|
95
|
+
* two mistakes cost different things. Reading a failed metadata lookup as
|
|
96
|
+
* "absent" embedded a media item with no hash. Reading a failed DOWNLOAD mint as
|
|
97
|
+
* "absent" tells a buyer who paid that the file they own is not there — and
|
|
98
|
+
* "not there" is already this route's answer for "not yours", so a best-effort
|
|
99
|
+
* mode would make a throttled request indistinguishable from a refusal. There is
|
|
100
|
+
* no download path that legitimately wants that, so there is no option to ask
|
|
101
|
+
* for it.
|
|
102
|
+
*/
|
|
103
|
+
class ServiceLinkedDownloadUrlError extends Error {
|
|
104
|
+
constructor(unresolvedIds, statuses, cause) {
|
|
105
|
+
const uniqueStatuses = Array.from(new Set(statuses)).sort((a, b) => a - b);
|
|
106
|
+
const statusSuffix = uniqueStatuses.length > 0 ? ` — status ${uniqueStatuses.join(', ')}` : '';
|
|
107
|
+
super(`Could not mint download URLs for ${unresolvedIds.length} id(s)${statusSuffix}. Treat this as unknown, never as "the caller holds no right".`);
|
|
108
|
+
this.code = 'SERVICE_LINKED_DOWNLOAD_URL_UNRESOLVED';
|
|
109
|
+
this.name = 'ServiceLinkedDownloadUrlError';
|
|
110
|
+
this.unresolvedIds = unresolvedIds;
|
|
111
|
+
this.statuses = uniqueStatuses;
|
|
112
|
+
this.cause = cause;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
exports.ServiceLinkedDownloadUrlError = ServiceLinkedDownloadUrlError;
|
|
89
116
|
class OxyAuthenticationTimeoutError extends OxyAuthenticationError {
|
|
90
117
|
constructor(operationName, timeoutMs) {
|
|
91
118
|
super(`Authentication timeout (${timeoutMs}ms): ${operationName} requires user authentication. Please ensure the user is logged in before calling this method.`, 'AUTH_TIMEOUT', 408);
|
|
@@ -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",
|
|
@@ -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",
|
|
@@ -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",
|
|
@@ -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,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OxyServicesAgencyMixin = OxyServicesAgencyMixin;
|
|
4
|
+
const mixinHelpers_1 = require("./mixinHelpers");
|
|
5
|
+
function query(path, key, value) {
|
|
6
|
+
return `${path}?${key}=${encodeURIComponent(value)}`;
|
|
7
|
+
}
|
|
8
|
+
function OxyServicesAgencyMixin(Base) {
|
|
9
|
+
return class extends Base {
|
|
10
|
+
async listAvailableCapabilityCatalogs(accountId) {
|
|
11
|
+
try {
|
|
12
|
+
const response = await this.makeRequest('GET', query('/capabilities/catalogs/available', 'accountId', accountId), undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.MEDIUM });
|
|
13
|
+
return response.catalogs;
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
throw this.handleError(error);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async listDelegationGrants(ownerAccountId) {
|
|
20
|
+
try {
|
|
21
|
+
const response = await this.makeRequest('GET', query('/capabilities/grants', 'ownerAccountId', ownerAccountId), undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.SHORT });
|
|
22
|
+
return response.grants;
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
throw this.handleError(error);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async createDelegationGrant(input) {
|
|
29
|
+
try {
|
|
30
|
+
const response = await this.makeRequest('POST', '/capabilities/grants', input, { cache: false });
|
|
31
|
+
this.clearCacheEntry(`GET:/capabilities/grants?ownerAccountId=${encodeURIComponent(input.ownerAccountId)}`);
|
|
32
|
+
return response.grant;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
throw this.handleError(error);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async revokeDelegationGrant(grantId, ownerAccountId) {
|
|
39
|
+
try {
|
|
40
|
+
await this.makeRequest('DELETE', `/capabilities/grants/${encodeURIComponent(grantId)}`, undefined, { cache: false });
|
|
41
|
+
this.clearCacheEntry(`GET:/capabilities/grants?ownerAccountId=${encodeURIComponent(ownerAccountId)}`);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
throw this.handleError(error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async updateDelegationGrant(grantId, ownerAccountId, input) {
|
|
48
|
+
try {
|
|
49
|
+
const response = await this.makeRequest('PUT', `/capabilities/grants/${encodeURIComponent(grantId)}`, input, { cache: false });
|
|
50
|
+
this.clearCacheEntry(`GET:/capabilities/grants?ownerAccountId=${encodeURIComponent(ownerAccountId)}`);
|
|
51
|
+
return response.grant;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
throw this.handleError(error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async listAccountCapabilityPolicies(accountId) {
|
|
58
|
+
try {
|
|
59
|
+
const response = await this.makeRequest('GET', query('/capabilities/account-policies', 'accountId', accountId), undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.SHORT });
|
|
60
|
+
return response.policies;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
throw this.handleError(error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async putAccountCapabilityPolicy(appId, input) {
|
|
67
|
+
try {
|
|
68
|
+
const response = await this.makeRequest('PUT', `/capabilities/account-policies/${encodeURIComponent(appId)}`, input, { cache: false });
|
|
69
|
+
this.clearCacheEntry(`GET:/capabilities/account-policies?accountId=${encodeURIComponent(input.accountId)}`);
|
|
70
|
+
return response.policy;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
throw this.handleError(error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async deleteAccountCapabilityPolicy(appId, accountId) {
|
|
77
|
+
try {
|
|
78
|
+
await this.makeRequest('DELETE', `/capabilities/account-policies/${encodeURIComponent(appId)}?accountId=${encodeURIComponent(accountId)}`, undefined, { cache: false });
|
|
79
|
+
this.clearCacheEntry(`GET:/capabilities/account-policies?accountId=${encodeURIComponent(accountId)}`);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
throw this.handleError(error);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async listCapabilityExecutionAuthorizations(ownerAccountId) {
|
|
86
|
+
try {
|
|
87
|
+
const response = await this.makeRequest('GET', query('/capabilities/execution-authorizations', 'ownerAccountId', ownerAccountId), undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.SHORT });
|
|
88
|
+
return response.authorizations;
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
throw this.handleError(error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async revokeCapabilityExecutionAuthorization(authorizationId, ownerAccountId) {
|
|
95
|
+
try {
|
|
96
|
+
await this.makeRequest('DELETE', `/capabilities/execution-authorizations/${encodeURIComponent(authorizationId)}`, undefined, { cache: false });
|
|
97
|
+
this.clearCacheEntry(`GET:/capabilities/execution-authorizations?ownerAccountId=${encodeURIComponent(ownerAccountId)}`);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
throw this.handleError(error);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async listCapabilityAuditEvents(accountId) {
|
|
104
|
+
try {
|
|
105
|
+
const response = await this.makeRequest('GET', query('/capabilities/audit', 'accountId', accountId), undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.SHORT });
|
|
106
|
+
return response.events;
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
throw this.handleError(error);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -19,6 +19,13 @@ const SERVICE_ASSET_METADATA_CHUNK_SIZE = 100;
|
|
|
19
19
|
* {@link SERVICE_ASSET_METADATA_CHUNK_SIZE} for the forward id lookup.
|
|
20
20
|
*/
|
|
21
21
|
const SERVICE_ASSET_METADATA_BY_SHA_CHUNK_SIZE = 100;
|
|
22
|
+
/**
|
|
23
|
+
* Maximum number of ids sent per `POST /assets/service/linked-url` request.
|
|
24
|
+
* Matches that route's own cap, which is a QUARTER of the metadata routes' —
|
|
25
|
+
* see `MAX_ASSETS_LINKED_URL` in the API for why minting download credentials is
|
|
26
|
+
* budgeted more tightly than projecting rows.
|
|
27
|
+
*/
|
|
28
|
+
const SERVICE_LINKED_DOWNLOAD_URL_CHUNK_SIZE = 25;
|
|
22
29
|
/**
|
|
23
30
|
* Lowercase hex SHA-256 digest matcher (exactly 64 hex chars). The reverse
|
|
24
31
|
* lookup drops non-conforming hashes client-side so a single malformed value
|
|
@@ -361,6 +368,89 @@ function OxyServicesAssetsMixin(Base) {
|
|
|
361
368
|
}
|
|
362
369
|
return settled.flat();
|
|
363
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* Mint short-lived direct download URLs for files the calling application's
|
|
373
|
+
* own users attached to it, via `POST /assets/service/linked-url`.
|
|
374
|
+
*
|
|
375
|
+
* This is the ONLY path in this SDK by which a service token reaches file
|
|
376
|
+
* BYTES. {@link getServiceAssetMetadataByIds} is metadata-only, and every
|
|
377
|
+
* other download helper here — {@link getFileDownloadUrl},
|
|
378
|
+
* {@link getFileDownloadUrlAsync}, {@link getBatchFileAccess} — resolves a URL
|
|
379
|
+
* for the CURRENT USER through `makeRequest`, which is a different question: a
|
|
380
|
+
* relying service holds no user session, and the buyer whose entitlement it is
|
|
381
|
+
* enforcing is not a viewer Oxy knows anything about.
|
|
382
|
+
*
|
|
383
|
+
* **Service-token auth (required), plus the `files:linked:read` scope**, which
|
|
384
|
+
* is NOT implied by `files:read`. The calling client must be service-configured
|
|
385
|
+
* (`configureServiceAuth(apiKey, apiSecret)`); a user-session request is
|
|
386
|
+
* rejected by the route's service-auth guard.
|
|
387
|
+
*
|
|
388
|
+
* **What the server will and will not give you.** A URL comes back only for a
|
|
389
|
+
* file whose OWN OWNER linked it to this application (`file_links.created_by =
|
|
390
|
+
* files.owner_user_id`). Every other id is omitted: unknown, deleted,
|
|
391
|
+
* system-owned, linked by somebody else, or not linked here at all. So the
|
|
392
|
+
* result is routinely shorter than the input and must be mapped by `id`.
|
|
393
|
+
*
|
|
394
|
+
* ABSENCE IS A REFUSAL, NOT A FACT ABOUT THE FILE, and the caller is the only
|
|
395
|
+
* party that can tell the difference. The route deliberately cannot — it
|
|
396
|
+
* answers identically for "no such file" and "not yours" so it leaks nothing —
|
|
397
|
+
* which means a service must decide what to tell its user from its OWN
|
|
398
|
+
* entitlement record, never from this response. Do not surface "file not
|
|
399
|
+
* found" on the strength of an omission.
|
|
400
|
+
*
|
|
401
|
+
* FAILURE IS NOT ABSENCE EITHER, and here it cannot be opted out of: a failed
|
|
402
|
+
* chunk throws {@link ServiceLinkedDownloadUrlError} carrying its ids, with no
|
|
403
|
+
* `{ partial: true }` mode. Swallowing a 429 would tell a user who paid for a
|
|
404
|
+
* file that it is not there. An empty input resolves to `[]` with no network
|
|
405
|
+
* call.
|
|
406
|
+
*
|
|
407
|
+
* Never cached at the SDK layer, and that is a stronger statement than the
|
|
408
|
+
* sibling methods' "low hit rate": each entry is a live credential for the
|
|
409
|
+
* bytes, so caching one keeps it usable after the service has decided the
|
|
410
|
+
* caller's entitlement ended.
|
|
411
|
+
*/
|
|
412
|
+
async getServiceLinkedDownloadUrls(ids) {
|
|
413
|
+
const uniqueIds = Array.from(new Set(ids.filter((id) => typeof id === 'string' && id.trim().length > 0)));
|
|
414
|
+
if (uniqueIds.length === 0) {
|
|
415
|
+
return [];
|
|
416
|
+
}
|
|
417
|
+
const chunks = [];
|
|
418
|
+
for (let i = 0; i < uniqueIds.length; i += SERVICE_LINKED_DOWNLOAD_URL_CHUNK_SIZE) {
|
|
419
|
+
chunks.push(uniqueIds.slice(i, i + SERVICE_LINKED_DOWNLOAD_URL_CHUNK_SIZE));
|
|
420
|
+
}
|
|
421
|
+
// Chunks stay independent so one failure never cancels work already in
|
|
422
|
+
// flight; the failures are collected and re-raised together below, exactly
|
|
423
|
+
// as in the metadata sibling.
|
|
424
|
+
const unresolvedIds = [];
|
|
425
|
+
const statuses = [];
|
|
426
|
+
let firstError;
|
|
427
|
+
const settled = await Promise.all(chunks.map(async (chunk) => {
|
|
428
|
+
try {
|
|
429
|
+
const entries = await this.makeServiceRequest('POST', '/assets/service/linked-url', { ids: chunk });
|
|
430
|
+
return Array.isArray(entries) ? entries : [];
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
const status = (0, errorUtils_1.extractErrorStatus)(error);
|
|
434
|
+
// `chunkSize` and `status`, never the ids and never a `url`: a minted
|
|
435
|
+
// URL in a log line is a credential in a log line.
|
|
436
|
+
logger_1.logger.warn('getServiceLinkedDownloadUrls: chunk failed', {
|
|
437
|
+
method: 'getServiceLinkedDownloadUrls',
|
|
438
|
+
chunkSize: chunk.length,
|
|
439
|
+
status,
|
|
440
|
+
error: error instanceof Error ? error.message : String(error),
|
|
441
|
+
});
|
|
442
|
+
unresolvedIds.push(...chunk);
|
|
443
|
+
if (typeof status === 'number')
|
|
444
|
+
statuses.push(status);
|
|
445
|
+
firstError ?? (firstError = error);
|
|
446
|
+
return [];
|
|
447
|
+
}
|
|
448
|
+
}));
|
|
449
|
+
if (unresolvedIds.length > 0) {
|
|
450
|
+
throw new OxyServices_errors_1.ServiceLinkedDownloadUrlError(unresolvedIds, statuses, firstError);
|
|
451
|
+
}
|
|
452
|
+
return settled.flat();
|
|
453
|
+
}
|
|
364
454
|
/**
|
|
365
455
|
* Reverse content-address lookup: resolve many content `sha256` digests to
|
|
366
456
|
* the servable Oxy asset holding each, in one round-trip per chunk via
|
|
@@ -4,9 +4,6 @@ exports.OxyServicesConnectedAppsMixin = OxyServicesConnectedAppsMixin;
|
|
|
4
4
|
const mixinHelpers_1 = require("./mixinHelpers");
|
|
5
5
|
function OxyServicesConnectedAppsMixin(Base) {
|
|
6
6
|
return class extends Base {
|
|
7
|
-
constructor(...args) {
|
|
8
|
-
super(...args);
|
|
9
|
-
}
|
|
10
7
|
/**
|
|
11
8
|
* Resolve an OAuth client identifier to the owning application's PUBLIC
|
|
12
9
|
* identity. No authentication required — the API returns only sanitized,
|
|
@@ -74,5 +71,25 @@ function OxyServicesConnectedAppsMixin(Base) {
|
|
|
74
71
|
throw this.handleError(error);
|
|
75
72
|
}
|
|
76
73
|
}
|
|
74
|
+
/** List resource-bound external MCP connections for the active Oxy account. */
|
|
75
|
+
async listConnectedMcpClients() {
|
|
76
|
+
try {
|
|
77
|
+
const response = await this.makeRequest('GET', '/auth/mcp/oauth/grants', undefined, { cache: true, cacheTTL: mixinHelpers_1.CACHE_TIMES.SHORT });
|
|
78
|
+
return response.grants;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
throw this.handleError(error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Revoke one external MCP connection and every token in its family. */
|
|
85
|
+
async revokeConnectedMcpClient(grantId) {
|
|
86
|
+
try {
|
|
87
|
+
await this.makeRequest('DELETE', `/auth/mcp/oauth/grants/${encodeURIComponent(grantId)}`, undefined, { cache: false });
|
|
88
|
+
this.clearCacheEntry('GET:/auth/mcp/oauth/grants');
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
throw this.handleError(error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
77
94
|
};
|
|
78
95
|
}
|
|
@@ -4,12 +4,10 @@ exports.OxyServicesUtilityMixin = OxyServicesUtilityMixin;
|
|
|
4
4
|
/**
|
|
5
5
|
* Utility Methods Mixin
|
|
6
6
|
*
|
|
7
|
-
* Provides utility methods including
|
|
8
|
-
* and Express.js authentication middleware
|
|
7
|
+
* Provides utility methods including Express.js authentication middleware.
|
|
9
8
|
*/
|
|
10
9
|
const jwt_decode_1 = require("jwt-decode");
|
|
11
10
|
const protocol_1 = require("@oxy.so/protocol");
|
|
12
|
-
const apiUtils_1 = require("../utils/apiUtils");
|
|
13
11
|
const logger_1 = require("../logger");
|
|
14
12
|
const oxyServiceEnvironment_1 = require("../utils/oxyServiceEnvironment");
|
|
15
13
|
/**
|
|
@@ -138,24 +136,6 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
138
136
|
return null;
|
|
139
137
|
}
|
|
140
138
|
}
|
|
141
|
-
/**
|
|
142
|
-
* Fetch link metadata
|
|
143
|
-
*/
|
|
144
|
-
async fetchLinkMetadata(url) {
|
|
145
|
-
try {
|
|
146
|
-
const path = (0, apiUtils_1.buildUrl)('/links/preview', { url, wait: 1 });
|
|
147
|
-
const preview = await this.makeRequest('GET', path, undefined, { cache: false });
|
|
148
|
-
return {
|
|
149
|
-
url: preview.url,
|
|
150
|
-
title: preview.title?.trim() || preview.url.replace(/^https?:\/\//, '').replace(/\/$/, ''),
|
|
151
|
-
description: preview.description?.trim() || 'Link',
|
|
152
|
-
image: preview.image,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
throw this.handleError(error);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
139
|
/**
|
|
160
140
|
* Express.js authentication middleware
|
|
161
141
|
*
|
package/dist/cjs/mixins/index.js
CHANGED
|
@@ -20,6 +20,7 @@ const OxyServices_reputation_1 = require("./OxyServices.reputation");
|
|
|
20
20
|
const OxyServices_assets_1 = require("./OxyServices.assets");
|
|
21
21
|
const OxyServices_accounts_1 = require("./OxyServices.accounts");
|
|
22
22
|
const OxyServices_connectedApps_1 = require("./OxyServices.connectedApps");
|
|
23
|
+
const OxyServices_agency_1 = require("./OxyServices.agency");
|
|
23
24
|
const OxyServices_store_1 = require("./OxyServices.store");
|
|
24
25
|
const OxyServices_location_1 = require("./OxyServices.location");
|
|
25
26
|
const OxyServices_analytics_1 = require("./OxyServices.analytics");
|
|
@@ -34,7 +35,6 @@ const OxyServices_appData_1 = require("./OxyServices.appData");
|
|
|
34
35
|
const OxyServices_civic_1 = require("./OxyServices.civic");
|
|
35
36
|
const OxyServices_chains_1 = require("./OxyServices.chains");
|
|
36
37
|
const OxyServices_nodes_1 = require("./OxyServices.nodes");
|
|
37
|
-
const OxyServices_links_1 = require("./OxyServices.links");
|
|
38
38
|
const OxyServices_followGraph_1 = require("./OxyServices.followGraph");
|
|
39
39
|
const OxyServices_inference_1 = require("./OxyServices.inference");
|
|
40
40
|
const OxyServices_deviceBoot_1 = require("./OxyServices.deviceBoot");
|
|
@@ -73,6 +73,8 @@ const MIXIN_PIPELINE = [
|
|
|
73
73
|
// OAuth-consent surface (public app identity + connected-app grants). Kept
|
|
74
74
|
// separate from account ownership.
|
|
75
75
|
OxyServices_connectedApps_1.OxyServicesConnectedAppsMixin,
|
|
76
|
+
// Oxy-owned delegated authority for Alia and digital-agent accounts.
|
|
77
|
+
OxyServices_agency_1.OxyServicesAgencyMixin,
|
|
76
78
|
// The app store: the public storefront, the reviews on it, and the listing a
|
|
77
79
|
// publisher edits. A module OVER the platform — turn it off and OAuth still
|
|
78
80
|
// works — so it is its own surface rather than more of `accounts`.
|
|
@@ -95,9 +97,6 @@ const MIXIN_PIPELINE = [
|
|
|
95
97
|
// User nodes / decentralization (Fase 5): register/read/revoke/manage the
|
|
96
98
|
// caller's personal data node + ingest hint.
|
|
97
99
|
OxyServices_nodes_1.OxyServicesNodesMixin,
|
|
98
|
-
// Link previews / unfurls: SDK-owned link-metadata resolution via oxy-api,
|
|
99
|
-
// so apps stop scraping link metadata locally.
|
|
100
|
-
OxyServices_links_1.OxyServicesLinksMixin,
|
|
101
100
|
// The user-owned follow graph (#809). One relationship per user and target,
|
|
102
101
|
// shared across applications, with per-application context on top.
|
|
103
102
|
OxyServices_followGraph_1.OxyServicesFollowGraphMixin,
|
package/dist/cjs/server/index.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
exports.isOfficialWebOrigin = exports.registrableApex = exports.OXY_IDENTITY_CACHE_PREFIXES = exports.oxyUserByIdCacheKey = exports.evictOxyIdentityCache = exports.publishOxyUserInvalidation = exports.createOxyUserInvalidationHandler = exports.verifySecret = exports.verifyCapabilityTicket = exports.readCapabilityAuthorization = exports.issueCapabilityTicket = exports.inputSatisfiesCapabilityLimits = exports.createCapabilityTicketMiddleware = exports.CapabilityTicketError = exports.OXY_CSP_BASELINE = exports.inlineScriptCspHash = exports.formatOxyCspPolicy = exports.extractInlineScripts = exports.cspSourcesFor = exports.createOxySecurityHeaders = exports.buildOxyPagesHeaders = exports.buildOxyCspDirectives = exports.createOxyCors = exports.UPSTREAM_HEADERS_TIMEOUT_MS = exports.MAX_URL_LENGTH = exports.MAX_REDIRECTS = exports.DEFAULT_USER_AGENT = exports.BLOCKED_HOSTNAMES = exports.ALLOWED_PROTOCOLS = exports.ALLOWED_PORTS = exports.UpstreamError = exports.SsrfRejection = exports.safeFetch = exports.isBlockedIp = exports.assertSafePublicUrl = exports.createOxyRateLimit = exports.OXY_SERVICE_ENVIRONMENTS = exports.requireOxyAuth = exports.isOxyAuthenticated = exports.getRequiredOxyUserId = exports.getRequiredOxyBillingPrincipal = exports.getOxyUserId = exports.getOxyRequestAttribution = exports.getOxyDelegatedUserId = exports.getOxyBillingPrincipal = exports.createOxyAuthMiddleware = exports.createOptionalOxyAuth = void 0;
|
|
19
|
+
exports.withoutNodeHttpObservation = exports.observeNodeHttp = exports.createEcosystemTraffic = exports.isOfficialWebOrigin = exports.registrableApex = exports.OXY_IDENTITY_CACHE_PREFIXES = exports.oxyUserByIdCacheKey = exports.evictOxyIdentityCache = exports.publishOxyUserInvalidation = exports.createOxyUserInvalidationHandler = exports.verifySecret = exports.verifyCapabilityTicket = exports.readCapabilityAuthorization = exports.issueCapabilityTicket = exports.inputSatisfiesCapabilityLimits = exports.createCapabilityTicketMiddleware = exports.CapabilityTicketError = exports.OXY_CSP_BASELINE = exports.inlineScriptCspHash = exports.formatOxyCspPolicy = exports.extractInlineScripts = exports.cspSourcesFor = exports.createOxySecurityHeaders = exports.buildOxyPagesHeaders = exports.buildOxyCspDirectives = exports.createOxyCors = exports.UPSTREAM_HEADERS_TIMEOUT_MS = exports.MAX_URL_LENGTH = exports.MAX_REDIRECTS = exports.DEFAULT_USER_AGENT = exports.BLOCKED_HOSTNAMES = exports.ALLOWED_PROTOCOLS = exports.ALLOWED_PORTS = exports.UpstreamError = exports.SsrfRejection = exports.safeFetch = exports.isBlockedIp = exports.assertSafePublicUrl = exports.createOxyRateLimit = exports.OXY_SERVICE_ENVIRONMENTS = exports.requireOxyAuth = exports.isOxyAuthenticated = exports.getRequiredOxyUserId = exports.getRequiredOxyBillingPrincipal = exports.getOxyUserId = exports.getOxyRequestAttribution = exports.getOxyDelegatedUserId = exports.getOxyBillingPrincipal = exports.createOxyAuthMiddleware = exports.createOptionalOxyAuth = void 0;
|
|
20
20
|
var auth_1 = require("./auth");
|
|
21
21
|
Object.defineProperty(exports, "createOptionalOxyAuth", { enumerable: true, get: function () { return auth_1.createOptionalOxyAuth; } });
|
|
22
22
|
Object.defineProperty(exports, "createOxyAuthMiddleware", { enumerable: true, get: function () { return auth_1.createOxyAuthMiddleware; } });
|
|
@@ -95,3 +95,8 @@ var registrableApex_1 = require("../utils/registrableApex");
|
|
|
95
95
|
Object.defineProperty(exports, "registrableApex", { enumerable: true, get: function () { return registrableApex_1.registrableApex; } });
|
|
96
96
|
var officialOrigins_1 = require("../utils/officialOrigins");
|
|
97
97
|
Object.defineProperty(exports, "isOfficialWebOrigin", { enumerable: true, get: function () { return officialOrigins_1.isOfficialWebOrigin; } });
|
|
98
|
+
var traffic_1 = require("./traffic");
|
|
99
|
+
Object.defineProperty(exports, "createEcosystemTraffic", { enumerable: true, get: function () { return traffic_1.createEcosystemTraffic; } });
|
|
100
|
+
var trafficNodeHttp_1 = require("./trafficNodeHttp");
|
|
101
|
+
Object.defineProperty(exports, "observeNodeHttp", { enumerable: true, get: function () { return trafficNodeHttp_1.observeNodeHttp; } });
|
|
102
|
+
Object.defineProperty(exports, "withoutNodeHttpObservation", { enumerable: true, get: function () { return trafficNodeHttp_1.withoutNodeHttpObservation; } });
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createEcosystemTraffic = createEcosystemTraffic;
|
|
4
|
+
const trafficNodeHttp_1 = require("./trafficNodeHttp");
|
|
5
|
+
const socket_1 = require("@oxy.so/telemetry/socket");
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
7
|
+
const OxyServices_1 = require("../OxyServices");
|
|
8
|
+
const collector_1 = require("@oxy.so/telemetry/collector");
|
|
9
|
+
/** Install once per service process, before registering HTTP routes. */
|
|
10
|
+
function createEcosystemTraffic(options) {
|
|
11
|
+
const region = options.region ?? process.env.AWS_REGION ?? 'unknown';
|
|
12
|
+
const baseURL = options.baseURL ?? process.env.OXY_API_URL ?? 'https://api.oxy.so';
|
|
13
|
+
const activityKey = process.env.OXY_ACTIVITY_API_KEY;
|
|
14
|
+
const activitySecret = process.env.OXY_ACTIVITY_API_SECRET;
|
|
15
|
+
if (!options.credential && Boolean(activityKey) !== Boolean(activitySecret)) {
|
|
16
|
+
throw new Error('Ecosystem activity requires a complete OXY_ACTIVITY_API_KEY and OXY_ACTIVITY_API_SECRET pair');
|
|
17
|
+
}
|
|
18
|
+
const apiKey = activityKey || process.env.OXY_SERVICE_API_KEY;
|
|
19
|
+
const apiSecret = activitySecret || process.env.OXY_SERVICE_API_SECRET;
|
|
20
|
+
if (!options.credential && (!apiKey || !apiSecret))
|
|
21
|
+
throw new Error('Ecosystem activity requires OXY_SERVICE_API_KEY and OXY_SERVICE_API_SECRET');
|
|
22
|
+
const oxy = new OxyServices_1.OxyServices({ baseURL });
|
|
23
|
+
if (apiKey && apiSecret)
|
|
24
|
+
oxy.configureServiceAuth(apiKey, apiSecret);
|
|
25
|
+
const credential = options.credential ?? (() => oxy.getServiceToken());
|
|
26
|
+
const fetcher = globalThis.fetch.bind(globalThis);
|
|
27
|
+
const instanceId = (0, node_crypto_1.randomUUID)();
|
|
28
|
+
const location = options.location ?? (0, collector_1.infrastructureLocation)(region);
|
|
29
|
+
if (!location)
|
|
30
|
+
throw new Error('Ecosystem activity requires a known infrastructure region or explicit location');
|
|
31
|
+
const post = async (path, data) => {
|
|
32
|
+
const token = await credential();
|
|
33
|
+
const response = await fetcher(new URL(path, baseURL), {
|
|
34
|
+
method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
|
|
35
|
+
body: JSON.stringify(data), signal: AbortSignal.timeout(4000),
|
|
36
|
+
});
|
|
37
|
+
if (!response.ok)
|
|
38
|
+
throw new Error('Activity collection failed');
|
|
39
|
+
};
|
|
40
|
+
const collector = (0, collector_1.createTrafficCollector)(events => post('/internal/activity', events));
|
|
41
|
+
let stopped = false;
|
|
42
|
+
let heartbeatWork = Promise.resolve();
|
|
43
|
+
let heartbeatPending = false;
|
|
44
|
+
const reportError = options.onError ?? (() => console.warn('Ecosystem activity publisher unavailable'));
|
|
45
|
+
const heartbeat = (removed = false) => {
|
|
46
|
+
if (!removed && (stopped || heartbeatPending))
|
|
47
|
+
return heartbeatWork;
|
|
48
|
+
heartbeatPending = true;
|
|
49
|
+
heartbeatWork = heartbeatWork.then(() => post('/internal/activity/infrastructure', { instanceId, service: options.service, region, ...location, status: options.ready?.() ? 'online' : 'unknown', removed })).catch(reportError).finally(() => { heartbeatPending = false; });
|
|
50
|
+
return heartbeatWork;
|
|
51
|
+
};
|
|
52
|
+
const flush = () => collector.flush().catch(reportError);
|
|
53
|
+
const cleanups = new Set();
|
|
54
|
+
const sockets = new WeakMap();
|
|
55
|
+
let fetchInstalled = false;
|
|
56
|
+
const wrapFetch = (fetcher) => {
|
|
57
|
+
const wrapped = (0, collector_1.instrumentTrafficFetch)(fetcher, collector, options.service, region, options.resolveEndpoint ?? collector_1.resolveOxyServiceEndpoint);
|
|
58
|
+
return Object.assign((...args) => (0, trafficNodeHttp_1.withoutNodeHttpObservation)(() => wrapped(...args)), fetcher);
|
|
59
|
+
};
|
|
60
|
+
const observeHttp = (0, collector_1.trafficMiddleware)(collector, options.service, region);
|
|
61
|
+
const timer = setInterval(() => { void flush(); }, 2000);
|
|
62
|
+
timer.unref?.();
|
|
63
|
+
void heartbeat();
|
|
64
|
+
const heartbeatTimer = setInterval(() => { void heartbeat(); }, 10000);
|
|
65
|
+
heartbeatTimer.unref?.();
|
|
66
|
+
return {
|
|
67
|
+
record: flow => collector.record({ ...flow, service: options.service, region }),
|
|
68
|
+
middleware: observeHttp,
|
|
69
|
+
observeHttp,
|
|
70
|
+
wrapFetch,
|
|
71
|
+
installFetch() {
|
|
72
|
+
if (fetchInstalled || stopped)
|
|
73
|
+
return;
|
|
74
|
+
fetchInstalled = true;
|
|
75
|
+
const original = globalThis.fetch;
|
|
76
|
+
const wrapped = wrapFetch(original);
|
|
77
|
+
globalThis.fetch = wrapped;
|
|
78
|
+
cleanups.add(() => { if (globalThis.fetch === wrapped)
|
|
79
|
+
globalThis.fetch = original; });
|
|
80
|
+
cleanups.add((0, trafficNodeHttp_1.observeNodeHttp)(collector, options.service, region, options.resolveEndpoint ?? collector_1.resolveOxyServiceEndpoint));
|
|
81
|
+
},
|
|
82
|
+
observeWebSocket(socket, socketOptions) {
|
|
83
|
+
const existing = sockets.get(socket);
|
|
84
|
+
if (existing)
|
|
85
|
+
return existing;
|
|
86
|
+
const unobserve = (0, socket_1.observeTrafficWebSocket)(socket, collector, options.service, region, socketOptions);
|
|
87
|
+
const cleanup = () => { unobserve(); socket.off('close', cleanup); cleanups.delete(cleanup); sockets.delete(socket); };
|
|
88
|
+
sockets.set(socket, cleanup);
|
|
89
|
+
socket.once('close', cleanup);
|
|
90
|
+
cleanups.add(cleanup);
|
|
91
|
+
return cleanup;
|
|
92
|
+
},
|
|
93
|
+
observeSocket(socket, socketOptions) {
|
|
94
|
+
const existing = sockets.get(socket);
|
|
95
|
+
if (existing)
|
|
96
|
+
return existing;
|
|
97
|
+
const unobserve = (0, socket_1.observeTrafficSocket)(socket, collector, options.service, region, socketOptions);
|
|
98
|
+
const cleanup = () => { unobserve(); socket.off('disconnect', cleanup); cleanups.delete(cleanup); sockets.delete(socket); };
|
|
99
|
+
sockets.set(socket, cleanup);
|
|
100
|
+
socket.once('disconnect', cleanup);
|
|
101
|
+
cleanups.add(cleanup);
|
|
102
|
+
return cleanup;
|
|
103
|
+
},
|
|
104
|
+
async stop() { if (stopped)
|
|
105
|
+
return; stopped = true; cleanups.forEach(cleanup => cleanup()); cleanups.clear(); clearInterval(timer); clearInterval(heartbeatTimer); await flush(); await heartbeat(true); },
|
|
106
|
+
};
|
|
107
|
+
}
|