@oxyhq/core 17.0.3 → 18.0.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.
@@ -134,10 +134,9 @@ async function runSessionColdBoot(opts) {
134
134
  // origin persisted a deviceId + deviceSecret, mint a short access token with
135
135
  // a single bearer-less POST (no cookie, no navigation). The mint itself runs
136
136
  // through `refreshDeviceSecretArm`, which acquires the client's PROCESS-WIDE
137
- // single-flight, persists the rotated `nextDeviceSecret` BEFORE planting the
138
- // token, and returns a classified outcome — so this step can never
139
- // double-rotate the server against the scheduler/transport/401 lanes, and
140
- // the durable store always converges on the true `current` secret.
137
+ // single-flight, persists `nextDeviceSecret` BEFORE planting the token, and
138
+ // returns a classified outcome — so concurrent mint lanes share one in-flight
139
+ // request and the durable store always converges on the server's credential.
141
140
  steps.push({
142
141
  id: 'device-secret-mint',
143
142
  // Network step — skip entirely when the caller reports the device offline so
@@ -156,7 +155,7 @@ async function runSessionColdBoot(opts) {
156
155
  const result = await (0, refresh_1.refreshDeviceSecretArm)({ oxy, store, pin });
157
156
  switch (result.status) {
158
157
  case 'ok':
159
- // The arm persisted the rotated secret and planted the token.
158
+ // The arm persisted nextDeviceSecret and planted the token.
160
159
  return {
161
160
  kind: 'session',
162
161
  session: {
@@ -126,6 +126,42 @@ function OxyServicesAccountsMixin(Base) {
126
126
  throw this.handleError(error);
127
127
  }
128
128
  }
129
+ /**
130
+ * Mint a `channel` account under `ownerUserId` via service auth
131
+ * (`accounts:provision` scope). Requires `configureServiceAuth` first.
132
+ */
133
+ async provisionChannelAccount(data) {
134
+ try {
135
+ return await this.makeServiceRequest('POST', '/accounts/service/channels', data);
136
+ }
137
+ catch (error) {
138
+ throw this.handleError(error);
139
+ }
140
+ }
141
+ /**
142
+ * Grant membership on a channel account via service auth
143
+ * (`accounts:provision` scope).
144
+ */
145
+ async provisionChannelMember(channelAccountId, data) {
146
+ try {
147
+ return await this.makeServiceRequest('POST', `/accounts/service/channels/${encodeURIComponent(channelAccountId)}/members`, data);
148
+ }
149
+ catch (error) {
150
+ throw this.handleError(error);
151
+ }
152
+ }
153
+ /**
154
+ * Revoke membership on a channel account via service auth
155
+ * (`accounts:provision` scope).
156
+ */
157
+ async revokeChannelMember(channelAccountId, memberUserId) {
158
+ try {
159
+ return await this.makeServiceRequest('DELETE', `/accounts/service/channels/${encodeURIComponent(channelAccountId)}/members/${encodeURIComponent(memberUserId)}`);
160
+ }
161
+ catch (error) {
162
+ throw this.handleError(error);
163
+ }
164
+ }
129
165
  /**
130
166
  * Update an account's mutable profile fields. Tree placement changes
131
167
  * (reparenting) go through the dedicated move endpoint, not here.
@@ -65,8 +65,9 @@ function OxyServicesDeviceBootMixin(Base) {
65
65
  * Zero-cookie mint. Present the first-party `deviceId` + `deviceSecret` to
66
66
  * `POST /session/device/token` — NO bearer, NO cookies: possession of the
67
67
  * secret IS the device-ownership proof. Returns a fresh short access token
68
- * for the device's active account plus `nextDeviceSecret` (rotation-in-use)
69
- * and the projected device-session `state`.
68
+ * for the device's active account plus `nextDeviceSecret` (on mint, the same
69
+ * proven secret echoed back — rotation happens on sign-in, not mint) and
70
+ * the projected device-session `state`.
70
71
  *
71
72
  * `skipAuth`: this call carries no bearer, so a 401 must surface DIRECTLY —
72
73
  * never trigger `HttpService`'s 401→refresh→retry dance. The cold boot / re-
@@ -40,19 +40,14 @@ const MIN_SCHEDULE_DELAY_MS = 1000;
40
40
  const MIN_FAILURE_BACKOFF_MS = 5000;
41
41
  const MAX_FAILURE_BACKOFF_MS = 5 * 60000;
42
42
  /**
43
- * Arm 1 — the rotating device-secret mint, run under the owning client's
44
- * PROCESS-WIDE single-flight (`httpService.runSingleFlightDeviceSecretMint`).
43
+ * Arm 1 — the device-secret mint, run under the owning client's PROCESS-WIDE
44
+ * single-flight (`httpService.runSingleFlightDeviceSecretMint`).
45
45
  *
46
- * The server ROTATES the presented `deviceSecret` on every successful mint and
47
- * the just-presented secret is valid only for a short grace window. If two lanes
48
- * (cold boot, the proactive scheduler, a request-time preflight, a 401 retry,
49
- * the socket token transport, or a tab-focus reconcile) minted concurrently they
50
- * would double-rotate the server and the durable store could converge on the
51
- * SUPERSEDED secret — after the grace window the next cold boot mint 401s and the
52
- * user is signed out. Routing EVERY lane through this one single-flight makes
53
- * concurrent callers await the SAME in-flight mint and all receive its result, so
54
- * there is exactly one rotation and the store always converges on the true
55
- * `current` secret.
46
+ * Concurrent lanes (cold boot, the proactive scheduler, a request-time preflight,
47
+ * a 401 retry, the socket token transport, or a tab-focus reconcile) must not
48
+ * each persist a different view of the mint response. Routing EVERY lane through
49
+ * this one single-flight makes concurrent callers await the SAME in-flight mint
50
+ * and all receive its result, so the durable store converges on one credential.
56
51
  *
57
52
  * On success it persists `nextDeviceSecret` (read-back-verified) BEFORE planting
58
53
  * the access token; a failed durable persist yields `persist-failed` WITHOUT
@@ -123,8 +118,8 @@ async function refreshDeviceSecretArm(deps) {
123
118
  expiresAt: mint.expiresAt,
124
119
  ...(bound ? { sessionId: bound.sessionId, userId: bound.accountId } : {}),
125
120
  };
126
- // Rotation-in-use anti-loss: persist the NEXT secret and read-back-VERIFY it
127
- // landed BEFORE planting the token. A failed durable persist must NOT plant.
121
+ // Persist nextDeviceSecret (read-back-verified) BEFORE planting the token.
122
+ // A failed durable persist must NOT plant.
128
123
  const persistedOk = await store.save(next);
129
124
  if (!persistedOk) {
130
125
  return { status: 'persist-failed' };