@oxyhq/core 3.15.0 → 3.16.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.
@@ -102,26 +102,27 @@ describe('OxyServices — "Sign in with Oxy" handoff', () => {
102
102
  });
103
103
 
104
104
  describe('getCommonsApprovalInfo (approver)', () => {
105
- it('GETs the server-resolved approval info by authorizeCode', async () => {
106
- const info = {
107
- application: {
108
- id: 'app1',
109
- name: 'Mention',
110
- type: 'first_party' as const,
111
- isOfficial: true,
112
- isInternal: false,
113
- scopes: ['profile'],
114
- },
105
+ const baseInfo = {
106
+ application: {
107
+ id: 'app1',
108
+ name: 'Mention',
109
+ type: 'first_party' as const,
110
+ isOfficial: true,
111
+ isInternal: false,
115
112
  scopes: ['profile'],
116
- boundOrigin: 'https://mention.earth',
117
- expiresAt: 1700000300000,
118
- status: 'pending',
119
- };
120
- makeRequestSpy.mockResolvedValue(info);
113
+ },
114
+ scopes: ['profile'],
115
+ boundOrigin: 'https://mention.earth',
116
+ expiresAt: 1700000300000,
117
+ status: 'pending',
118
+ };
119
+
120
+ it('GETs the server-resolved approval info by authorizeCode', async () => {
121
+ makeRequestSpy.mockResolvedValue({ ...baseInfo, originVerified: true });
121
122
 
122
123
  const result = await oxy.getCommonsApprovalInfo('code-1');
123
124
 
124
- expect(result).toEqual(info);
125
+ expect(result).toEqual({ ...baseInfo, originVerified: true });
125
126
  expect(makeRequestSpy).toHaveBeenCalledWith(
126
127
  'GET',
127
128
  '/auth/session/approve-info/code-1',
@@ -129,6 +130,30 @@ describe('OxyServices — "Sign in with Oxy" handoff', () => {
129
130
  expect.objectContaining({ cache: false }),
130
131
  );
131
132
  });
133
+
134
+ it('coerces a missing originVerified to false (fail-safe to "not verified")', async () => {
135
+ makeRequestSpy.mockResolvedValue(baseInfo);
136
+
137
+ const result = await oxy.getCommonsApprovalInfo('code-1');
138
+
139
+ expect(result.originVerified).toBe(false);
140
+ });
141
+
142
+ it('coerces a non-boolean originVerified to false', async () => {
143
+ makeRequestSpy.mockResolvedValue({ ...baseInfo, originVerified: 'yes' });
144
+
145
+ const result = await oxy.getCommonsApprovalInfo('code-1');
146
+
147
+ expect(result.originVerified).toBe(false);
148
+ });
149
+
150
+ it('passes through a server originVerified:false unchanged', async () => {
151
+ makeRequestSpy.mockResolvedValue({ ...baseInfo, originVerified: false });
152
+
153
+ const result = await oxy.getCommonsApprovalInfo('code-1');
154
+
155
+ expect(result.originVerified).toBe(false);
156
+ });
132
157
  });
133
158
 
134
159
  describe('approveCommonsSignIn (approver)', () => {
@@ -7,10 +7,9 @@
7
7
  * - PUBLIC (no access token planted, no `expiresIn`) → the clean CDN form
8
8
  * `${cloudURL}/<id>[?variant=...]` (default `https://cloud.oxy.so/<id>`),
9
9
  * which CloudFront resolves against the public media origin.
10
- * - SIGNED / PRIVATE (an access token is present OR `expiresIn` is passed) →
11
- * the authenticated API origin form
12
- * `${baseURL}/assets/<id>/stream?...&token=...` private assets are not on
13
- * the public CDN.
10
+ * - EXPIRING ORIGIN FALLBACK (`expiresIn` is passed) → the API origin
11
+ * stream form without a bearer token in the query string. Callers that need
12
+ * private access should use `getFileDownloadUrlAsync()` for a scoped URL.
14
13
  */
15
14
 
16
15
  import { OxyServices } from '../../OxyServices';
@@ -50,38 +49,24 @@ describe('OxyServices.getFileDownloadUrl', () => {
50
49
  );
51
50
  });
52
51
 
53
- it('can omit the token for persisted public image URLs while authenticated', () => {
52
+ });
53
+
54
+ describe('token-safe URL generation', () => {
55
+ it('does not include the in-memory access token in synchronous image URLs', () => {
54
56
  const oxy = new OxyServices({ baseURL: 'https://api.oxy.so' });
55
57
  oxy.setTokens('access-token-abc');
56
58
 
57
- const url = oxy.getFileDownloadUrl('file123', 'thumb', undefined, {
58
- omitToken: true,
59
- });
59
+ const url = oxy.getFileDownloadUrl('file123', 'thumb');
60
60
 
61
61
  expect(url).toBe('https://cloud.oxy.so/file123?variant=thumb');
62
62
  expect(url).not.toContain('access-token-abc');
63
63
  expect(url).not.toContain('token=');
64
64
  });
65
- });
66
65
 
67
- describe('signed / private assets authenticated API origin', () => {
68
- it('returns the stream endpoint with the token when an access token is present', () => {
66
+ it('routes through the stream endpoint when expiresIn is requested without embedding a token', () => {
69
67
  const oxy = new OxyServices({ baseURL: 'https://api.oxy.so' });
70
68
  oxy.setTokens('access-token-abc');
71
69
 
72
- const url = oxy.getFileDownloadUrl('file123', 'thumb');
73
-
74
- expect(url.startsWith('https://api.oxy.so/assets/file123/stream?')).toBe(true);
75
- const params = new URLSearchParams(url.split('?')[1]);
76
- expect(params.get('variant')).toBe('thumb');
77
- expect(params.get('token')).toBe('access-token-abc');
78
- expect(params.get('fallback')).toBe('placeholderVisible');
79
- expect(url).not.toContain('cloud.oxy.so');
80
- });
81
-
82
- it('routes through the stream endpoint when expiresIn is requested even without a token', () => {
83
- const oxy = new OxyServices({ baseURL: 'https://api.oxy.so' });
84
-
85
70
  const url = oxy.getFileDownloadUrl('file123', 'thumb', 3600);
86
71
 
87
72
  expect(url.startsWith('https://api.oxy.so/assets/file123/stream?')).toBe(true);
@@ -90,6 +75,7 @@ describe('OxyServices.getFileDownloadUrl', () => {
90
75
  expect(params.get('variant')).toBe('thumb');
91
76
  expect(params.get('fallback')).toBe('placeholderVisible');
92
77
  expect(params.get('token')).toBeNull();
78
+ expect(url).not.toContain('access-token-abc');
93
79
  expect(url).not.toContain('cloud.oxy.so');
94
80
  });
95
81
  });
@@ -32,6 +32,7 @@ import { OxyServicesContactsMixin } from './OxyServices.contacts';
32
32
  import { OxyServicesAppDataMixin } from './OxyServices.appData';
33
33
  import { OxyServicesCivicMixin } from './OxyServices.civic';
34
34
  import { OxyServicesNodesMixin } from './OxyServices.nodes';
35
+ import { OxyServicesLinksMixin } from './OxyServices.links';
35
36
 
36
37
  /**
37
38
  * Instance shape of every mixin in the pipeline, intersected. The runtime
@@ -68,6 +69,7 @@ type AllMixinInstances =
68
69
  & InstanceType<ReturnType<typeof OxyServicesAppDataMixin<typeof OxyServicesBase>>>
69
70
  & InstanceType<ReturnType<typeof OxyServicesCivicMixin<typeof OxyServicesBase>>>
70
71
  & InstanceType<ReturnType<typeof OxyServicesNodesMixin<typeof OxyServicesBase>>>
72
+ & InstanceType<ReturnType<typeof OxyServicesLinksMixin<typeof OxyServicesBase>>>
71
73
  & InstanceType<ReturnType<typeof OxyServicesUtilityMixin<typeof OxyServicesBase>>>;
72
74
 
73
75
  /**
@@ -139,6 +141,9 @@ const MIXIN_PIPELINE: MixinFunction[] = [
139
141
  // User nodes / decentralization (Fase 5): register/read/revoke/manage the
140
142
  // caller's personal data node + ingest hint.
141
143
  OxyServicesNodesMixin,
144
+ // Link previews / unfurls: SDK-owned link-metadata resolution via oxy-api,
145
+ // so apps stop scraping link metadata locally.
146
+ OxyServicesLinksMixin,
142
147
 
143
148
  // Utility (last, can use all above)
144
149
  OxyServicesUtilityMixin,