@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
@@ -79,6 +79,30 @@ export declare class ServiceAssetMetadataError extends Error {
79
79
  readonly cause?: unknown;
80
80
  constructor(unresolvedIds: string[], statuses: number[], cause?: unknown);
81
81
  }
82
+ /**
83
+ * A chunk of `POST /assets/service/linked-url` failed, so the SDK cannot say
84
+ * whether those ids have URLs.
85
+ *
86
+ * Its own class rather than a reuse of {@link ServiceAssetMetadataError}, and
87
+ * deliberately WITHOUT that one's `{ partial: true }` escape hatch, because the
88
+ * two mistakes cost different things. Reading a failed metadata lookup as
89
+ * "absent" embedded a media item with no hash. Reading a failed DOWNLOAD mint as
90
+ * "absent" tells a buyer who paid that the file they own is not there — and
91
+ * "not there" is already this route's answer for "not yours", so a best-effort
92
+ * mode would make a throttled request indistinguishable from a refusal. There is
93
+ * no download path that legitimately wants that, so there is no option to ask
94
+ * for it.
95
+ */
96
+ export declare class ServiceLinkedDownloadUrlError extends Error {
97
+ readonly code = "SERVICE_LINKED_DOWNLOAD_URL_UNRESOLVED";
98
+ /** Every id belonging to a chunk whose request failed. */
99
+ readonly unresolvedIds: string[];
100
+ /** HTTP statuses observed across the failed chunks (deduped, ascending). */
101
+ readonly statuses: number[];
102
+ /** The first underlying transport/API failure — see the sibling class. */
103
+ readonly cause?: unknown;
104
+ constructor(unresolvedIds: string[], statuses: number[], cause?: unknown);
105
+ }
82
106
  export declare class OxyAuthenticationTimeoutError extends OxyAuthenticationError {
83
107
  constructor(operationName: string, timeoutMs: number);
84
108
  }
@@ -559,12 +559,14 @@ export declare class KeyManager {
559
559
  *
560
560
  * The domain separation is carried by `info` (e.g. `"oxypay/faircoin/v1"`),
561
561
  * so distinct apps/purposes get independent seeds from the same identity.
562
+ * That legacy Peable tag is a cryptographic contract and must not be renamed:
563
+ * changing it would derive a different wallet for every existing user.
562
564
  * The output is HKDF keying material, never the private key itself — a
563
- * consumer (e.g. Oxy Pay's FairCoin HD wallet) can feed it straight into
565
+ * consumer (e.g. Peable's FairCoin HD wallet) can feed it straight into
564
566
  * `HDKey.fromMasterSeed` and never touches the identity key.
565
567
  *
566
568
  * Key source (native only): prefers the shared ecosystem identity written to
567
- * `group.so.oxy.shared` (what a Relying Party like Oxy Pay reads), then falls
569
+ * `group.so.oxy.shared` (what a Relying Party like Peable reads), then falls
568
570
  * back to this device's primary identity (Commons/Accounts). Both reproduce
569
571
  * from the user's Oxy recovery phrase, so the derived seed is recoverable.
570
572
  *
@@ -38,7 +38,8 @@ export { getCanonicalUserHandle, getNormalizedUserHandle, } from './utils/userHa
38
38
  export type { CanonicalUserHandleInput, UserHandleInput } from './utils/userHandle';
39
39
  export { normalizeProfileLinks } from './utils/profileLinks';
40
40
  export type { ProfileLink, ProfileLinkMetadata } from './utils/profileLinks';
41
- export type { PublicApplication, ConnectedApp, } from './mixins/OxyServices.connectedApps';
41
+ export type { PublicApplication, ConnectedApp, ConnectedMcpClient, } from './mixins/OxyServices.connectedApps';
42
+ export type { AccountCapabilityPolicy, AvailableCapabilityCatalog, CapabilityExecutionAuthorization, CreateDelegationGrantInput, DelegationCatalogBinding, DelegationGrantView, PutAccountCapabilityPolicyInput, UpdateDelegationGrantInput, } from './mixins/OxyServices.agency';
42
43
  export type { StoreCategory, StoreRating, StoreListingSummary, StoreListingDetail, StoreScreenshot, StoreScreenshotPlatform, StoreReview, StoreOwnReview, WriteStoreReviewInput, StoreListingStatus, PublisherListing, WriteListingInput, AddScreenshotInput, UpdateScreenshotInput, StorePage, StorePageOptions, StoreReviewsOptions, } from './mixins/OxyServices.store';
43
44
  export type { AccountKind, AccountCategoryId, AccountRelationship, AccountRole, AccountMemberStatus, AccountMemberSource, AccountMember, AccountNode, ListAccountsOptions, CreateAccountInput, UpdateAccountInput, ProvisionChannelInput, ProvisionChannelMemberInput, ProvisionChannelResult, InviteAccountMemberInput, UpdateAccountMemberInput, TransferAccountOwnershipInput, AccountSuccessResult, SwitchAccountResult, Application, ApplicationType, ApplicationStatus, ApplicationCredential, ApplicationCredentialType, ApplicationCredentialStatus, ApplicationEnvironment, CreateApplicationInput, UpdateApplicationInput, CreateApplicationCredentialInput, RotateApplicationCredentialInput, ApplicationCredentialWithSecret, RotateApplicationCredentialResult, ApplicationUsagePeriod, ApplicationUsageSummary, ApplicationUsageByDay, ApplicationUsageByEndpoint, ApplicationUsageStats, } from './mixins/OxyServices.accounts';
44
45
  export { ACCOUNT_CATEGORY_IDS, MAX_ACCOUNT_CATEGORIES, SELECTABLE_ACCOUNT_CATEGORY_IDS, isSelectableAccountCategoryId, kindAcceptsAccountCategories, } from './mixins/OxyServices.accounts';
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Native Alia and digital-agent authority.
3
+ *
4
+ * These methods manage Oxy-owned delegation grants, account autonomy policy,
5
+ * execution authorizations and their audit trail. They deliberately use the
6
+ * signed-in account session: capability tickets and app service credentials
7
+ * stay on the server and are never exposed to Settings clients.
8
+ */
9
+ import type { AppCapabilityCatalog, AuditEvent, AutonomyLevel, CapabilityCatalogBinding, CapabilityPackage, DelegationGrant, GrantLimit, ResourceRef, ToolGrantOverride } from '@oxy.so/contracts';
10
+ import type { OxyServicesBase } from '../OxyServices.base';
11
+ export type DelegationCatalogBinding = CapabilityCatalogBinding;
12
+ export type DelegationGrantView = DelegationGrant;
13
+ export interface CreateDelegationGrantInput {
14
+ ownerAccountId: string;
15
+ actorAccountId: string;
16
+ resource: ResourceRef;
17
+ capabilityPackages: CapabilityPackage[];
18
+ capabilities: string[];
19
+ toolOverrides?: ToolGrantOverride[];
20
+ limits?: GrantLimit[];
21
+ maximumAutonomy: AutonomyLevel;
22
+ canRedelegate?: boolean;
23
+ expiresAt?: string | null;
24
+ }
25
+ export type UpdateDelegationGrantInput = Pick<CreateDelegationGrantInput, 'capabilityPackages' | 'capabilities' | 'toolOverrides' | 'limits' | 'maximumAutonomy' | 'canRedelegate' | 'expiresAt'>;
26
+ export interface AvailableCapabilityCatalog {
27
+ id: string;
28
+ appId: string;
29
+ version: string;
30
+ digest: string;
31
+ audience: string;
32
+ catalog: AppCapabilityCatalog;
33
+ }
34
+ export interface AccountCapabilityPolicy {
35
+ id: string;
36
+ accountId: string;
37
+ appSlug: string;
38
+ maximumAutonomy: AutonomyLevel;
39
+ deniedCapabilities: string[];
40
+ createdAt: string;
41
+ updatedAt: string;
42
+ }
43
+ export interface PutAccountCapabilityPolicyInput {
44
+ accountId: string;
45
+ maximumAutonomy: AutonomyLevel;
46
+ deniedCapabilities: string[];
47
+ }
48
+ export interface CapabilityExecutionAuthorization {
49
+ id: string;
50
+ kind: 'direct_request' | 'automation';
51
+ requesterAccountId: string;
52
+ ownerAccountId: string;
53
+ coordinatorApplicationId: string;
54
+ coordinatorCredentialId: string;
55
+ actorType: 'alia' | 'agent';
56
+ actorAccountId: string | null;
57
+ resourceApp: string;
58
+ effectiveAccountId: string;
59
+ resourceType: string;
60
+ resourceKey: string;
61
+ tool: string;
62
+ runId: string | null;
63
+ stepId: string | null;
64
+ automationId: string | null;
65
+ maximumAutonomy: AutonomyLevel;
66
+ limits: GrantLimit[];
67
+ expiresAt: string;
68
+ revokedAt: string | null;
69
+ createdAt: string;
70
+ updatedAt: string;
71
+ }
72
+ export declare function OxyServicesAgencyMixin<T extends typeof OxyServicesBase>(Base: T): {
73
+ new (...args: any[]): {
74
+ listAvailableCapabilityCatalogs(accountId: string): Promise<AvailableCapabilityCatalog[]>;
75
+ listDelegationGrants(ownerAccountId: string): Promise<DelegationGrantView[]>;
76
+ createDelegationGrant(input: CreateDelegationGrantInput): Promise<DelegationGrantView>;
77
+ revokeDelegationGrant(grantId: string, ownerAccountId: string): Promise<void>;
78
+ updateDelegationGrant(grantId: string, ownerAccountId: string, input: UpdateDelegationGrantInput): Promise<DelegationGrantView>;
79
+ listAccountCapabilityPolicies(accountId: string): Promise<AccountCapabilityPolicy[]>;
80
+ putAccountCapabilityPolicy(appId: string, input: PutAccountCapabilityPolicyInput): Promise<AccountCapabilityPolicy>;
81
+ deleteAccountCapabilityPolicy(appId: string, accountId: string): Promise<void>;
82
+ listCapabilityExecutionAuthorizations(ownerAccountId: string): Promise<CapabilityExecutionAuthorization[]>;
83
+ revokeCapabilityExecutionAuthorization(authorizationId: string, ownerAccountId: string): Promise<void>;
84
+ listCapabilityAuditEvents(accountId: string): Promise<AuditEvent[]>;
85
+ httpService: import("../HttpService").HttpService;
86
+ cloudURL: string;
87
+ config: import("../OxyServices.base").OxyConfig;
88
+ __resetTokensForTests(): void;
89
+ makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
90
+ getBaseURL(): string;
91
+ getClient(): import("../HttpService").HttpService;
92
+ createLinkedClient(config: import("../OxyServices.base").OxyConfig): import("..").LinkedHttpClient;
93
+ getMetrics(): {
94
+ totalRequests: number;
95
+ successfulRequests: number;
96
+ failedRequests: number;
97
+ cacheHits: number;
98
+ cacheMisses: number;
99
+ averageResponseTime: number;
100
+ };
101
+ clearCache(): void;
102
+ clearCacheEntry(key: string): void;
103
+ clearCacheByPrefix(prefix: string): number;
104
+ getCacheStats(): {
105
+ size: number;
106
+ hits: number;
107
+ misses: number;
108
+ hitRate: number;
109
+ };
110
+ getCloudURL(): string;
111
+ setTokens(accessToken: string): void;
112
+ clearTokens(): void;
113
+ onTokensChanged(listener: (accessToken: string | null) => void): () => void;
114
+ _cachedUserId: string | null | undefined;
115
+ _cachedAccessToken: string | null;
116
+ getCurrentUserId(): string | null;
117
+ hasValidToken(): boolean;
118
+ getAccessToken(): string | null;
119
+ getAccessTokenExpiry(): number | null;
120
+ waitForAuth(timeoutMs?: number): Promise<boolean>;
121
+ withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
122
+ maxRetries?: number;
123
+ retryDelay?: number;
124
+ authTimeoutMs?: number;
125
+ }): Promise<T_1>;
126
+ validate(): Promise<boolean>;
127
+ handleError(error: unknown): Error;
128
+ healthCheck(): Promise<{
129
+ status: string;
130
+ users?: number;
131
+ timestamp?: string;
132
+ [key: string]: any;
133
+ }>;
134
+ };
135
+ } & T;
@@ -1,4 +1,4 @@
1
- import type { AccountStorageUsageResponse, AssetUploadInput, AssetUrlResponse, AssetVariant, BatchFileAccessResponse, ServiceAssetMetadata, ServiceAssetMetadataBySha } from '../models/interfaces';
1
+ import type { AccountStorageUsageResponse, AssetUploadInput, AssetUrlResponse, AssetVariant, BatchFileAccessResponse, ServiceAssetMetadata, ServiceAssetMetadataBySha, ServiceLinkedDownloadUrl } from '../models/interfaces';
2
2
  import type { OxyServicesBase } from '../OxyServices.base';
3
3
  export declare function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>(Base: T): {
4
4
  new (...args: any[]): {
@@ -181,6 +181,48 @@ export declare function OxyServicesAssetsMixin<T extends typeof OxyServicesBase>
181
181
  getServiceAssetMetadataByIds(ids: string[], options?: {
182
182
  partial?: boolean;
183
183
  }): Promise<ServiceAssetMetadata[]>;
184
+ /**
185
+ * Mint short-lived direct download URLs for files the calling application's
186
+ * own users attached to it, via `POST /assets/service/linked-url`.
187
+ *
188
+ * This is the ONLY path in this SDK by which a service token reaches file
189
+ * BYTES. {@link getServiceAssetMetadataByIds} is metadata-only, and every
190
+ * other download helper here — {@link getFileDownloadUrl},
191
+ * {@link getFileDownloadUrlAsync}, {@link getBatchFileAccess} — resolves a URL
192
+ * for the CURRENT USER through `makeRequest`, which is a different question: a
193
+ * relying service holds no user session, and the buyer whose entitlement it is
194
+ * enforcing is not a viewer Oxy knows anything about.
195
+ *
196
+ * **Service-token auth (required), plus the `files:linked:read` scope**, which
197
+ * is NOT implied by `files:read`. The calling client must be service-configured
198
+ * (`configureServiceAuth(apiKey, apiSecret)`); a user-session request is
199
+ * rejected by the route's service-auth guard.
200
+ *
201
+ * **What the server will and will not give you.** A URL comes back only for a
202
+ * file whose OWN OWNER linked it to this application (`file_links.created_by =
203
+ * files.owner_user_id`). Every other id is omitted: unknown, deleted,
204
+ * system-owned, linked by somebody else, or not linked here at all. So the
205
+ * result is routinely shorter than the input and must be mapped by `id`.
206
+ *
207
+ * ABSENCE IS A REFUSAL, NOT A FACT ABOUT THE FILE, and the caller is the only
208
+ * party that can tell the difference. The route deliberately cannot — it
209
+ * answers identically for "no such file" and "not yours" so it leaks nothing —
210
+ * which means a service must decide what to tell its user from its OWN
211
+ * entitlement record, never from this response. Do not surface "file not
212
+ * found" on the strength of an omission.
213
+ *
214
+ * FAILURE IS NOT ABSENCE EITHER, and here it cannot be opted out of: a failed
215
+ * chunk throws {@link ServiceLinkedDownloadUrlError} carrying its ids, with no
216
+ * `{ partial: true }` mode. Swallowing a 429 would tell a user who paid for a
217
+ * file that it is not there. An empty input resolves to `[]` with no network
218
+ * call.
219
+ *
220
+ * Never cached at the SDK layer, and that is a stronger statement than the
221
+ * sibling methods' "low hit rate": each entry is a live credential for the
222
+ * bytes, so caching one keeps it usable after the service has decided the
223
+ * caller's entitlement ended.
224
+ */
225
+ getServiceLinkedDownloadUrls(ids: string[]): Promise<ServiceLinkedDownloadUrl[]>;
184
226
  /**
185
227
  * Reverse content-address lookup: resolve many content `sha256` digests to
186
228
  * the servable Oxy asset holding each, in one round-trip per chunk via
@@ -77,6 +77,17 @@ export interface ConnectedApp {
77
77
  /** ISO timestamp of when the grant was last exercised. */
78
78
  lastUsedAt: string;
79
79
  }
80
+ /** An external MCP client authorized for one exact app resource and account. */
81
+ export interface ConnectedMcpClient {
82
+ id: string;
83
+ appSlug: string;
84
+ resource: string;
85
+ scopes: string[];
86
+ clientId: string;
87
+ clientName: string;
88
+ createdAt: string;
89
+ lastUsedAt: string;
90
+ }
80
91
  export declare function OxyServicesConnectedAppsMixin<T extends typeof OxyServicesBase>(Base: T): {
81
92
  new (...args: any[]): {
82
93
  /**
@@ -116,6 +127,10 @@ export declare function OxyServicesConnectedAppsMixin<T extends typeof OxyServic
116
127
  * @param applicationId - The connected application's Mongo `_id`.
117
128
  */
118
129
  revokeAppGrant(applicationId: string): Promise<void>;
130
+ /** List resource-bound external MCP connections for the active Oxy account. */
131
+ listConnectedMcpClients(): Promise<ConnectedMcpClient[]>;
132
+ /** Revoke one external MCP connection and every token in its family. */
133
+ revokeConnectedMcpClient(grantId: string): Promise<void>;
119
134
  httpService: import("../HttpService").HttpService;
120
135
  cloudURL: string;
121
136
  config: import("../OxyServices.base").OxyConfig;
@@ -121,15 +121,6 @@ export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase
121
121
  * @internal Used by the auth() middleware — not part of the public API
122
122
  */
123
123
  verifyServiceActingAs(appId: string, userId: string): Promise<ServiceActingAsVerification | null>;
124
- /**
125
- * Fetch link metadata
126
- */
127
- fetchLinkMetadata(url: string): Promise<{
128
- url: string;
129
- title: string;
130
- description: string;
131
- image?: string;
132
- }>;
133
124
  /**
134
125
  * Express.js authentication middleware
135
126
  *
@@ -16,6 +16,7 @@ import { OxyServicesReputationMixin } from './OxyServices.reputation';
16
16
  import { OxyServicesAssetsMixin } from './OxyServices.assets';
17
17
  import { OxyServicesAccountsMixin } from './OxyServices.accounts';
18
18
  import { OxyServicesConnectedAppsMixin } from './OxyServices.connectedApps';
19
+ import { OxyServicesAgencyMixin } from './OxyServices.agency';
19
20
  import { OxyServicesStoreMixin } from './OxyServices.store';
20
21
  import { OxyServicesLocationMixin } from './OxyServices.location';
21
22
  import { OxyServicesAnalyticsMixin } from './OxyServices.analytics';
@@ -30,7 +31,6 @@ import { OxyServicesAppDataMixin } from './OxyServices.appData';
30
31
  import { OxyServicesCivicMixin } from './OxyServices.civic';
31
32
  import { OxyServicesChainsMixin } from './OxyServices.chains';
32
33
  import { OxyServicesNodesMixin } from './OxyServices.nodes';
33
- import { OxyServicesLinksMixin } from './OxyServices.links';
34
34
  import { OxyServicesFollowGraphMixin } from './OxyServices.followGraph';
35
35
  import { OxyServicesInferenceMixin } from './OxyServices.inference';
36
36
  import { OxyServicesDeviceBootMixin } from './OxyServices.deviceBoot';
@@ -44,7 +44,7 @@ import { OxyServicesDeviceTransferMixin } from './OxyServices.deviceTransfer';
44
44
  * If you add a new mixin to `MIXIN_PIPELINE`, add it here too so its methods
45
45
  * are visible without a cast.
46
46
  */
47
- type AllMixinInstances = InstanceType<ReturnType<typeof OxyServicesAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUserMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesIdentityMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesIdentityBackupMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPrivacyMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLanguageMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPaymentMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesReputationMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAssetsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAccountsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesConnectedAppsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesStoreMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLocationMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAnalyticsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesDevicesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesSecurityMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFeaturesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesTopicsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesContactsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesNotificationsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAppDataMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesCivicMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesChainsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesNodesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLinksMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFollowGraphMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesInferenceMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesDeviceBootMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesDeviceTransferMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUtilityMixin<typeof OxyServicesBase>>>;
47
+ type AllMixinInstances = InstanceType<ReturnType<typeof OxyServicesAuthMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUserMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesIdentityMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesIdentityBackupMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPrivacyMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLanguageMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesPaymentMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesReputationMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAssetsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAccountsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesConnectedAppsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAgencyMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesStoreMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesLocationMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAnalyticsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesDevicesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesSecurityMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFeaturesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesTopicsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesContactsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesNotificationsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesAppDataMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesCivicMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesChainsMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesNodesMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesFollowGraphMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesInferenceMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesDeviceBootMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesDeviceTransferMixin<typeof OxyServicesBase>>> & InstanceType<ReturnType<typeof OxyServicesUtilityMixin<typeof OxyServicesBase>>>;
48
48
  /**
49
49
  * Constructor type for the fully composed mixin pipeline. Each mixin returns
50
50
  * a new constructor that augments its input; reducing across the pipeline
@@ -525,6 +525,32 @@ export interface AssetUpdateVisibilityResponse {
525
525
  * deleted ids are omitted from the response (never error the whole batch),
526
526
  * so the result may be shorter than the requested id list.
527
527
  */
528
+ /**
529
+ * A short-lived direct download URL for a file the calling application's own
530
+ * user attached to it, from `POST /assets/service/linked-url`.
531
+ *
532
+ * `url` is BEARER AUTHORITY over the bytes for `expiresIn` seconds: whoever
533
+ * holds the string is the reader, and nothing about the calling service's
534
+ * entitlement is re-evaluated when it is used. Hand it only to a caller the
535
+ * service has itself authorized, and never log it.
536
+ *
537
+ * `expiresIn` bounds when a transfer may START, not how long one may take. A
538
+ * large file downloads fine on a slow link; a RESUMED range request after the
539
+ * deadline does not, and needs a fresh URL.
540
+ *
541
+ * Unlike {@link ServiceAssetMetadata}, the absence of an id from the response is
542
+ * an AUTHORIZATION answer as often as an existence one — see
543
+ * `getServiceLinkedDownloadUrls`.
544
+ */
545
+ export interface ServiceLinkedDownloadUrl {
546
+ id: string;
547
+ url: string;
548
+ /** Seconds from issue until `url` stops being accepted. */
549
+ expiresIn: number;
550
+ mime: string;
551
+ size: number;
552
+ sha256: string;
553
+ }
528
554
  export interface ServiceAssetMetadata {
529
555
  id: string;
530
556
  sha256: string;
@@ -33,3 +33,6 @@ export { evictOxyIdentityCache, oxyUserByIdCacheKey, OXY_IDENTITY_CACHE_PREFIXES
33
33
  export type { OxyIdentityCacheEvictor } from '../utils/identityCacheSweep';
34
34
  export { registrableApex } from '../utils/registrableApex';
35
35
  export { isOfficialWebOrigin } from '../utils/officialOrigins';
36
+ export { createEcosystemTraffic } from './traffic';
37
+ export type { EcosystemTrafficOptions } from './traffic';
38
+ export { observeNodeHttp, withoutNodeHttpObservation } from './trafficNodeHttp';
@@ -0,0 +1,27 @@
1
+ import { observeTrafficSocket, observeTrafficWebSocket, type TrafficWebSocket, type TrafficSocket } from '@oxy.so/telemetry/socket';
2
+ import type { RequestHandler } from 'express';
3
+ import { trafficMiddleware, type TrafficFlow, type ServiceEndpoint } from '@oxy.so/telemetry/collector';
4
+ export interface EcosystemTrafficOptions {
5
+ service: string;
6
+ region?: string;
7
+ baseURL?: string;
8
+ credential?: () => Promise<string>;
9
+ resolveEndpoint?: (url: URL) => ServiceEndpoint | undefined;
10
+ onError?: () => void;
11
+ ready?: () => boolean;
12
+ location?: {
13
+ label: string;
14
+ coordinates: [number, number];
15
+ };
16
+ }
17
+ /** Install once per service process, before registering HTTP routes. */
18
+ export declare function createEcosystemTraffic(options: EcosystemTrafficOptions): {
19
+ middleware: RequestHandler;
20
+ record(flow: Omit<TrafficFlow, 'service' | 'region'>): void;
21
+ wrapFetch(fetcher: typeof fetch): typeof fetch;
22
+ installFetch(): void;
23
+ observeHttp: ReturnType<typeof trafficMiddleware>;
24
+ observeSocket(socket: TrafficSocket, options?: Parameters<typeof observeTrafficSocket>[4]): () => void;
25
+ observeWebSocket(socket: TrafficWebSocket, options?: Parameters<typeof observeTrafficWebSocket>[4]): () => void;
26
+ stop(): Promise<void>;
27
+ };
@@ -0,0 +1,6 @@
1
+ import { type TrafficFlow, type ServiceEndpoint } from '@oxy.so/telemetry/collector';
2
+ export declare function withoutNodeHttpObservation<T>(callback: () => T): T;
3
+ /** Node and Bun share the HTTP facade; Bun does not emit Node's HTTP diagnostic channels. */
4
+ export declare function observeNodeHttp(collector: {
5
+ record(flow: TrafficFlow): void;
6
+ }, service: string, region: string, resolveEndpoint: (url: URL) => ServiceEndpoint | undefined): () => void;
@@ -43,7 +43,7 @@
43
43
  * normalize whitespace and Unicode form.
44
44
  */
45
45
  /**
46
- * Normalize a SINGLE-LINE text value: page/link-preview titles, `siteName`,
46
+ * Normalize a SINGLE-LINE text value such as a page title or `siteName`,
47
47
  * display names, image alt text, handles, profile field labels.
48
48
  *
49
49
  * A line break in such a value is never meaningful — it is an artifact of the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxy.so/core",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -86,7 +86,7 @@
86
86
  "node": ">=18.0.0"
87
87
  },
88
88
  "scripts": {
89
- "build": "bun run build:cjs && bun run build:esm && bun run build:types && bun run copy-assets",
89
+ "build": "bun run --filter @oxy.so/telemetry build && bun run build:cjs && bun run build:esm && bun run build:types && bun run copy-assets && node scripts/mark-module-formats.mjs",
90
90
  "copy-assets": "cp -r src/i18n/locales dist/cjs/i18n/locales && cp -r src/i18n/locales dist/esm/i18n/locales",
91
91
  "build:cjs": "tsc -p tsconfig.cjs.json",
92
92
  "build:esm": "tsc -p tsconfig.esm.json && node scripts/fix-esm-imports.mjs",
@@ -94,8 +94,9 @@
94
94
  "clean": "rm -rf dist",
95
95
  "typescript": "tsc --noEmit",
96
96
  "test": "jest --passWithNoTests",
97
+ "test:package": "node scripts/verify-package-formats.mjs",
97
98
  "lint": "biome lint --error-on-warnings ./src",
98
- "prepublishOnly": "node ../../scripts/assert-bun-publish.mjs && bun run clean && bun run build",
99
+ "prepublishOnly": "node ../../scripts/assert-bun-publish.mjs && bun run clean && bun run build && bun run test:package",
99
100
  "release": "rm -rf dist && bun run build && release-it",
100
101
  "generate:display-name-policy": "node scripts/generateDisplayNamePolicyRanges.mjs"
101
102
  },
@@ -114,11 +115,11 @@
114
115
  }
115
116
  },
116
117
  "dependencies": {
117
- "@oxy.so/telemetry": "^1.0.0",
118
+ "@oxy.so/telemetry": "^1.1.1",
118
119
  "@noble/ciphers": "^1.3.0",
119
120
  "@noble/hashes": "^1.8.0",
120
- "@oxy.so/contracts": "^1.0.0",
121
- "@oxy.so/protocol": "^1.0.0",
121
+ "@oxy.so/contracts": "^1.1.1",
122
+ "@oxy.so/protocol": "^1.0.1",
122
123
  "@scure/bip39": "^1.6.0",
123
124
  "buffer": "^6.0.3",
124
125
  "express-rate-limit": "^8.6.0",
@@ -109,6 +109,42 @@ export class ServiceAssetMetadataError extends Error {
109
109
  }
110
110
  }
111
111
 
112
+ /**
113
+ * A chunk of `POST /assets/service/linked-url` failed, so the SDK cannot say
114
+ * whether those ids have URLs.
115
+ *
116
+ * Its own class rather than a reuse of {@link ServiceAssetMetadataError}, and
117
+ * deliberately WITHOUT that one's `{ partial: true }` escape hatch, because the
118
+ * two mistakes cost different things. Reading a failed metadata lookup as
119
+ * "absent" embedded a media item with no hash. Reading a failed DOWNLOAD mint as
120
+ * "absent" tells a buyer who paid that the file they own is not there — and
121
+ * "not there" is already this route's answer for "not yours", so a best-effort
122
+ * mode would make a throttled request indistinguishable from a refusal. There is
123
+ * no download path that legitimately wants that, so there is no option to ask
124
+ * for it.
125
+ */
126
+ export class ServiceLinkedDownloadUrlError extends Error {
127
+ public readonly code = 'SERVICE_LINKED_DOWNLOAD_URL_UNRESOLVED';
128
+ /** Every id belonging to a chunk whose request failed. */
129
+ public readonly unresolvedIds: string[];
130
+ /** HTTP statuses observed across the failed chunks (deduped, ascending). */
131
+ public readonly statuses: number[];
132
+ /** The first underlying transport/API failure — see the sibling class. */
133
+ public readonly cause?: unknown;
134
+
135
+ constructor(unresolvedIds: string[], statuses: number[], cause?: unknown) {
136
+ const uniqueStatuses = Array.from(new Set(statuses)).sort((a, b) => a - b);
137
+ const statusSuffix = uniqueStatuses.length > 0 ? ` — status ${uniqueStatuses.join(', ')}` : '';
138
+ super(
139
+ `Could not mint download URLs for ${unresolvedIds.length} id(s)${statusSuffix}. Treat this as unknown, never as "the caller holds no right".`,
140
+ );
141
+ this.name = 'ServiceLinkedDownloadUrlError';
142
+ this.unresolvedIds = unresolvedIds;
143
+ this.statuses = uniqueStatuses;
144
+ this.cause = cause;
145
+ }
146
+ }
147
+
112
148
  export class OxyAuthenticationTimeoutError extends OxyAuthenticationError {
113
149
  constructor(operationName: string, timeoutMs: number) {
114
150
  super(
@@ -146,7 +146,7 @@ export type IdentityRecoveryResult =
146
146
  /**
147
147
  * HKDF salt that domain-separates every identity-scoped seed produced by
148
148
  * {@link KeyManager.deriveScopedSeed}. Versioned so a future scheme change is a
149
- * new, non-colliding tag. The per-app domain (e.g. Oxy Pay's FairCoin wallet)
149
+ * new, non-colliding tag. The per-app domain (e.g. Peable's FairCoin wallet)
150
150
  * is carried by the caller's `info` string, not this salt.
151
151
  */
152
152
  const SCOPED_SEED_KDF_SALT = 'oxy-identity-scoped-seed-v1';
@@ -2567,12 +2567,14 @@ export class KeyManager {
2567
2567
  *
2568
2568
  * The domain separation is carried by `info` (e.g. `"oxypay/faircoin/v1"`),
2569
2569
  * so distinct apps/purposes get independent seeds from the same identity.
2570
+ * That legacy Peable tag is a cryptographic contract and must not be renamed:
2571
+ * changing it would derive a different wallet for every existing user.
2570
2572
  * The output is HKDF keying material, never the private key itself — a
2571
- * consumer (e.g. Oxy Pay's FairCoin HD wallet) can feed it straight into
2573
+ * consumer (e.g. Peable's FairCoin HD wallet) can feed it straight into
2572
2574
  * `HDKey.fromMasterSeed` and never touches the identity key.
2573
2575
  *
2574
2576
  * Key source (native only): prefers the shared ecosystem identity written to
2575
- * `group.so.oxy.shared` (what a Relying Party like Oxy Pay reads), then falls
2577
+ * `group.so.oxy.shared` (what a Relying Party like Peable reads), then falls
2576
2578
  * back to this device's primary identity (Commons/Accounts). Both reproduce
2577
2579
  * from the user's Oxy recovery phrase, so the derived seed is recoverable.
2578
2580
  *
@@ -2604,4 +2606,3 @@ export class KeyManager {
2604
2606
  }
2605
2607
 
2606
2608
  export default KeyManager;
2607
-
@@ -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",