@solana-mobile/dapp-store-publishing-tools 0.16.0 → 1.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.
- package/lib/CoreUtils.js +0 -3
- package/lib/index.js +1 -0
- package/lib/portal/attestation.js +189 -0
- package/lib/portal/compat.js +3 -0
- package/lib/portal/index.js +5 -0
- package/lib/portal/signer.js +432 -0
- package/lib/portal/types.js +1 -0
- package/lib/portal/workflow/contracts.js +1 -0
- package/lib/portal/workflow/execution.js +493 -0
- package/lib/portal/workflow/ingestion.js +265 -0
- package/lib/portal/workflow/lifecycle.js +616 -0
- package/lib/portal/workflow/logging.js +8 -0
- package/lib/portal/workflow/source/files.js +304 -0
- package/lib/portal/workflow/source/preparation.js +318 -0
- package/lib/portal/workflow/state/bundle.js +260 -0
- package/lib/portal/workflow/state/checkpoints.js +53 -0
- package/lib/portal/workflow/state/session.js +100 -0
- package/lib/portal/workflow.js +1 -0
- package/lib/publish/PublishCoreAttestation.js +18 -17
- package/lib/publish/PublishCoreRemove.js +7 -89
- package/lib/publish/PublishCoreSubmit.js +7 -117
- package/lib/publish/PublishCoreSupport.js +7 -86
- package/lib/publish/PublishCoreUpdate.js +7 -117
- package/lib/publish/index.js +1 -0
- package/lib/schemas/releaseJsonMetadata.json +1 -2
- package/package.json +2 -4
- package/src/CoreUtils.ts +0 -6
- package/src/index.ts +1 -0
- package/src/portal/attestation.ts +76 -0
- package/src/portal/compat.ts +5 -0
- package/src/portal/index.ts +5 -0
- package/src/portal/signer.ts +327 -0
- package/src/portal/types.ts +447 -0
- package/src/portal/workflow/contracts.ts +108 -0
- package/src/portal/workflow/execution.ts +412 -0
- package/src/portal/workflow/ingestion.ts +187 -0
- package/src/portal/workflow/lifecycle.ts +435 -0
- package/src/portal/workflow/logging.ts +17 -0
- package/src/portal/workflow/source/files.ts +49 -0
- package/src/portal/workflow/source/preparation.ts +189 -0
- package/src/portal/workflow/state/bundle.ts +193 -0
- package/src/portal/workflow/state/checkpoints.ts +70 -0
- package/src/portal/workflow/state/session.ts +87 -0
- package/src/portal/workflow.ts +9 -0
- package/src/publish/PublishCoreAttestation.ts +21 -26
- package/src/publish/PublishCoreRemove.ts +13 -109
- package/src/publish/PublishCoreSubmit.ts +18 -150
- package/src/publish/PublishCoreSupport.ts +13 -102
- package/src/publish/PublishCoreUpdate.ts +17 -155
- package/src/publish/index.ts +2 -1
- package/src/schemas/releaseJsonMetadata.json +1 -2
- package/lib/publish/dapp_publisher_portal.js +0 -206
- package/src/publish/dapp_publisher_portal.ts +0 -81
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAttestationPayloadFromClient,
|
|
3
|
+
type PublicationAttestationResult,
|
|
4
|
+
} from "../attestation.js";
|
|
5
|
+
import {
|
|
6
|
+
signSerializedTransaction,
|
|
7
|
+
type PublicationTransactionValidation,
|
|
8
|
+
} from "../signer.js";
|
|
9
|
+
import type {
|
|
10
|
+
PublicationAttestationClient,
|
|
11
|
+
PublicationBundle,
|
|
12
|
+
PublicationSession,
|
|
13
|
+
PublicationSigner,
|
|
14
|
+
PublicationWorkflowLogger,
|
|
15
|
+
PublicationWorkflowResult,
|
|
16
|
+
} from "../types.js";
|
|
17
|
+
import type { PublicationWorkflowClient } from "./contracts.js";
|
|
18
|
+
import { logWorkflowInfo } from "./logging.js";
|
|
19
|
+
import {
|
|
20
|
+
checkpointAtLeast,
|
|
21
|
+
publicationStageToCheckpoint,
|
|
22
|
+
} from "./state/checkpoints.js";
|
|
23
|
+
import {
|
|
24
|
+
normalizePublicationBundle,
|
|
25
|
+
resolvePublicationFeePayer,
|
|
26
|
+
resolvePublicationSignerAddress,
|
|
27
|
+
resolveReleaseDisplayName,
|
|
28
|
+
resolveReleaseMetadataUri,
|
|
29
|
+
validatePublicationBundle,
|
|
30
|
+
} from "./state/bundle.js";
|
|
31
|
+
import {
|
|
32
|
+
normalizePublicationSession,
|
|
33
|
+
resolveReleaseMintAddress,
|
|
34
|
+
} from "./state/session.js";
|
|
35
|
+
|
|
36
|
+
type PublicationExecutionContext = {
|
|
37
|
+
client: PublicationWorkflowClient;
|
|
38
|
+
bundle: PublicationBundle;
|
|
39
|
+
signer: PublicationSigner;
|
|
40
|
+
attestationClient: PublicationAttestationClient;
|
|
41
|
+
publicationSession: PublicationSession;
|
|
42
|
+
publicationCheckpoint: NonNullable<PublicationSession["checkpoint"]>;
|
|
43
|
+
logger?: PublicationWorkflowLogger;
|
|
44
|
+
releaseMetadataUri: string;
|
|
45
|
+
publisherAddress: string;
|
|
46
|
+
payerAddress: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type PublicationExecutionState = {
|
|
50
|
+
releaseTransactionSignature?: string;
|
|
51
|
+
collectionTransactionSignature?: string;
|
|
52
|
+
attestationResult?: PublicationAttestationResult;
|
|
53
|
+
hubspotTicketId?: string;
|
|
54
|
+
releaseMintAddress?: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
async function signPreparedTransaction(
|
|
58
|
+
signer: PublicationSigner,
|
|
59
|
+
serializedTransaction: string,
|
|
60
|
+
validation: PublicationTransactionValidation
|
|
61
|
+
): Promise<string> {
|
|
62
|
+
return signSerializedTransaction(signer, serializedTransaction, validation);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function submitReleaseMintIfNeeded(
|
|
66
|
+
context: PublicationExecutionContext,
|
|
67
|
+
state: PublicationExecutionState
|
|
68
|
+
) {
|
|
69
|
+
if (checkpointAtLeast(context.publicationCheckpoint, "mint-submitted")) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
logWorkflowInfo(context.logger, "Preparing release NFT transaction", {
|
|
74
|
+
step: "mint.prepare",
|
|
75
|
+
status: "running",
|
|
76
|
+
releaseId: context.bundle.releaseId,
|
|
77
|
+
publicationSessionId: context.publicationSession.id,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const preparedReleaseTransaction =
|
|
81
|
+
await context.client.prepareReleaseNftTransaction({
|
|
82
|
+
releaseId: context.bundle.releaseId,
|
|
83
|
+
releaseName: resolveReleaseDisplayName(context.bundle),
|
|
84
|
+
releaseMetadataUri: context.releaseMetadataUri,
|
|
85
|
+
appMintAddress: context.bundle.signerAuthority.appMintAddress,
|
|
86
|
+
publisherAddress: context.publisherAddress,
|
|
87
|
+
payerAddress: context.payerAddress,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
state.releaseMintAddress = preparedReleaseTransaction.mintAddress;
|
|
91
|
+
state.releaseTransactionSignature = await signPreparedTransaction(
|
|
92
|
+
context.signer,
|
|
93
|
+
preparedReleaseTransaction.transaction,
|
|
94
|
+
{
|
|
95
|
+
kind: "release-mint",
|
|
96
|
+
expectedBlockhash: preparedReleaseTransaction.blockhash,
|
|
97
|
+
expectedFeePayerAddress: context.payerAddress,
|
|
98
|
+
expectedSignerAddress: context.publisherAddress,
|
|
99
|
+
expectedMintAddress: preparedReleaseTransaction.mintAddress,
|
|
100
|
+
expectedAppMintAddress: context.bundle.signerAuthority.appMintAddress,
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const signedTransactionResult = await context.client.submitSignedTransaction({
|
|
105
|
+
signedTransaction: state.releaseTransactionSignature,
|
|
106
|
+
publicationSessionId: context.publicationSession.id,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
state.releaseTransactionSignature =
|
|
110
|
+
signedTransactionResult.transactionSignature;
|
|
111
|
+
|
|
112
|
+
logWorkflowInfo(context.logger, "Release NFT transaction submitted", {
|
|
113
|
+
step: "mint.submit",
|
|
114
|
+
status: "complete",
|
|
115
|
+
releaseId: context.bundle.releaseId,
|
|
116
|
+
publicationSessionId: context.publicationSession.id,
|
|
117
|
+
transactionSignature: state.releaseTransactionSignature,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function saveReleaseMintIfNeeded(
|
|
122
|
+
context: PublicationExecutionContext,
|
|
123
|
+
state: PublicationExecutionState
|
|
124
|
+
) {
|
|
125
|
+
if (checkpointAtLeast(context.publicationCheckpoint, "mint-saved")) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const mintAddress =
|
|
130
|
+
context.publicationSession.releaseMintAddress ??
|
|
131
|
+
context.publicationSession.expectedMintAddress ??
|
|
132
|
+
context.bundle.release.releaseMintAddress ??
|
|
133
|
+
state.releaseMintAddress;
|
|
134
|
+
|
|
135
|
+
if (!mintAddress) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
"Publication bundle did not include a release mint address"
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!state.releaseTransactionSignature) {
|
|
142
|
+
throw new Error("Release transaction signature is missing");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
logWorkflowInfo(context.logger, "Saving release NFT data", {
|
|
146
|
+
step: "mint.save",
|
|
147
|
+
status: "running",
|
|
148
|
+
releaseId: context.bundle.releaseId,
|
|
149
|
+
publicationSessionId: context.publicationSession.id,
|
|
150
|
+
mintAddress,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
await context.client.saveReleaseNftData({
|
|
154
|
+
releaseId: context.bundle.releaseId,
|
|
155
|
+
mintAddress,
|
|
156
|
+
transactionSignature: state.releaseTransactionSignature,
|
|
157
|
+
metadataUri: context.releaseMetadataUri,
|
|
158
|
+
ownerAddress: context.publisherAddress,
|
|
159
|
+
releaseName: resolveReleaseDisplayName(context.bundle),
|
|
160
|
+
releaseVersion: context.bundle.release.versionName,
|
|
161
|
+
androidPackage: context.bundle.release.androidPackage,
|
|
162
|
+
appMintAddress: context.bundle.signerAuthority.appMintAddress,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
state.releaseMintAddress = mintAddress;
|
|
166
|
+
|
|
167
|
+
logWorkflowInfo(context.logger, "Release NFT data saved", {
|
|
168
|
+
step: "mint.save",
|
|
169
|
+
status: "complete",
|
|
170
|
+
releaseId: context.bundle.releaseId,
|
|
171
|
+
publicationSessionId: context.publicationSession.id,
|
|
172
|
+
mintAddress,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function verifyReleaseCollectionIfNeeded(
|
|
177
|
+
context: PublicationExecutionContext,
|
|
178
|
+
state: PublicationExecutionState
|
|
179
|
+
) {
|
|
180
|
+
if (
|
|
181
|
+
checkpointAtLeast(context.publicationCheckpoint, "verification-submitted")
|
|
182
|
+
) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!state.releaseMintAddress) {
|
|
187
|
+
throw new Error(
|
|
188
|
+
"Publication bundle did not include a release mint address for collection verification"
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
logWorkflowInfo(
|
|
193
|
+
context.logger,
|
|
194
|
+
"Preparing collection verification transaction",
|
|
195
|
+
{
|
|
196
|
+
step: "verify.prepare",
|
|
197
|
+
status: "running",
|
|
198
|
+
releaseId: context.bundle.releaseId,
|
|
199
|
+
publicationSessionId: context.publicationSession.id,
|
|
200
|
+
mintAddress: state.releaseMintAddress,
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const preparedVerifyTransaction =
|
|
205
|
+
await context.client.prepareVerifyCollectionTransaction({
|
|
206
|
+
dappId: context.bundle.signerAuthority.dappId ?? context.bundle.dapp.id,
|
|
207
|
+
nftMintAddress: state.releaseMintAddress,
|
|
208
|
+
collectionMintAddress: context.bundle.signerAuthority.appMintAddress,
|
|
209
|
+
collectionAuthority: context.bundle.signerAuthority.collectionAuthority,
|
|
210
|
+
payerAddress: context.payerAddress,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
state.collectionTransactionSignature = await signPreparedTransaction(
|
|
214
|
+
context.signer,
|
|
215
|
+
preparedVerifyTransaction.transaction,
|
|
216
|
+
{
|
|
217
|
+
kind: "verify-collection",
|
|
218
|
+
expectedBlockhash: preparedVerifyTransaction.blockhash,
|
|
219
|
+
expectedFeePayerAddress: context.payerAddress,
|
|
220
|
+
expectedSignerAddress: context.publisherAddress,
|
|
221
|
+
expectedNftMintAddress: state.releaseMintAddress,
|
|
222
|
+
expectedCollectionMintAddress:
|
|
223
|
+
context.bundle.signerAuthority.appMintAddress,
|
|
224
|
+
expectedCollectionAuthority:
|
|
225
|
+
context.bundle.signerAuthority.collectionAuthority,
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
logWorkflowInfo(
|
|
230
|
+
context.logger,
|
|
231
|
+
"Submitting collection verification transaction",
|
|
232
|
+
{
|
|
233
|
+
step: "verify.submit",
|
|
234
|
+
status: "running",
|
|
235
|
+
releaseId: context.bundle.releaseId,
|
|
236
|
+
publicationSessionId: context.publicationSession.id,
|
|
237
|
+
mintAddress: state.releaseMintAddress,
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const signedVerifyTransactionResult =
|
|
242
|
+
await context.client.submitSignedTransaction({
|
|
243
|
+
signedTransaction: state.collectionTransactionSignature,
|
|
244
|
+
publicationSessionId: context.publicationSession.id,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
state.collectionTransactionSignature =
|
|
248
|
+
signedVerifyTransactionResult.transactionSignature;
|
|
249
|
+
|
|
250
|
+
await context.client.markReleaseCollectionAsVerified({
|
|
251
|
+
releaseId: context.bundle.releaseId,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
logWorkflowInfo(context.logger, "Release collection verified", {
|
|
255
|
+
step: "verify.submit",
|
|
256
|
+
status: "complete",
|
|
257
|
+
releaseId: context.bundle.releaseId,
|
|
258
|
+
publicationSessionId: context.publicationSession.id,
|
|
259
|
+
transactionSignature: state.collectionTransactionSignature,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function attestAndSubmitIfNeeded(
|
|
264
|
+
context: PublicationExecutionContext,
|
|
265
|
+
state: PublicationExecutionState
|
|
266
|
+
) {
|
|
267
|
+
if (checkpointAtLeast(context.publicationCheckpoint, "submitted")) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
logWorkflowInfo(context.logger, "Creating attestation payload", {
|
|
272
|
+
step: "attestation.create",
|
|
273
|
+
status: "running",
|
|
274
|
+
releaseId: context.bundle.releaseId,
|
|
275
|
+
publicationSessionId: context.publicationSession.id,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
state.attestationResult = await createAttestationPayloadFromClient(
|
|
279
|
+
context.attestationClient,
|
|
280
|
+
context.signer
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
logWorkflowInfo(context.logger, "Attestation payload created", {
|
|
284
|
+
step: "attestation.create",
|
|
285
|
+
status: "complete",
|
|
286
|
+
releaseId: context.bundle.releaseId,
|
|
287
|
+
publicationSessionId: context.publicationSession.id,
|
|
288
|
+
requestUniqueId: state.attestationResult.requestUniqueId,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
logWorkflowInfo(context.logger, "Submitting release to store", {
|
|
292
|
+
step: "submit.store",
|
|
293
|
+
status: "running",
|
|
294
|
+
releaseId: context.bundle.releaseId,
|
|
295
|
+
publicationSessionId: context.publicationSession.id,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const submissionResult = await context.client.submitToStore({
|
|
299
|
+
releaseId: context.bundle.releaseId,
|
|
300
|
+
whatsNew: context.bundle.release.newInVersion,
|
|
301
|
+
attestation: {
|
|
302
|
+
payload: state.attestationResult.payload,
|
|
303
|
+
requestUniqueId: state.attestationResult.requestUniqueId,
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
state.hubspotTicketId =
|
|
308
|
+
submissionResult.hubspotTicketId ?? state.hubspotTicketId;
|
|
309
|
+
|
|
310
|
+
logWorkflowInfo(context.logger, "Release submitted to store", {
|
|
311
|
+
step: "submit.store",
|
|
312
|
+
status: "complete",
|
|
313
|
+
releaseId: context.bundle.releaseId,
|
|
314
|
+
publicationSessionId: context.publicationSession.id,
|
|
315
|
+
hubspotTicketId: state.hubspotTicketId,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export async function runPublicationWorkflow(
|
|
320
|
+
client: PublicationWorkflowClient,
|
|
321
|
+
bundle: PublicationBundle,
|
|
322
|
+
signer: PublicationSigner,
|
|
323
|
+
attestationClient: PublicationAttestationClient,
|
|
324
|
+
session: PublicationSession,
|
|
325
|
+
logger?: PublicationWorkflowLogger
|
|
326
|
+
): Promise<PublicationWorkflowResult> {
|
|
327
|
+
const normalizedBundle = normalizePublicationBundle(bundle);
|
|
328
|
+
const normalizedSession = normalizePublicationSession(session);
|
|
329
|
+
validatePublicationBundle(normalizedBundle);
|
|
330
|
+
|
|
331
|
+
const requiredSignerAddress =
|
|
332
|
+
resolvePublicationSignerAddress(normalizedBundle);
|
|
333
|
+
if (signer.publicKey !== requiredSignerAddress) {
|
|
334
|
+
throw new Error(
|
|
335
|
+
`Publication signer mismatch. Expected ${requiredSignerAddress}; received ${signer.publicKey}.`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (normalizedSession.stage === "Failed") {
|
|
340
|
+
throw new Error(
|
|
341
|
+
normalizedSession.lastError ||
|
|
342
|
+
normalizedSession.error ||
|
|
343
|
+
"Publication session failed"
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const publicationSession = normalizedSession;
|
|
348
|
+
const publicationCheckpoint =
|
|
349
|
+
publicationSession.checkpoint ??
|
|
350
|
+
publicationStageToCheckpoint(publicationSession.stage);
|
|
351
|
+
const state: PublicationExecutionState = {
|
|
352
|
+
releaseTransactionSignature:
|
|
353
|
+
publicationSession.mintTransactionSignature ?? undefined,
|
|
354
|
+
collectionTransactionSignature:
|
|
355
|
+
publicationSession.verifyTransactionSignature ??
|
|
356
|
+
publicationSession.verificationTransactionSignature ??
|
|
357
|
+
undefined,
|
|
358
|
+
hubspotTicketId: publicationSession.hubspotTicketId ?? undefined,
|
|
359
|
+
releaseMintAddress: resolveReleaseMintAddress(
|
|
360
|
+
normalizedBundle,
|
|
361
|
+
publicationSession
|
|
362
|
+
),
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const context: PublicationExecutionContext = {
|
|
366
|
+
client,
|
|
367
|
+
bundle: normalizedBundle,
|
|
368
|
+
signer,
|
|
369
|
+
attestationClient,
|
|
370
|
+
publicationSession,
|
|
371
|
+
publicationCheckpoint,
|
|
372
|
+
logger,
|
|
373
|
+
releaseMetadataUri: resolveReleaseMetadataUri(
|
|
374
|
+
normalizedBundle,
|
|
375
|
+
publicationSession
|
|
376
|
+
),
|
|
377
|
+
publisherAddress: resolvePublicationSignerAddress(normalizedBundle),
|
|
378
|
+
payerAddress: resolvePublicationFeePayer(normalizedBundle, signer),
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
await submitReleaseMintIfNeeded(context, state);
|
|
382
|
+
await saveReleaseMintIfNeeded(context, state);
|
|
383
|
+
await verifyReleaseCollectionIfNeeded(context, state);
|
|
384
|
+
await attestAndSubmitIfNeeded(context, state);
|
|
385
|
+
|
|
386
|
+
if (!state.releaseMintAddress) {
|
|
387
|
+
throw new Error(
|
|
388
|
+
"Publication session did not resolve a release mint address"
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
ingestionSessionId:
|
|
394
|
+
publicationSession.ingestionSessionId ??
|
|
395
|
+
normalizedBundle.ingestionSessionId ??
|
|
396
|
+
"",
|
|
397
|
+
publicationSessionId:
|
|
398
|
+
publicationSession.id || normalizedBundle.publicationSessionId || "",
|
|
399
|
+
releaseId: normalizedBundle.releaseId || publicationSession.releaseId || "",
|
|
400
|
+
releaseMintAddress: state.releaseMintAddress,
|
|
401
|
+
collectionMintAddress: normalizedBundle.signerAuthority.appMintAddress,
|
|
402
|
+
releaseTransactionSignature: state.releaseTransactionSignature,
|
|
403
|
+
collectionTransactionSignature: state.collectionTransactionSignature,
|
|
404
|
+
attestationRequestUniqueId:
|
|
405
|
+
state.attestationResult?.requestUniqueId ??
|
|
406
|
+
publicationSession.attestationRequestUniqueId ??
|
|
407
|
+
undefined,
|
|
408
|
+
hubspotTicketId: state.hubspotTicketId,
|
|
409
|
+
publicationBundle: normalizedBundle,
|
|
410
|
+
publicationSession,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PublicationIngestionSession,
|
|
3
|
+
PublicationWorkflowLogger,
|
|
4
|
+
} from "../types.js";
|
|
5
|
+
import type {
|
|
6
|
+
PublicationWorkflowClient,
|
|
7
|
+
PublicationWorkflowPollOptions,
|
|
8
|
+
} from "./contracts.js";
|
|
9
|
+
import { logWorkflowInfo } from "./logging.js";
|
|
10
|
+
|
|
11
|
+
function isReadyIngestionSession(
|
|
12
|
+
session: PublicationIngestionSession
|
|
13
|
+
): boolean {
|
|
14
|
+
return session.status === "Ready" || session.status === "ready";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function buildIngestionStatusProgress(
|
|
18
|
+
session: PublicationIngestionSession
|
|
19
|
+
): number {
|
|
20
|
+
if (
|
|
21
|
+
typeof session.processingProgress === "number" &&
|
|
22
|
+
Number.isFinite(session.processingProgress)
|
|
23
|
+
) {
|
|
24
|
+
return Math.max(0, Math.min(1, session.processingProgress / 100));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
switch (session.status) {
|
|
28
|
+
case "created":
|
|
29
|
+
return 0.15;
|
|
30
|
+
case "queued":
|
|
31
|
+
return 0.3;
|
|
32
|
+
case "processing":
|
|
33
|
+
return 0.7;
|
|
34
|
+
case "Ready":
|
|
35
|
+
case "ready":
|
|
36
|
+
case "Failed":
|
|
37
|
+
case "failed":
|
|
38
|
+
return 1;
|
|
39
|
+
default:
|
|
40
|
+
return 0.15;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function buildIngestionStatusMessage(
|
|
45
|
+
session: PublicationIngestionSession
|
|
46
|
+
): string | null {
|
|
47
|
+
if (
|
|
48
|
+
typeof session.processingDetail === "string" &&
|
|
49
|
+
session.processingDetail.trim().length > 0
|
|
50
|
+
) {
|
|
51
|
+
return session.processingDetail.trim();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
typeof session.processingStage === "string" &&
|
|
56
|
+
session.processingStage.trim().length > 0
|
|
57
|
+
) {
|
|
58
|
+
return session.processingStage.trim();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
switch (session.status) {
|
|
62
|
+
case "created":
|
|
63
|
+
return "Portal ingestion request created";
|
|
64
|
+
case "queued":
|
|
65
|
+
return "Portal ingestion queued";
|
|
66
|
+
case "processing":
|
|
67
|
+
return "Portal ingestion is processing the APK";
|
|
68
|
+
case "Ready":
|
|
69
|
+
case "ready":
|
|
70
|
+
return "Portal ingestion is ready";
|
|
71
|
+
default:
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function waitForIngestionSessionReady(
|
|
77
|
+
client: PublicationWorkflowClient,
|
|
78
|
+
ingestionSessionId: string,
|
|
79
|
+
options: PublicationWorkflowPollOptions,
|
|
80
|
+
logger?: PublicationWorkflowLogger
|
|
81
|
+
): Promise<PublicationIngestionSession> {
|
|
82
|
+
logWorkflowInfo(logger, "Waiting for portal ingestion to finish", {
|
|
83
|
+
step: "ingestion.wait",
|
|
84
|
+
status: "running",
|
|
85
|
+
ingestionSessionId,
|
|
86
|
+
stepProgress: 0,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
let previousSnapshot:
|
|
90
|
+
| {
|
|
91
|
+
status: PublicationIngestionSession["status"];
|
|
92
|
+
progress: number | null;
|
|
93
|
+
stage: string | null;
|
|
94
|
+
detail: string | null;
|
|
95
|
+
}
|
|
96
|
+
| undefined;
|
|
97
|
+
|
|
98
|
+
for (let attempt = 1; attempt <= options.maxPollAttempts; attempt += 1) {
|
|
99
|
+
const session = await client.getIngestionSession({
|
|
100
|
+
sessionId: ingestionSessionId,
|
|
101
|
+
ingestionSessionId,
|
|
102
|
+
});
|
|
103
|
+
if (session.status === "Failed" || session.status === "failed") {
|
|
104
|
+
throw new Error(
|
|
105
|
+
session.error ||
|
|
106
|
+
session.processingError ||
|
|
107
|
+
"Publication ingestion failed before the bundle was ready"
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const nextSnapshot = {
|
|
112
|
+
status: session.status,
|
|
113
|
+
progress:
|
|
114
|
+
typeof session.processingProgress === "number"
|
|
115
|
+
? session.processingProgress
|
|
116
|
+
: null,
|
|
117
|
+
stage:
|
|
118
|
+
typeof session.processingStage === "string"
|
|
119
|
+
? session.processingStage
|
|
120
|
+
: null,
|
|
121
|
+
detail:
|
|
122
|
+
typeof session.processingDetail === "string"
|
|
123
|
+
? session.processingDetail
|
|
124
|
+
: null,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
if (
|
|
128
|
+
!previousSnapshot ||
|
|
129
|
+
previousSnapshot.status !== nextSnapshot.status ||
|
|
130
|
+
previousSnapshot.progress !== nextSnapshot.progress ||
|
|
131
|
+
previousSnapshot.stage !== nextSnapshot.stage ||
|
|
132
|
+
previousSnapshot.detail !== nextSnapshot.detail
|
|
133
|
+
) {
|
|
134
|
+
previousSnapshot = nextSnapshot;
|
|
135
|
+
const statusMessage = buildIngestionStatusMessage(session);
|
|
136
|
+
|
|
137
|
+
if (statusMessage) {
|
|
138
|
+
logWorkflowInfo(logger, statusMessage, {
|
|
139
|
+
step: "ingestion.wait",
|
|
140
|
+
status: "running",
|
|
141
|
+
ingestionSessionId,
|
|
142
|
+
releaseId: session.releaseId ?? undefined,
|
|
143
|
+
publicationSessionId: session.publicationSessionId ?? undefined,
|
|
144
|
+
androidPackage: session.androidPackage ?? undefined,
|
|
145
|
+
versionName: session.versionName ?? undefined,
|
|
146
|
+
ingestionStatus:
|
|
147
|
+
session.processingDetail ??
|
|
148
|
+
session.processingStage ??
|
|
149
|
+
session.status,
|
|
150
|
+
ingestionProgress: nextSnapshot.progress ?? undefined,
|
|
151
|
+
ingestionStage: nextSnapshot.stage ?? undefined,
|
|
152
|
+
ingestionDetail: nextSnapshot.detail ?? undefined,
|
|
153
|
+
stepProgress: buildIngestionStatusProgress(session),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (isReadyIngestionSession(session)) {
|
|
159
|
+
logWorkflowInfo(logger, "Portal ingestion is ready", {
|
|
160
|
+
step: "ingestion.wait",
|
|
161
|
+
status: "complete",
|
|
162
|
+
ingestionSessionId,
|
|
163
|
+
releaseId: session.releaseId ?? undefined,
|
|
164
|
+
publicationSessionId: session.publicationSessionId ?? undefined,
|
|
165
|
+
androidPackage: session.androidPackage ?? undefined,
|
|
166
|
+
versionName: session.versionName ?? undefined,
|
|
167
|
+
ingestionStatus:
|
|
168
|
+
session.processingDetail ?? session.processingStage ?? session.status,
|
|
169
|
+
ingestionProgress:
|
|
170
|
+
typeof session.processingProgress === "number"
|
|
171
|
+
? session.processingProgress
|
|
172
|
+
: 100,
|
|
173
|
+
ingestionStage: session.processingStage ?? "Ready",
|
|
174
|
+
ingestionDetail:
|
|
175
|
+
session.processingDetail ?? "Publication ingestion is ready",
|
|
176
|
+
stepProgress: 1,
|
|
177
|
+
});
|
|
178
|
+
return session;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
await new Promise((resolve) => setTimeout(resolve, options.pollIntervalMs));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
throw new Error(
|
|
185
|
+
`Timed out waiting for ingestion session ${ingestionSessionId} to become ready`
|
|
186
|
+
);
|
|
187
|
+
}
|