@wp-typia/project-tools 0.13.4 → 0.15.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/runtime/scaffold-onboarding.js +8 -4
- package/dist/runtime/scaffold.d.ts +1 -0
- package/dist/runtime/scaffold.js +3 -0
- package/dist/runtime/schema-core.d.ts +1 -0
- package/dist/runtime/schema-core.js +188 -8
- package/package.json +1 -1
- package/templates/_shared/compound/persistence/scripts/block-config.ts.mustache +16 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/api-types.ts.mustache +10 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/api.ts.mustache +51 -38
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/data.ts.mustache +76 -33
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/interactivity.ts.mustache +206 -41
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/render.php.mustache +37 -43
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/transport.ts.mustache +254 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/types.ts.mustache +13 -8
- package/templates/_shared/compound/persistence-auth/{{slugKebabCase}}.php.mustache +139 -0
- package/templates/_shared/compound/persistence-public/{{slugKebabCase}}.php.mustache +159 -0
- package/templates/_shared/persistence/auth/{{slugKebabCase}}.php.mustache +139 -0
- package/templates/_shared/persistence/core/scripts/sync-rest-contracts.ts.mustache +16 -0
- package/templates/_shared/persistence/core/src/api-types.ts.mustache +10 -0
- package/templates/_shared/persistence/core/src/api.ts.mustache +51 -38
- package/templates/_shared/persistence/core/src/data.ts.mustache +76 -33
- package/templates/_shared/persistence/core/src/interactivity.ts.mustache +206 -43
- package/templates/_shared/persistence/core/src/transport.ts.mustache +254 -0
- package/templates/_shared/persistence/public/{{slugKebabCase}}.php.mustache +159 -0
- package/templates/_shared/rest-helpers/public/inc/rest-public.php.mustache +1 -1
- package/templates/_shared/workspace/persistence-auth/server.php.mustache +139 -0
- package/templates/_shared/workspace/persistence-public/inc/rest-public.php.mustache +1 -1
- package/templates/_shared/workspace/persistence-public/server.php.mustache +159 -0
- package/templates/persistence/src/edit.tsx.mustache +1 -1
- package/templates/persistence/src/render.php.mustache +37 -43
- package/templates/persistence/src/types.ts.mustache +13 -9
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { getContext, store } from '@wordpress/interactivity';
|
|
2
2
|
import { generatePublicWriteRequestId } from '@wp-typia/block-runtime/identifiers';
|
|
3
3
|
|
|
4
|
-
import { fetchState, writeState } from './api';
|
|
5
|
-
import type {
|
|
4
|
+
import { fetchBootstrap, fetchState, writeState } from './api';
|
|
5
|
+
import type {
|
|
6
|
+
{{pascalCase}}ClientState,
|
|
7
|
+
{{pascalCase}}Context,
|
|
8
|
+
{{pascalCase}}State,
|
|
9
|
+
} from './types';
|
|
6
10
|
|
|
7
11
|
function hasExpiredPublicWriteToken(
|
|
8
|
-
|
|
12
|
+
expiresAt?: number
|
|
9
13
|
): boolean {
|
|
10
14
|
return (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Date.now() >= context.publicWriteExpiresAt * 1000
|
|
15
|
+
typeof expiresAt === 'number' &&
|
|
16
|
+
expiresAt > 0 &&
|
|
17
|
+
Date.now() >= expiresAt * 1000
|
|
15
18
|
);
|
|
16
19
|
}
|
|
17
20
|
|
|
@@ -20,67 +23,212 @@ function getWriteBlockedMessage(
|
|
|
20
23
|
): string {
|
|
21
24
|
return context.persistencePolicy === 'authenticated'
|
|
22
25
|
? 'Sign in to persist this counter.'
|
|
23
|
-
: '
|
|
26
|
+
: 'Public writes are temporarily unavailable.';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const BOOTSTRAP_MAX_ATTEMPTS = 3;
|
|
30
|
+
const BOOTSTRAP_RETRY_DELAYS_MS = [ 250, 500 ];
|
|
31
|
+
|
|
32
|
+
async function waitForBootstrapRetry( delayMs: number ): Promise< void > {
|
|
33
|
+
await new Promise( ( resolve ) => {
|
|
34
|
+
setTimeout( resolve, delayMs );
|
|
35
|
+
} );
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getClientState(
|
|
39
|
+
context: {{pascalCase}}Context
|
|
40
|
+
): {{pascalCase}}ClientState {
|
|
41
|
+
if ( context.client ) {
|
|
42
|
+
return context.client;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
context.client = {
|
|
46
|
+
bootstrapError: '',
|
|
47
|
+
writeExpiry: 0,
|
|
48
|
+
writeNonce: '',
|
|
49
|
+
writeToken: '',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return context.client;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function clearBootstrapError(
|
|
56
|
+
context: {{pascalCase}}Context,
|
|
57
|
+
clientState: {{pascalCase}}ClientState
|
|
58
|
+
): void {
|
|
59
|
+
if ( context.error === clientState.bootstrapError ) {
|
|
60
|
+
context.error = '';
|
|
61
|
+
}
|
|
62
|
+
clientState.bootstrapError = '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function setBootstrapError(
|
|
66
|
+
context: {{pascalCase}}Context,
|
|
67
|
+
clientState: {{pascalCase}}ClientState,
|
|
68
|
+
message: string
|
|
69
|
+
): void {
|
|
70
|
+
clientState.bootstrapError = message;
|
|
71
|
+
context.error = message;
|
|
24
72
|
}
|
|
25
73
|
|
|
26
74
|
const { actions, state } = store( '{{slugKebabCase}}', {
|
|
27
75
|
state: {
|
|
28
|
-
canWrite: false,
|
|
29
|
-
count: 0,
|
|
30
|
-
error: undefined,
|
|
31
76
|
isHydrated: false,
|
|
32
|
-
isLoading: false,
|
|
33
|
-
isSaving: false,
|
|
34
|
-
isVisible: true,
|
|
35
77
|
} as {{pascalCase}}State,
|
|
36
78
|
|
|
37
79
|
actions: {
|
|
38
|
-
async
|
|
80
|
+
async loadState() {
|
|
39
81
|
const context = getContext< {{pascalCase}}Context >();
|
|
40
82
|
if ( context.postId <= 0 || ! context.resourceKey ) {
|
|
41
83
|
return;
|
|
42
84
|
}
|
|
43
85
|
|
|
44
|
-
|
|
45
|
-
|
|
86
|
+
context.isLoading = true;
|
|
87
|
+
context.error = '';
|
|
46
88
|
|
|
47
89
|
try {
|
|
48
90
|
const result = await fetchState( {
|
|
49
91
|
postId: context.postId,
|
|
50
92
|
resourceKey: context.resourceKey,
|
|
93
|
+
}, {
|
|
94
|
+
transportTarget: 'frontend',
|
|
51
95
|
} );
|
|
52
96
|
if ( ! result.isValid || ! result.data ) {
|
|
53
|
-
|
|
97
|
+
context.error = result.errors[ 0 ]?.expected ?? 'Unable to load counter';
|
|
54
98
|
return;
|
|
55
99
|
}
|
|
56
100
|
context.count = result.data.count;
|
|
57
|
-
context.storage = result.data.storage;
|
|
58
|
-
state.count = result.data.count;
|
|
59
101
|
} catch ( error ) {
|
|
60
|
-
|
|
102
|
+
context.error =
|
|
61
103
|
error instanceof Error ? error.message : 'Unknown loading error';
|
|
62
104
|
} finally {
|
|
63
|
-
|
|
105
|
+
context.isLoading = false;
|
|
64
106
|
}
|
|
65
107
|
},
|
|
108
|
+
async loadBootstrap() {
|
|
109
|
+
const context = getContext< {{pascalCase}}Context >();
|
|
110
|
+
const clientState = getClientState( context );
|
|
111
|
+
if ( context.postId <= 0 || ! context.resourceKey ) {
|
|
112
|
+
context.bootstrapReady = true;
|
|
113
|
+
context.canWrite = false;
|
|
114
|
+
clientState.bootstrapError = '';
|
|
115
|
+
clientState.writeExpiry = 0;
|
|
116
|
+
clientState.writeNonce = '';
|
|
117
|
+
clientState.writeToken = '';
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
context.isBootstrapping = true;
|
|
122
|
+
|
|
123
|
+
let bootstrapSucceeded = false;
|
|
124
|
+
let lastBootstrapError =
|
|
125
|
+
'Unable to initialize write access';
|
|
126
|
+
|
|
127
|
+
for ( let attempt = 1; attempt <= BOOTSTRAP_MAX_ATTEMPTS; attempt += 1 ) {
|
|
128
|
+
try {
|
|
129
|
+
const result = await fetchBootstrap( {
|
|
130
|
+
postId: context.postId,
|
|
131
|
+
resourceKey: context.resourceKey,
|
|
132
|
+
}, {
|
|
133
|
+
transportTarget: 'frontend',
|
|
134
|
+
} );
|
|
135
|
+
if ( ! result.isValid || ! result.data ) {
|
|
136
|
+
lastBootstrapError =
|
|
137
|
+
result.errors[ 0 ]?.expected ??
|
|
138
|
+
'Unable to initialize write access';
|
|
139
|
+
if ( attempt < BOOTSTRAP_MAX_ATTEMPTS ) {
|
|
140
|
+
await waitForBootstrapRetry(
|
|
141
|
+
BOOTSTRAP_RETRY_DELAYS_MS[ attempt - 1 ] ?? 750
|
|
142
|
+
);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
clientState.writeExpiry =
|
|
149
|
+
typeof result.data.publicWriteExpiresAt === 'number' &&
|
|
150
|
+
result.data.publicWriteExpiresAt > 0
|
|
151
|
+
? result.data.publicWriteExpiresAt
|
|
152
|
+
: 0;
|
|
153
|
+
clientState.writeToken =
|
|
154
|
+
typeof result.data.publicWriteToken === 'string' &&
|
|
155
|
+
result.data.publicWriteToken.length > 0
|
|
156
|
+
? result.data.publicWriteToken
|
|
157
|
+
: '';
|
|
158
|
+
clientState.writeNonce =
|
|
159
|
+
typeof result.data.restNonce === 'string' &&
|
|
160
|
+
result.data.restNonce.length > 0
|
|
161
|
+
? result.data.restNonce
|
|
162
|
+
: '';
|
|
163
|
+
context.bootstrapReady = true;
|
|
164
|
+
context.canWrite =
|
|
165
|
+
result.data.canWrite === true &&
|
|
166
|
+
(
|
|
167
|
+
context.persistencePolicy === 'authenticated'
|
|
168
|
+
? clientState.writeNonce.length > 0
|
|
169
|
+
: clientState.writeToken.length > 0 &&
|
|
170
|
+
! hasExpiredPublicWriteToken( clientState.writeExpiry )
|
|
171
|
+
);
|
|
172
|
+
clearBootstrapError( context, clientState );
|
|
173
|
+
bootstrapSucceeded = true;
|
|
174
|
+
break;
|
|
175
|
+
} catch ( error ) {
|
|
176
|
+
lastBootstrapError =
|
|
177
|
+
error instanceof Error ? error.message : 'Unknown bootstrap error';
|
|
178
|
+
if ( attempt < BOOTSTRAP_MAX_ATTEMPTS ) {
|
|
179
|
+
await waitForBootstrapRetry(
|
|
180
|
+
BOOTSTRAP_RETRY_DELAYS_MS[ attempt - 1 ] ?? 750
|
|
181
|
+
);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if ( ! bootstrapSucceeded ) {
|
|
189
|
+
context.bootstrapReady = false;
|
|
190
|
+
context.canWrite = false;
|
|
191
|
+
clientState.writeExpiry = 0;
|
|
192
|
+
clientState.writeNonce = '';
|
|
193
|
+
clientState.writeToken = '';
|
|
194
|
+
setBootstrapError( context, clientState, lastBootstrapError );
|
|
195
|
+
}
|
|
196
|
+
context.isBootstrapping = false;
|
|
197
|
+
},
|
|
66
198
|
async increment() {
|
|
67
199
|
const context = getContext< {{pascalCase}}Context >();
|
|
200
|
+
const clientState = getClientState( context );
|
|
68
201
|
if ( context.postId <= 0 || ! context.resourceKey ) {
|
|
69
202
|
return;
|
|
70
203
|
}
|
|
71
|
-
if (
|
|
204
|
+
if ( ! context.bootstrapReady ) {
|
|
205
|
+
await actions.loadBootstrap();
|
|
206
|
+
}
|
|
207
|
+
if ( ! context.bootstrapReady ) {
|
|
208
|
+
context.error = 'Write access is still initializing.';
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (
|
|
212
|
+
context.persistencePolicy === 'public' &&
|
|
213
|
+
hasExpiredPublicWriteToken( clientState.writeExpiry )
|
|
214
|
+
) {
|
|
215
|
+
await actions.loadBootstrap();
|
|
216
|
+
}
|
|
217
|
+
if (
|
|
218
|
+
context.persistencePolicy === 'public' &&
|
|
219
|
+
hasExpiredPublicWriteToken( clientState.writeExpiry )
|
|
220
|
+
) {
|
|
72
221
|
context.canWrite = false;
|
|
73
|
-
|
|
74
|
-
state.error = getWriteBlockedMessage( context );
|
|
222
|
+
context.error = getWriteBlockedMessage( context );
|
|
75
223
|
return;
|
|
76
224
|
}
|
|
77
|
-
if ( ! context.canWrite
|
|
78
|
-
|
|
225
|
+
if ( ! context.canWrite ) {
|
|
226
|
+
context.error = getWriteBlockedMessage( context );
|
|
79
227
|
return;
|
|
80
228
|
}
|
|
81
229
|
|
|
82
|
-
|
|
83
|
-
|
|
230
|
+
context.isSaving = true;
|
|
231
|
+
context.error = '';
|
|
84
232
|
|
|
85
233
|
try {
|
|
86
234
|
const result = await writeState( {
|
|
@@ -92,24 +240,28 @@ const { actions, state } = store( '{{slugKebabCase}}', {
|
|
|
92
240
|
: undefined,
|
|
93
241
|
publicWriteToken:
|
|
94
242
|
context.persistencePolicy === 'public' &&
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
? context.publicWriteToken
|
|
243
|
+
clientState.writeToken.length > 0
|
|
244
|
+
? clientState.writeToken
|
|
98
245
|
: undefined,
|
|
99
246
|
resourceKey: context.resourceKey,
|
|
100
|
-
},
|
|
247
|
+
}, {
|
|
248
|
+
restNonce:
|
|
249
|
+
clientState.writeNonce.length > 0
|
|
250
|
+
? clientState.writeNonce
|
|
251
|
+
: undefined,
|
|
252
|
+
transportTarget: 'frontend',
|
|
253
|
+
} );
|
|
101
254
|
if ( ! result.isValid || ! result.data ) {
|
|
102
|
-
|
|
255
|
+
context.error = result.errors[ 0 ]?.expected ?? 'Unable to update counter';
|
|
103
256
|
return;
|
|
104
257
|
}
|
|
105
258
|
context.count = result.data.count;
|
|
106
259
|
context.storage = result.data.storage;
|
|
107
|
-
state.count = result.data.count;
|
|
108
260
|
} catch ( error ) {
|
|
109
|
-
|
|
261
|
+
context.error =
|
|
110
262
|
error instanceof Error ? error.message : 'Unknown update error';
|
|
111
263
|
} finally {
|
|
112
|
-
|
|
264
|
+
context.isSaving = false;
|
|
113
265
|
}
|
|
114
266
|
},
|
|
115
267
|
},
|
|
@@ -117,18 +269,29 @@ const { actions, state } = store( '{{slugKebabCase}}', {
|
|
|
117
269
|
callbacks: {
|
|
118
270
|
init() {
|
|
119
271
|
const context = getContext< {{pascalCase}}Context >();
|
|
120
|
-
context.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
272
|
+
context.client = {
|
|
273
|
+
bootstrapError: '',
|
|
274
|
+
writeExpiry: 0,
|
|
275
|
+
writeNonce: '',
|
|
276
|
+
writeToken: '',
|
|
277
|
+
};
|
|
278
|
+
context.bootstrapReady = false;
|
|
279
|
+
context.canWrite = false;
|
|
280
|
+
context.count = 0;
|
|
281
|
+
context.error = '';
|
|
282
|
+
context.isBootstrapping = false;
|
|
283
|
+
context.isLoading = false;
|
|
284
|
+
context.isSaving = false;
|
|
125
285
|
},
|
|
126
286
|
mounted() {
|
|
127
287
|
state.isHydrated = true;
|
|
128
288
|
if ( typeof document !== 'undefined' ) {
|
|
129
289
|
document.documentElement.dataset[ '{{slugCamelCase}}Hydrated' ] = 'true';
|
|
130
290
|
}
|
|
131
|
-
void
|
|
291
|
+
void Promise.allSettled( [
|
|
292
|
+
actions.loadState(),
|
|
293
|
+
actions.loadBootstrap(),
|
|
294
|
+
] );
|
|
132
295
|
},
|
|
133
296
|
},
|
|
134
297
|
} );
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type EndpointCallOptions,
|
|
3
|
+
resolveRestRouteUrl,
|
|
4
|
+
} from '@wp-typia/rest';
|
|
5
|
+
|
|
6
|
+
export type PersistenceTransportTarget = 'editor' | 'frontend';
|
|
7
|
+
export type PersistenceTransportOperation = 'read' | 'write';
|
|
8
|
+
|
|
9
|
+
export interface PersistenceTransportOptions {
|
|
10
|
+
restNonce?: string;
|
|
11
|
+
transportTarget?: PersistenceTransportTarget;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface EndpointWithPath {
|
|
15
|
+
path: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type PersistenceTransportResolver = (
|
|
19
|
+
endpoint: EndpointWithPath,
|
|
20
|
+
request?: unknown,
|
|
21
|
+
options?: PersistenceTransportOptions
|
|
22
|
+
) => EndpointCallOptions;
|
|
23
|
+
|
|
24
|
+
interface PersistenceTransportTargetResolvers {
|
|
25
|
+
read: PersistenceTransportResolver;
|
|
26
|
+
write: PersistenceTransportResolver;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Replace any of these base URLs to route a given target through a contract-compatible proxy or BFF.
|
|
30
|
+
const EDITOR_READ_BASE_URL: string | undefined = undefined;
|
|
31
|
+
const EDITOR_WRITE_BASE_URL: string | undefined = undefined;
|
|
32
|
+
const FRONTEND_READ_BASE_URL: string | undefined = undefined;
|
|
33
|
+
const FRONTEND_WRITE_BASE_URL: string | undefined = undefined;
|
|
34
|
+
|
|
35
|
+
function resolveEndpointUrl(
|
|
36
|
+
endpointPath: string,
|
|
37
|
+
baseUrl?: string
|
|
38
|
+
): string {
|
|
39
|
+
if ( typeof baseUrl === 'string' && baseUrl.trim().length > 0 ) {
|
|
40
|
+
const origin =
|
|
41
|
+
typeof window !== 'undefined' ? window.location.origin : 'http://localhost';
|
|
42
|
+
const resolvedBaseUrl = new URL( baseUrl.trim(), origin );
|
|
43
|
+
const [ pathWithQuery, hash = '' ] = endpointPath.split( '#', 2 );
|
|
44
|
+
const [ rawPath, rawQuery = '' ] = pathWithQuery.split( '?', 2 );
|
|
45
|
+
const normalizedEndpointPath = rawPath.replace( /^\/+/, '' );
|
|
46
|
+
|
|
47
|
+
if ( resolvedBaseUrl.searchParams.has( 'rest_route' ) ) {
|
|
48
|
+
const restRouteBase = resolvedBaseUrl.searchParams.get( 'rest_route' ) ?? '/';
|
|
49
|
+
const normalizedRestRouteBase = restRouteBase
|
|
50
|
+
.replace( /^\/+/, '' )
|
|
51
|
+
.replace( /\/+$/, '' );
|
|
52
|
+
const nextRestRoute = normalizedRestRouteBase.length > 0
|
|
53
|
+
? `/${ normalizedRestRouteBase }/${ normalizedEndpointPath }`
|
|
54
|
+
: `/${ normalizedEndpointPath }`;
|
|
55
|
+
resolvedBaseUrl.searchParams.set( 'rest_route', nextRestRoute );
|
|
56
|
+
} else {
|
|
57
|
+
const basePath = resolvedBaseUrl.pathname.endsWith( '/' )
|
|
58
|
+
? resolvedBaseUrl.pathname
|
|
59
|
+
: `${ resolvedBaseUrl.pathname }/`;
|
|
60
|
+
resolvedBaseUrl.pathname = `${ basePath }${ normalizedEndpointPath }`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for ( const [ key, value ] of new URLSearchParams( rawQuery ) ) {
|
|
64
|
+
resolvedBaseUrl.searchParams.append( key, value );
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if ( hash ) {
|
|
68
|
+
resolvedBaseUrl.hash = hash;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return resolvedBaseUrl.toString();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return resolveRestRouteUrl( endpointPath );
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isPlainObject(
|
|
78
|
+
value: unknown
|
|
79
|
+
): value is Record< string, unknown > {
|
|
80
|
+
if ( Object.prototype.toString.call( value ) !== '[object Object]' ) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const prototype = Object.getPrototypeOf( value );
|
|
85
|
+
return prototype === null || prototype === Object.prototype;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function encodeRequestQuery( request: unknown ): string {
|
|
89
|
+
if ( request === undefined || request === null ) {
|
|
90
|
+
return '';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if ( request instanceof URLSearchParams ) {
|
|
94
|
+
return request.toString();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if ( ! isPlainObject( request ) ) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
'Persistence transport read requests must be plain objects or URLSearchParams.'
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const params = new URLSearchParams();
|
|
104
|
+
|
|
105
|
+
for ( const [ key, value ] of Object.entries( request ) ) {
|
|
106
|
+
if ( value === undefined || value === null ) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if ( Array.isArray( value ) ) {
|
|
111
|
+
for ( const item of value ) {
|
|
112
|
+
params.append( key, String( item ) );
|
|
113
|
+
}
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
params.set( key, String( value ) );
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return params.toString();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function joinUrlWithQuery(
|
|
124
|
+
url: string,
|
|
125
|
+
query: string
|
|
126
|
+
): string {
|
|
127
|
+
if ( ! query ) {
|
|
128
|
+
return url;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const nextUrl = new URL(
|
|
132
|
+
url,
|
|
133
|
+
typeof window !== 'undefined' ? window.location.origin : 'http://localhost'
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
for ( const [ key, value ] of new URLSearchParams( query ) ) {
|
|
137
|
+
nextUrl.searchParams.append( key, value );
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return nextUrl.toString();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function resolveRestNonce( fallback?: string ): string | undefined {
|
|
144
|
+
if ( typeof fallback === 'string' && fallback.length > 0 ) {
|
|
145
|
+
return fallback;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if ( typeof window === 'undefined' ) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const wpApiSettings = ( window as typeof window & {
|
|
153
|
+
wpApiSettings?: { nonce?: string };
|
|
154
|
+
} ).wpApiSettings;
|
|
155
|
+
|
|
156
|
+
return typeof wpApiSettings?.nonce === 'string' && wpApiSettings.nonce.length > 0
|
|
157
|
+
? wpApiSettings.nonce
|
|
158
|
+
: undefined;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function buildCallOptions( {
|
|
162
|
+
baseUrl,
|
|
163
|
+
endpoint,
|
|
164
|
+
includeRestNonce,
|
|
165
|
+
operation,
|
|
166
|
+
options,
|
|
167
|
+
request,
|
|
168
|
+
}: {
|
|
169
|
+
baseUrl?: string;
|
|
170
|
+
endpoint: EndpointWithPath;
|
|
171
|
+
includeRestNonce: boolean;
|
|
172
|
+
operation: PersistenceTransportOperation;
|
|
173
|
+
options?: PersistenceTransportOptions;
|
|
174
|
+
request?: unknown;
|
|
175
|
+
} ): EndpointCallOptions {
|
|
176
|
+
const nonce = includeRestNonce ? resolveRestNonce( options?.restNonce ) : undefined;
|
|
177
|
+
const requestOptions: EndpointCallOptions['requestOptions'] = {};
|
|
178
|
+
|
|
179
|
+
if ( nonce ) {
|
|
180
|
+
requestOptions.headers = {
|
|
181
|
+
'X-WP-Nonce': nonce,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if ( typeof baseUrl === 'string' && baseUrl.trim().length > 0 ) {
|
|
186
|
+
const endpointUrl = resolveEndpointUrl( endpoint.path, baseUrl );
|
|
187
|
+
requestOptions.url =
|
|
188
|
+
operation === 'read'
|
|
189
|
+
? joinUrlWithQuery( endpointUrl, encodeRequestQuery( request ) )
|
|
190
|
+
: endpointUrl;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return Object.keys( requestOptions ).length > 0
|
|
194
|
+
? {
|
|
195
|
+
requestOptions,
|
|
196
|
+
}
|
|
197
|
+
: {};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export const persistenceTransportTargets: Record<
|
|
201
|
+
PersistenceTransportTarget,
|
|
202
|
+
PersistenceTransportTargetResolvers
|
|
203
|
+
> = {
|
|
204
|
+
editor: {
|
|
205
|
+
read: ( endpoint, request, options ) =>
|
|
206
|
+
buildCallOptions( {
|
|
207
|
+
baseUrl: EDITOR_READ_BASE_URL,
|
|
208
|
+
endpoint,
|
|
209
|
+
includeRestNonce: {{isAuthenticatedPersistencePolicy}},
|
|
210
|
+
operation: 'read',
|
|
211
|
+
options,
|
|
212
|
+
request,
|
|
213
|
+
} ),
|
|
214
|
+
write: ( endpoint, request, options ) =>
|
|
215
|
+
buildCallOptions( {
|
|
216
|
+
baseUrl: EDITOR_WRITE_BASE_URL,
|
|
217
|
+
endpoint,
|
|
218
|
+
includeRestNonce: {{isAuthenticatedPersistencePolicy}},
|
|
219
|
+
operation: 'write',
|
|
220
|
+
options,
|
|
221
|
+
request,
|
|
222
|
+
} ),
|
|
223
|
+
},
|
|
224
|
+
frontend: {
|
|
225
|
+
read: ( endpoint, request, options ) =>
|
|
226
|
+
buildCallOptions( {
|
|
227
|
+
baseUrl: FRONTEND_READ_BASE_URL,
|
|
228
|
+
endpoint,
|
|
229
|
+
includeRestNonce: false,
|
|
230
|
+
operation: 'read',
|
|
231
|
+
options,
|
|
232
|
+
request,
|
|
233
|
+
} ),
|
|
234
|
+
write: ( endpoint, request, options ) =>
|
|
235
|
+
buildCallOptions( {
|
|
236
|
+
baseUrl: FRONTEND_WRITE_BASE_URL,
|
|
237
|
+
endpoint,
|
|
238
|
+
includeRestNonce: {{isAuthenticatedPersistencePolicy}},
|
|
239
|
+
operation: 'write',
|
|
240
|
+
options,
|
|
241
|
+
request,
|
|
242
|
+
} ),
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
export function resolveTransportCallOptions(
|
|
247
|
+
target: PersistenceTransportTarget,
|
|
248
|
+
operation: PersistenceTransportOperation,
|
|
249
|
+
endpoint: EndpointWithPath,
|
|
250
|
+
request?: unknown,
|
|
251
|
+
options?: PersistenceTransportOptions
|
|
252
|
+
): EndpointCallOptions {
|
|
253
|
+
return persistenceTransportTargets[ target ][ operation ]( endpoint, request, options );
|
|
254
|
+
}
|