@sanity/sdk 3.0.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.
- package/dist/_exports/_internal.d.ts +72 -19
- package/dist/_exports/_internal.d.ts.map +1 -1
- package/dist/_exports/_internal.js +130 -11
- package/dist/_exports/_internal.js.map +1 -1
- package/dist/_exports/agent.d.ts +7 -7
- package/dist/_exports/agent.d.ts.map +1 -1
- package/dist/_exports/agent.js +1 -1
- package/dist/_exports/comlink.d.ts +9 -9
- package/dist/_exports/comlink.d.ts.map +1 -1
- package/dist/_exports/comlink.js +2 -2
- package/dist/_exports/dashboard.d.ts +2 -0
- package/dist/_exports/dashboard.js +2 -0
- package/dist/{authStore-C0o1NOxW.d.ts → authStore-BO2vch3I.d.ts} +516 -63
- package/dist/authStore-BO2vch3I.d.ts.map +1 -0
- package/dist/{authStore-CtE9bLHW.js → clientStore-BI4D8HhJ.js} +390 -15
- package/dist/clientStore-BI4D8HhJ.js.map +1 -0
- package/dist/{createSanityInstance-DUI1bGIg.d.ts → createSanityInstance-BQIqyGLX.d.ts} +17 -2
- package/dist/createSanityInstance-BQIqyGLX.d.ts.map +1 -0
- package/dist/{createStateSourceAction-BOKtqFNm.js → createStateSourceAction-DdAhEfnZ.js} +7 -3
- package/dist/createStateSourceAction-DdAhEfnZ.js.map +1 -0
- package/dist/defineProperty-Bho9Ngsi.js +31 -0
- package/dist/{getNodeState-CR3__B2D.js → getNodeState-B4nG0sjm.js} +2 -2
- package/dist/{getNodeState-CR3__B2D.js.map → getNodeState-B4nG0sjm.js.map} +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +370 -90
- package/dist/index.js.map +1 -1
- package/dist/topicStore-Ct7IFNtY.js +565 -0
- package/dist/topicStore-Ct7IFNtY.js.map +1 -0
- package/dist/topicStore-D0WqhQGF.d.ts +435 -0
- package/dist/topicStore-D0WqhQGF.d.ts.map +1 -0
- package/dist/{queryStore-C7yMHPUI.js → version-BcRmCiYi.js} +48 -16
- package/dist/version-BcRmCiYi.js.map +1 -0
- package/package.json +19 -18
- package/src/_exports/_internal.ts +13 -0
- package/src/_exports/dashboard.ts +36 -0
- package/src/_exports/index.ts +17 -1
- package/src/access/checkPermissions.test.ts +1 -1
- package/src/access/checkPermissions.ts +1 -1
- package/src/applications/applications.test-d.ts +28 -4
- package/src/applications/applications.test.ts +4 -4
- package/src/applications/applications.ts +43 -18
- package/src/auth/authMode.test.ts +24 -0
- package/src/auth/authMode.ts +8 -3
- package/src/auth/authStore.ts +12 -0
- package/src/auth/authStrategy.ts +2 -0
- package/src/auth/handleAuthCallback.test.ts +2 -2
- package/src/auth/handleAuthCallback.ts +1 -1
- package/src/auth/logout.test.ts +46 -1
- package/src/auth/logout.ts +2 -1
- package/src/auth/oauth/oauthActions.test.ts +457 -0
- package/src/auth/oauth/oauthActions.ts +389 -0
- package/src/auth/oauth/oauthAuth.test.ts +206 -0
- package/src/auth/oauth/oauthAuth.ts +168 -0
- package/src/auth/oauth/pkce.test.ts +44 -0
- package/src/auth/oauth/pkce.ts +60 -0
- package/src/auth/oauth/types.ts +12 -0
- package/src/auth/refreshStampedToken.ts +1 -1
- package/src/auth/studioModeAuth.test.ts +1 -1
- package/src/auth/studioModeAuth.ts +1 -1
- package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +2 -2
- package/src/auth/subscribeToStateAndFetchCurrentUser.ts +1 -1
- package/src/comments/addonDatasetStore.test.ts +2 -2
- package/src/comments/addonDatasetStore.ts +2 -2
- package/src/comments/commentActions.ts +6 -5
- package/src/comments/commentsStore.ts +4 -4
- package/src/config/authConfig.ts +18 -0
- package/src/dashboard/messageBus/__fixtures__/test-topics.ts +18 -0
- package/src/dashboard/messageBus/bus.test.ts +1033 -0
- package/src/dashboard/messageBus/bus.ts +1246 -0
- package/src/dashboard/messageBus/store.test.ts +142 -0
- package/src/dashboard/messageBus/store.ts +74 -0
- package/src/dashboard/messageBus/topicStore.test.ts +172 -0
- package/src/dashboard/messageBus/topicStore.ts +184 -0
- package/src/dashboard/messageBus/topics.test.ts +71 -0
- package/src/dashboard/messageBus/topics.ts +260 -0
- package/src/document/actions.ts +3 -3
- package/src/document/applyDocumentActions.ts +5 -9
- package/src/document/documentStore.concurrency.test.ts +2 -1
- package/src/document/documentStore.test.ts +19 -18
- package/src/document/documentStore.ts +17 -17
- package/src/document/permissions.ts +2 -1
- package/src/document/processActions.test.ts +2 -1
- package/src/document/processMutations.ts +61 -24
- package/src/document/reducers.ts +2 -2
- package/src/installations/installations.test-d.ts +3 -3
- package/src/installations/installations.test.ts +2 -2
- package/src/installations/installations.ts +4 -21
- package/src/organization/organization.test.ts +2 -2
- package/src/organization/organization.ts +1 -1
- package/src/organizations/organizations.test.ts +2 -2
- package/src/organizations/organizations.ts +1 -1
- package/src/presence/presenceStore.test.ts +15 -22
- package/src/presence/presenceStore.ts +3 -2
- package/src/project/project.test.ts +3 -3
- package/src/project/project.ts +1 -1
- package/src/projection/getProjectionState.test.ts +6 -4
- package/src/projection/getProjectionState.ts +4 -4
- package/src/projection/resolveProjection.ts +2 -2
- package/src/projects/projects.test.ts +2 -2
- package/src/projects/projects.ts +1 -1
- package/src/query/queryStore.ts +6 -6
- package/src/store/createSanityInstance.test.ts +1 -1
- package/src/store/createSanityInstance.ts +4 -4
- package/src/store/createStoreInstance.test.ts +0 -5
- package/src/telemetry/telemetryManager.test.ts +1 -1
- package/src/telemetry/telemetryManager.ts +2 -2
- package/src/typegen/defineProjection.test.ts +17 -0
- package/src/typegen/defineProjection.ts +19 -0
- package/src/typegen/groqCompat.ts +122 -0
- package/src/typegen/resolve.ts +37 -0
- package/src/users/usersStore.test.ts +1 -1
- package/src/users/usersStore.ts +5 -5
- package/src/utils/createGroqSearchFilter.test.ts +8 -0
- package/src/utils/createGroqSearchFilter.ts +6 -3
- package/src/utils/ids.test.ts +40 -15
- package/src/utils/ids.ts +47 -2
- package/dist/authStore-C0o1NOxW.d.ts.map +0 -1
- package/dist/authStore-CtE9bLHW.js.map +0 -1
- package/dist/clientStore-BtE2-q2i.js +0 -90
- package/dist/clientStore-BtE2-q2i.js.map +0 -1
- package/dist/createSanityInstance-DUI1bGIg.d.ts.map +0 -1
- package/dist/createStateSourceAction-BOKtqFNm.js.map +0 -1
- package/dist/getEnv-D5RGkQLu.js +0 -8
- package/dist/getEnv-D5RGkQLu.js.map +0 -1
- package/dist/logger-DAtJb5RQ.js +0 -173
- package/dist/logger-DAtJb5RQ.js.map +0 -1
- package/dist/queryStore-C7yMHPUI.js.map +0 -1
- package/dist/rolldown-runtime-vyAXikos.js +0 -9
- package/dist/telemetryManager-UIEiMtC0.js +0 -127
- package/dist/telemetryManager-UIEiMtC0.js.map +0 -1
- package/dist/version-D8RP_Q59.js +0 -9
- package/dist/version-D8RP_Q59.js.map +0 -1
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import {defer, distinctUntilChanged, filter, map, type Subscription} from 'rxjs'
|
|
2
|
+
|
|
3
|
+
import {type StoreContext} from '../../store/defineStore'
|
|
4
|
+
import {DEFAULT_BASE} from '../authConstants'
|
|
5
|
+
import {AuthStateType} from '../authStateType'
|
|
6
|
+
import {type AuthStoreState} from '../authStore'
|
|
7
|
+
import {type AuthStrategyOptions, type AuthStrategyResult} from '../authStrategy'
|
|
8
|
+
import {subscribeToStateAndFetchCurrentUser} from '../subscribeToStateAndFetchCurrentUser'
|
|
9
|
+
import {createLoggedInAuthState, getDefaultStorage, getStorageEvents} from '../utils'
|
|
10
|
+
import {type OAuthTokens} from './types'
|
|
11
|
+
|
|
12
|
+
/** localStorage (or configured `storageArea`) key for persisted OAuth tokens. */
|
|
13
|
+
export const OAUTH_TOKENS_KEY = '__sanity_oauth_tokens'
|
|
14
|
+
|
|
15
|
+
/** The persisted JSON shape of {@link OAuthTokens}, `expiresAt` as an ISO string. */
|
|
16
|
+
interface SerializedOAuthTokens extends Omit<OAuthTokens, 'expiresAt'> {
|
|
17
|
+
expiresAt: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Parses persisted token JSON back into {@link OAuthTokens}. Returns `null`
|
|
22
|
+
* when the value is missing or malformed.
|
|
23
|
+
*
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export function deserializeTokens(raw: string | null): OAuthTokens | null {
|
|
27
|
+
if (!raw) return null
|
|
28
|
+
try {
|
|
29
|
+
const parsed: unknown = JSON.parse(raw)
|
|
30
|
+
if (
|
|
31
|
+
typeof parsed !== 'object' ||
|
|
32
|
+
parsed === null ||
|
|
33
|
+
!('accessToken' in parsed) ||
|
|
34
|
+
typeof (parsed as SerializedOAuthTokens).accessToken !== 'string' ||
|
|
35
|
+
!('expiresAt' in parsed) ||
|
|
36
|
+
typeof (parsed as SerializedOAuthTokens).expiresAt !== 'string'
|
|
37
|
+
) {
|
|
38
|
+
return null
|
|
39
|
+
}
|
|
40
|
+
const value = parsed as SerializedOAuthTokens
|
|
41
|
+
return {
|
|
42
|
+
accessToken: value.accessToken,
|
|
43
|
+
tokenType: 'bearer',
|
|
44
|
+
expiresIn: value.expiresIn,
|
|
45
|
+
expiresAt: new Date(value.expiresAt),
|
|
46
|
+
...(value.refreshToken !== undefined && {refreshToken: value.refreshToken}),
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Resolves the initial auth state for OAuth mode.
|
|
55
|
+
*
|
|
56
|
+
* State discovery order:
|
|
57
|
+
* 1. Persisted tokens in `__sanity_oauth_tokens` → `LOGGED_IN`
|
|
58
|
+
* 2. Callback URL (contains `code`/`state`/`error` and matches `redirectUri`)
|
|
59
|
+
* → `LOGGING_IN`
|
|
60
|
+
* 3. Otherwise → `LOGGED_OUT`
|
|
61
|
+
*
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
export function getOauthInitialState(options: AuthStrategyOptions): AuthStrategyResult {
|
|
65
|
+
const {authConfig, initialLocationHref} = options
|
|
66
|
+
const storageKey = OAUTH_TOKENS_KEY
|
|
67
|
+
const storageArea = authConfig.storageArea ?? getDefaultStorage()
|
|
68
|
+
const redirectUri = authConfig.oauth?.redirectUri
|
|
69
|
+
|
|
70
|
+
// Persisted tokens win
|
|
71
|
+
const tokens = deserializeTokens(storageArea?.getItem(OAUTH_TOKENS_KEY) ?? null)
|
|
72
|
+
if (tokens) {
|
|
73
|
+
return {
|
|
74
|
+
authState: createLoggedInAuthState(tokens.accessToken, null),
|
|
75
|
+
storageKey,
|
|
76
|
+
storageArea,
|
|
77
|
+
authMethod: 'localstorage',
|
|
78
|
+
dashboardContext: {},
|
|
79
|
+
oauthTokens: tokens,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Callback URL with code/state/error whose origin + pathname match our
|
|
84
|
+
// redirect URI (query and hash are ignored).
|
|
85
|
+
const {searchParams} = new URL(initialLocationHref, DEFAULT_BASE)
|
|
86
|
+
const isCallback =
|
|
87
|
+
searchParams.has('code') || searchParams.has('state') || searchParams.has('error')
|
|
88
|
+
if (redirectUri && isCallback) {
|
|
89
|
+
const loc = new URL(initialLocationHref, DEFAULT_BASE)
|
|
90
|
+
const redirect = new URL(redirectUri, DEFAULT_BASE)
|
|
91
|
+
if (loc.origin === redirect.origin && loc.pathname === redirect.pathname) {
|
|
92
|
+
return {
|
|
93
|
+
authState: {type: AuthStateType.LOGGING_IN, isExchangingToken: false},
|
|
94
|
+
storageKey,
|
|
95
|
+
storageArea,
|
|
96
|
+
authMethod: undefined,
|
|
97
|
+
dashboardContext: {},
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// No tokens, not a callback
|
|
103
|
+
return {
|
|
104
|
+
authState: {type: AuthStateType.LOGGED_OUT, isDestroyingSession: false},
|
|
105
|
+
storageKey,
|
|
106
|
+
storageArea,
|
|
107
|
+
authMethod: undefined,
|
|
108
|
+
dashboardContext: {},
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Subscribes to cross-tab `storage` events for the OAuth tokens key, syncing
|
|
114
|
+
* this tab's auth state when tokens change in another tab.
|
|
115
|
+
*
|
|
116
|
+
* @internal
|
|
117
|
+
*/
|
|
118
|
+
export function subscribeToOAuthStorageEvents({state}: StoreContext<AuthStoreState>): Subscription {
|
|
119
|
+
const {storageArea} = state.get().options
|
|
120
|
+
|
|
121
|
+
const tokens$ = defer(getStorageEvents).pipe(
|
|
122
|
+
filter((e) => e.storageArea === storageArea && e.key === OAUTH_TOKENS_KEY),
|
|
123
|
+
map(() => deserializeTokens(storageArea?.getItem(OAUTH_TOKENS_KEY) ?? null)),
|
|
124
|
+
distinctUntilChanged((a, b) => a?.accessToken === b?.accessToken),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return tokens$.subscribe((tokens) => {
|
|
128
|
+
state.set('updateOAuthTokensFromStorageEvent', {
|
|
129
|
+
authState: tokens
|
|
130
|
+
? createLoggedInAuthState(tokens.accessToken, null)
|
|
131
|
+
: {type: AuthStateType.LOGGED_OUT, isDestroyingSession: false},
|
|
132
|
+
oauthTokens: tokens ?? undefined,
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Initialize OAuth auth subscriptions:
|
|
139
|
+
* - Subscribe to state changes and fetch current user
|
|
140
|
+
* - Subscribe to cross-tab storage events for the OAuth tokens key
|
|
141
|
+
*
|
|
142
|
+
* OAuth tokens are refreshed via the `refresh_token` grant (see
|
|
143
|
+
* `refreshOAuthTokens`), so the stamped-token refresher is not started here.
|
|
144
|
+
*
|
|
145
|
+
* @internal
|
|
146
|
+
*/
|
|
147
|
+
export function initializeOauthAuth(context: StoreContext<AuthStoreState>): {
|
|
148
|
+
dispose: () => void
|
|
149
|
+
tokenRefresherStarted: boolean
|
|
150
|
+
} {
|
|
151
|
+
const subscriptions: Subscription[] = []
|
|
152
|
+
|
|
153
|
+
subscriptions.push(subscribeToStateAndFetchCurrentUser(context, {useProjectHostname: false}))
|
|
154
|
+
|
|
155
|
+
const storageArea = context.state.get().options?.storageArea
|
|
156
|
+
if (storageArea) {
|
|
157
|
+
subscriptions.push(subscribeToOAuthStorageEvents(context))
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
dispose: () => {
|
|
162
|
+
for (const subscription of subscriptions) {
|
|
163
|
+
subscription.unsubscribe()
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
tokenRefresherStarted: false,
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
import {describe, expect, it} from 'vitest'
|
|
4
|
+
|
|
5
|
+
import {base64UrlEncode, generateCodeChallenge, generateCodeVerifier, generateState} from './pkce'
|
|
6
|
+
|
|
7
|
+
describe('base64UrlEncode', () => {
|
|
8
|
+
it('encodes bytes as base64url without padding', () => {
|
|
9
|
+
// 0xff 0xef 0xbf → "/++/" in base64, "_--_" in base64url (minus padding)
|
|
10
|
+
const encoded = base64UrlEncode(new Uint8Array([0xff, 0xef, 0xbf]))
|
|
11
|
+
expect(encoded).not.toContain('+')
|
|
12
|
+
expect(encoded).not.toContain('/')
|
|
13
|
+
expect(encoded).not.toContain('=')
|
|
14
|
+
expect(encoded).toBe('_--_')
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
describe('generateCodeVerifier / generateState', () => {
|
|
19
|
+
it('generates unique, URL-safe strings', () => {
|
|
20
|
+
const a = generateCodeVerifier()
|
|
21
|
+
const b = generateCodeVerifier()
|
|
22
|
+
const s = generateState()
|
|
23
|
+
expect(a).not.toBe(b)
|
|
24
|
+
expect(a).toMatch(/^[A-Za-z0-9_-]+$/)
|
|
25
|
+
expect(s).toMatch(/^[A-Za-z0-9_-]+$/)
|
|
26
|
+
expect(a.length).toBeGreaterThanOrEqual(43)
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe('generateCodeChallenge', () => {
|
|
31
|
+
it('produces base64url(sha256(verifier))', async () => {
|
|
32
|
+
const verifier = 'test-verifier'
|
|
33
|
+
const challenge = await generateCodeChallenge(verifier)
|
|
34
|
+
|
|
35
|
+
const expected = createHash('sha256')
|
|
36
|
+
.update(verifier)
|
|
37
|
+
.digest('base64')
|
|
38
|
+
.replace(/\+/g, '-')
|
|
39
|
+
.replace(/\//g, '_')
|
|
40
|
+
.replace(/=+$/, '')
|
|
41
|
+
|
|
42
|
+
expect(challenge).toBe(expected)
|
|
43
|
+
})
|
|
44
|
+
})
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PKCE (Proof Key for Code Exchange, RFC 7636) helpers for the OAuth
|
|
3
|
+
* authorization-code flow.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Number of random bytes used for the `code_verifier` and `state`. */
|
|
9
|
+
const RANDOM_BYTE_LENGTH = 32
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Encodes a byte array as a base64url string (RFC 4648 §5) with no padding,
|
|
13
|
+
* as required for PKCE `code_verifier`/`code_challenge` values.
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export function base64UrlEncode(bytes: Uint8Array): string {
|
|
18
|
+
let binary = ''
|
|
19
|
+
for (const byte of bytes) {
|
|
20
|
+
binary += String.fromCharCode(byte)
|
|
21
|
+
}
|
|
22
|
+
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function randomString(): string {
|
|
26
|
+
const bytes = new Uint8Array(RANDOM_BYTE_LENGTH)
|
|
27
|
+
crypto.getRandomValues(bytes)
|
|
28
|
+
return base64UrlEncode(bytes)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Generates a PKCE `code_verifier`
|
|
33
|
+
*
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
export function generateCodeVerifier(): string {
|
|
37
|
+
return randomString()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Generates the `state` parameter used to defend against CSRF on the OAuth
|
|
42
|
+
* callback.
|
|
43
|
+
*
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
export function generateState(): string {
|
|
47
|
+
return randomString()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Derives the PKCE `code_challenge` from a `code_verifier` using
|
|
52
|
+
* `base64url(sha256(verifier))` (the `S256` method).
|
|
53
|
+
*
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
export async function generateCodeChallenge(codeVerifier: string): Promise<string> {
|
|
57
|
+
const data = new TextEncoder().encode(codeVerifier)
|
|
58
|
+
const digest = await crypto.subtle.digest('SHA-256', data)
|
|
59
|
+
return base64UrlEncode(new Uint8Array(digest))
|
|
60
|
+
}
|
|
@@ -57,7 +57,7 @@ describe('subscribeToStateAndFetchCurrentUser', () => {
|
|
|
57
57
|
})
|
|
58
58
|
expect(mockRequest).toHaveBeenCalledWith({
|
|
59
59
|
method: 'GET',
|
|
60
|
-
|
|
60
|
+
url: '/users/me',
|
|
61
61
|
tag: 'users.get-current',
|
|
62
62
|
})
|
|
63
63
|
|
|
@@ -97,7 +97,7 @@ describe('subscribeToStateAndFetchCurrentUser', () => {
|
|
|
97
97
|
})
|
|
98
98
|
expect(mockRequest).toHaveBeenCalledWith({
|
|
99
99
|
method: 'GET',
|
|
100
|
-
|
|
100
|
+
url: '/users/me',
|
|
101
101
|
tag: 'users.get-current',
|
|
102
102
|
})
|
|
103
103
|
|
|
@@ -59,7 +59,7 @@ describe('getAddonDatasetState', () => {
|
|
|
59
59
|
)
|
|
60
60
|
|
|
61
61
|
expect(observableRequest).toHaveBeenCalledWith({
|
|
62
|
-
|
|
62
|
+
url: '/projects/p/datasets?datasetProfile=comments&addonFor=d',
|
|
63
63
|
tag: 'comments.addon-dataset.list',
|
|
64
64
|
})
|
|
65
65
|
})
|
|
@@ -139,7 +139,7 @@ describe('provisionAddonDataset', () => {
|
|
|
139
139
|
|
|
140
140
|
await expect(provisionAddonDataset(instance, {})).resolves.toBe('d-comments')
|
|
141
141
|
expect(request).toHaveBeenCalledWith({
|
|
142
|
-
|
|
142
|
+
url: '/comments/d/setup',
|
|
143
143
|
method: 'POST',
|
|
144
144
|
tag: 'comments.addon-dataset.setup',
|
|
145
145
|
})
|
|
@@ -67,7 +67,7 @@ function requestAddonDataset(
|
|
|
67
67
|
): Observable<string | undefined> {
|
|
68
68
|
return client.observable
|
|
69
69
|
.request<{name: string}[] | undefined>({
|
|
70
|
-
|
|
70
|
+
url: `/projects/${projectId}/datasets?datasetProfile=${ADDON_DATASET_PROFILE}&addonFor=${dataset}`,
|
|
71
71
|
tag: 'comments.addon-dataset.list',
|
|
72
72
|
})
|
|
73
73
|
.pipe(map((datasets) => datasets?.[0]?.name))
|
|
@@ -182,7 +182,7 @@ export const provisionAddonDataset: (
|
|
|
182
182
|
})
|
|
183
183
|
|
|
184
184
|
const response = await client.request<{datasetName?: string} | undefined>({
|
|
185
|
-
|
|
185
|
+
url: `/comments/${resource.dataset}/setup`,
|
|
186
186
|
method: 'POST',
|
|
187
187
|
tag: 'comments.addon-dataset.setup',
|
|
188
188
|
})
|
|
@@ -16,6 +16,7 @@ import {bindActionByResource, type BoundResourceKey} from '../store/createAction
|
|
|
16
16
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
17
17
|
import {type StoreState} from '../store/createStoreState'
|
|
18
18
|
import {type StoreContext} from '../store/defineStore'
|
|
19
|
+
import {randomUuid} from '../utils/ids'
|
|
19
20
|
import {
|
|
20
21
|
assertDatasetResource,
|
|
21
22
|
getAddonDatasetState,
|
|
@@ -326,7 +327,7 @@ export const createComment: (
|
|
|
326
327
|
options,
|
|
327
328
|
buildCommentPayload({
|
|
328
329
|
authorId: requireCurrentUserId(instance),
|
|
329
|
-
commentId: options.commentId ??
|
|
330
|
+
commentId: options.commentId ?? randomUuid(),
|
|
330
331
|
contentSnapshot: options.contentSnapshot,
|
|
331
332
|
context: options.context,
|
|
332
333
|
documentRevisionId: options.documentRevisionId,
|
|
@@ -336,7 +337,7 @@ export const createComment: (
|
|
|
336
337
|
fieldPath,
|
|
337
338
|
selection: options.selection,
|
|
338
339
|
status: options.status ?? 'open',
|
|
339
|
-
threadId: options.threadId ??
|
|
340
|
+
threadId: options.threadId ?? randomUuid(),
|
|
340
341
|
...(isReleasePerspective(perspective) ? {documentVersionId: perspective.releaseName} : {}),
|
|
341
342
|
}),
|
|
342
343
|
)
|
|
@@ -386,7 +387,7 @@ export const replyToComment: (
|
|
|
386
387
|
options,
|
|
387
388
|
buildCommentPayload({
|
|
388
389
|
authorId: requireCurrentUserId(instance),
|
|
389
|
-
commentId: options.commentId ??
|
|
390
|
+
commentId: options.commentId ?? randomUuid(),
|
|
390
391
|
handle: options,
|
|
391
392
|
message: options.message,
|
|
392
393
|
// Replies to a reply belong to the thread's first comment, matching how
|
|
@@ -420,7 +421,7 @@ export const updateComment: (
|
|
|
420
421
|
{commentId, message}: UpdateCommentOptions,
|
|
421
422
|
) => {
|
|
422
423
|
const lastEditedAt = new Date().toISOString()
|
|
423
|
-
const transactionId =
|
|
424
|
+
const transactionId = randomUuid()
|
|
424
425
|
const previous = findCommentById(state, commentId)
|
|
425
426
|
|
|
426
427
|
if (previous) {
|
|
@@ -468,7 +469,7 @@ export const setCommentStatus: (
|
|
|
468
469
|
{state, instance, key}: StoreContext<CommentsStoreState, BoundResourceKey>,
|
|
469
470
|
{commentId, status}: SetCommentStatusOptions,
|
|
470
471
|
) => {
|
|
471
|
-
const transactionId =
|
|
472
|
+
const transactionId = randomUuid()
|
|
472
473
|
const previousComments: StoredComment[] = []
|
|
473
474
|
|
|
474
475
|
for (const entry of Object.values(state.get().entries)) {
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
} from '../store/createStateSourceAction'
|
|
30
30
|
import {type StoreState} from '../store/createStoreState'
|
|
31
31
|
import {defineStore, type StoreContext} from '../store/defineStore'
|
|
32
|
-
import {
|
|
32
|
+
import {randomId} from '../utils/ids'
|
|
33
33
|
import {setCleanupTimeout} from '../utils/setCleanupTimeout'
|
|
34
34
|
import {observeAddonDatasetClient} from './addonDatasetStore'
|
|
35
35
|
import {buildCommentThreads} from './buildCommentThreads'
|
|
@@ -360,7 +360,7 @@ export const getCommentsState: (
|
|
|
360
360
|
selector: selectComments,
|
|
361
361
|
onSubscribe: ({state, instance}, options: CommentsOptions) => {
|
|
362
362
|
const key = getCommentsKey(toCommentsKeyParts(instance, options))
|
|
363
|
-
const subscriptionId =
|
|
363
|
+
const subscriptionId = randomId(16)
|
|
364
364
|
state.set('addSubscriber', addSubscriber(key, subscriptionId))
|
|
365
365
|
|
|
366
366
|
return () => {
|
|
@@ -390,7 +390,7 @@ export const getCommentThreadsState: (
|
|
|
390
390
|
selector: selectCommentThreads,
|
|
391
391
|
onSubscribe: ({state, instance}, options: CommentsOptions) => {
|
|
392
392
|
const key = getCommentsKey(toCommentsKeyParts(instance, options))
|
|
393
|
-
const subscriptionId =
|
|
393
|
+
const subscriptionId = randomId(16)
|
|
394
394
|
state.set('addSubscriber', addSubscriber(key, subscriptionId))
|
|
395
395
|
|
|
396
396
|
return () => {
|
|
@@ -452,7 +452,7 @@ function resolveList<T>(
|
|
|
452
452
|
// fetch and this promise would never settle. Holding it only for the duration
|
|
453
453
|
// of the resolve also means a component that suspends and then errors before
|
|
454
454
|
// mounting does not leave a subscriber-less entry behind holding its error.
|
|
455
|
-
const subscriptionId =
|
|
455
|
+
const subscriptionId = randomId(16)
|
|
456
456
|
state.set('addSubscriber', addSubscriber(key, subscriptionId))
|
|
457
457
|
|
|
458
458
|
const release = () => state.set('removeSubscriber', removeSubscriber(key, subscriptionId))
|
package/src/config/authConfig.ts
CHANGED
|
@@ -76,4 +76,22 @@ export interface AuthConfig {
|
|
|
76
76
|
* ignoring any storage or callback handling.
|
|
77
77
|
*/
|
|
78
78
|
token?: string
|
|
79
|
+
|
|
80
|
+
oauth?: {
|
|
81
|
+
/**
|
|
82
|
+
* Opaque OAuth `client_id`.
|
|
83
|
+
*/
|
|
84
|
+
clientId: string
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The redirect URI registered for this client. The OAuth provider
|
|
88
|
+
* redirects back here with the authorization `code` and `state`.
|
|
89
|
+
*/
|
|
90
|
+
redirectUri: string
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Organisation id used for RFC 8707 resource scoping.
|
|
94
|
+
*/
|
|
95
|
+
organizationId: string
|
|
96
|
+
}
|
|
79
97
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {type EventTopicDef, type StateTopicDef} from '../topics'
|
|
2
|
+
|
|
3
|
+
declare module '../topics' {
|
|
4
|
+
interface Topics {
|
|
5
|
+
'test.count': StateTopicDef<number>
|
|
6
|
+
'test.token': StateTopicDef<string | null>
|
|
7
|
+
'test.scheme': StateTopicDef<'light' | 'dark'>
|
|
8
|
+
'test.suspending': StateTopicDef<string>
|
|
9
|
+
'test.ping': EventTopicDef<{n: number}>
|
|
10
|
+
'test.echo': EventTopicDef<{n: number}, {n: number}>
|
|
11
|
+
'test.mint': EventTopicDef<void, string>
|
|
12
|
+
'test.profile': StateTopicDef<{
|
|
13
|
+
fullName: string
|
|
14
|
+
tags: readonly string[]
|
|
15
|
+
}>
|
|
16
|
+
'test.greet': EventTopicDef<{fullName: string}, {salutation: string; language: string}>
|
|
17
|
+
}
|
|
18
|
+
}
|