@oxyhq/core 3.1.0 → 3.2.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.
@@ -682,7 +682,14 @@ export class AuthManager {
682
682
  * Get a valid access token, refreshing automatically if expired or expiring soon.
683
683
  */
684
684
  async getAccessToken() {
685
- const token = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
685
+ // In cookieOnly / cookie-restore flows the active access token lives only in
686
+ // memory (`_lastKnownAccessToken` + httpService) and is intentionally never
687
+ // written to JS storage — the cookieOnly contract forbids persisting tokens
688
+ // in JS-accessible storage. Fall back to the in-memory token when storage has
689
+ // none, otherwise getAccessToken returns null after every cold-boot/reload and
690
+ // standalone API clients (e.g. the Console axios client) send no Authorization
691
+ // header → 401 on every authed endpoint while `isAuthenticated` is still true.
692
+ const token = (await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN)) ?? this._lastKnownAccessToken;
686
693
  if (!token)
687
694
  return null;
688
695
  try {
@@ -693,7 +700,9 @@ export class AuthManager {
693
700
  if (decoded.exp - now < buffer) {
694
701
  const refreshed = await this.refreshToken();
695
702
  if (refreshed) {
696
- return this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
703
+ // refreshToken() updates both storage and `_lastKnownAccessToken`;
704
+ // prefer storage but fall back to memory for the cookieOnly path.
705
+ return (await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN)) ?? this._lastKnownAccessToken;
697
706
  }
698
707
  }
699
708
  }
@@ -21,6 +21,7 @@ import { composeOxyServices } from './mixins/index.js';
21
21
  * - **Karma**: Karma system
22
22
  * - **Assets**: File upload and asset management
23
23
  * - **Applications**: Application, membership, and credential management
24
+ * - **Workspaces**: Workspace and membership management
24
25
  * - **Location**: Location-based features
25
26
  * - **Analytics**: Analytics tracking
26
27
  * - **Devices**: Device management
@@ -4,12 +4,40 @@ export function OxyServicesApplicationsMixin(Base) {
4
4
  constructor(...args) {
5
5
  super(...args);
6
6
  }
7
+ /**
8
+ * Resolve an OAuth client identifier to the owning application's PUBLIC
9
+ * identity. No authentication required — the API returns only sanitized,
10
+ * display-safe metadata ({@link PublicApplication}). Use this to render the
11
+ * requesting application's name/icon in consent, authorize, and device-flow
12
+ * approval UIs before any session exists.
13
+ *
14
+ * @param clientId - The OAuth `client_id` (an active credential's public
15
+ * key). URL-encoded before being placed in the path.
16
+ */
17
+ async getPublicApplication(clientId) {
18
+ try {
19
+ const res = await this.makeRequest('GET', `/auth/oauth/client/${encodeURIComponent(clientId)}`, undefined, { cache: true, cacheTTL: CACHE_TIMES.MEDIUM });
20
+ return res.application;
21
+ }
22
+ catch (error) {
23
+ throw this.handleError(error);
24
+ }
25
+ }
7
26
  /**
8
27
  * List applications the current user is an active member of.
28
+ *
29
+ * @param workspaceId - Optional workspace `_id` to scope the listing to
30
+ * applications belonging to that workspace. When provided it is appended
31
+ * as a `workspaceId` query parameter (URL-encoded). The query string is
32
+ * part of the request path, so the response cache keys on it
33
+ * automatically — scoped and unscoped lists never collide.
9
34
  */
10
- async getApplications() {
35
+ async getApplications(workspaceId) {
11
36
  try {
12
- const res = await this.makeRequest('GET', '/applications', undefined, { cache: true, cacheTTL: CACHE_TIMES.MEDIUM });
37
+ const path = workspaceId
38
+ ? `/applications?workspaceId=${encodeURIComponent(workspaceId)}`
39
+ : '/applications';
40
+ const res = await this.makeRequest('GET', path, undefined, { cache: true, cacheTTL: CACHE_TIMES.MEDIUM });
13
41
  return res.applications ?? [];
14
42
  }
15
43
  catch (error) {
@@ -0,0 +1,141 @@
1
+ import { CACHE_TIMES } from './mixinHelpers.js';
2
+ export function OxyServicesWorkspacesMixin(Base) {
3
+ return class extends Base {
4
+ constructor(...args) {
5
+ super(...args);
6
+ }
7
+ /**
8
+ * List workspaces the current user is an active member of.
9
+ */
10
+ async getWorkspaces() {
11
+ try {
12
+ const res = await this.makeRequest('GET', '/workspaces', undefined, { cache: true, cacheTTL: CACHE_TIMES.MEDIUM });
13
+ return res.workspaces ?? [];
14
+ }
15
+ catch (error) {
16
+ throw this.handleError(error);
17
+ }
18
+ }
19
+ /**
20
+ * Create a new team workspace. The caller becomes its `owner`.
21
+ * @param data - Workspace configuration.
22
+ */
23
+ async createWorkspace(data) {
24
+ try {
25
+ const res = await this.makeRequest('POST', '/workspaces', data, { cache: false });
26
+ return res.workspace;
27
+ }
28
+ catch (error) {
29
+ throw this.handleError(error);
30
+ }
31
+ }
32
+ /**
33
+ * Fetch a single workspace by id.
34
+ * @param workspaceId - The workspace's Mongo `_id`.
35
+ */
36
+ async getWorkspace(workspaceId) {
37
+ try {
38
+ const res = await this.makeRequest('GET', `/workspaces/${encodeURIComponent(workspaceId)}`, undefined, { cache: true, cacheTTL: CACHE_TIMES.LONG });
39
+ return res.workspace;
40
+ }
41
+ catch (error) {
42
+ throw this.handleError(error);
43
+ }
44
+ }
45
+ /**
46
+ * Update a workspace's mutable fields.
47
+ * @param workspaceId - The workspace's Mongo `_id`.
48
+ * @param data - Subset of updatable fields.
49
+ */
50
+ async updateWorkspace(workspaceId, data) {
51
+ try {
52
+ const res = await this.makeRequest('PATCH', `/workspaces/${encodeURIComponent(workspaceId)}`, data, { cache: false });
53
+ return res.workspace;
54
+ }
55
+ catch (error) {
56
+ throw this.handleError(error);
57
+ }
58
+ }
59
+ /**
60
+ * Soft-delete a workspace (owner only).
61
+ * @param workspaceId - The workspace's Mongo `_id`.
62
+ */
63
+ async deleteWorkspace(workspaceId) {
64
+ try {
65
+ return await this.makeRequest('DELETE', `/workspaces/${encodeURIComponent(workspaceId)}`, undefined, { cache: false });
66
+ }
67
+ catch (error) {
68
+ throw this.handleError(error);
69
+ }
70
+ }
71
+ /**
72
+ * List members of a workspace.
73
+ * @param workspaceId - The workspace's Mongo `_id`.
74
+ */
75
+ async getWorkspaceMembers(workspaceId) {
76
+ try {
77
+ const res = await this.makeRequest('GET', `/workspaces/${encodeURIComponent(workspaceId)}/members`, undefined, { cache: true, cacheTTL: CACHE_TIMES.MEDIUM });
78
+ return res.members ?? [];
79
+ }
80
+ catch (error) {
81
+ throw this.handleError(error);
82
+ }
83
+ }
84
+ /**
85
+ * Add a member to a workspace.
86
+ * @param workspaceId - The workspace's Mongo `_id`.
87
+ * @param data - Target user id and role (never `owner`).
88
+ */
89
+ async inviteWorkspaceMember(workspaceId, data) {
90
+ try {
91
+ const res = await this.makeRequest('POST', `/workspaces/${encodeURIComponent(workspaceId)}/members`, data, { cache: false });
92
+ return res.member;
93
+ }
94
+ catch (error) {
95
+ throw this.handleError(error);
96
+ }
97
+ }
98
+ /**
99
+ * Change a member's role.
100
+ * @param workspaceId - The workspace's Mongo `_id`.
101
+ * @param memberId - The member's Mongo `_id`.
102
+ * @param data - New role (never `owner`).
103
+ */
104
+ async updateWorkspaceMember(workspaceId, memberId, data) {
105
+ try {
106
+ const res = await this.makeRequest('PATCH', `/workspaces/${encodeURIComponent(workspaceId)}/members/${encodeURIComponent(memberId)}`, data, { cache: false });
107
+ return res.member;
108
+ }
109
+ catch (error) {
110
+ throw this.handleError(error);
111
+ }
112
+ }
113
+ /**
114
+ * Remove a member from a workspace.
115
+ * @param workspaceId - The workspace's Mongo `_id`.
116
+ * @param memberId - The member's Mongo `_id`.
117
+ */
118
+ async removeWorkspaceMember(workspaceId, memberId) {
119
+ try {
120
+ return await this.makeRequest('DELETE', `/workspaces/${encodeURIComponent(workspaceId)}/members/${encodeURIComponent(memberId)}`, undefined, { cache: false });
121
+ }
122
+ catch (error) {
123
+ throw this.handleError(error);
124
+ }
125
+ }
126
+ /**
127
+ * Transfer ownership of a workspace to another member (owner only).
128
+ * Demotes the current owner and promotes the target to `owner`.
129
+ * @param workspaceId - The workspace's Mongo `_id`.
130
+ * @param data - Target user id.
131
+ */
132
+ async transferWorkspaceOwnership(workspaceId, data) {
133
+ try {
134
+ return await this.makeRequest('POST', `/workspaces/${encodeURIComponent(workspaceId)}/transfer-ownership`, data, { cache: false });
135
+ }
136
+ catch (error) {
137
+ throw this.handleError(error);
138
+ }
139
+ }
140
+ };
141
+ }
@@ -17,6 +17,7 @@ import { OxyServicesPaymentMixin } from './OxyServices.payment.js';
17
17
  import { OxyServicesKarmaMixin } from './OxyServices.karma.js';
18
18
  import { OxyServicesAssetsMixin } from './OxyServices.assets.js';
19
19
  import { OxyServicesApplicationsMixin } from './OxyServices.applications.js';
20
+ import { OxyServicesWorkspacesMixin } from './OxyServices.workspaces.js';
20
21
  import { OxyServicesLocationMixin } from './OxyServices.location.js';
21
22
  import { OxyServicesAnalyticsMixin } from './OxyServices.analytics.js';
22
23
  import { OxyServicesDevicesMixin } from './OxyServices.devices.js';
@@ -61,6 +62,7 @@ const MIXIN_PIPELINE = [
61
62
  OxyServicesKarmaMixin,
62
63
  OxyServicesAssetsMixin,
63
64
  OxyServicesApplicationsMixin,
65
+ OxyServicesWorkspacesMixin,
64
66
  OxyServicesLocationMixin,
65
67
  OxyServicesAnalyticsMixin,
66
68
  OxyServicesDevicesMixin,