Install child-friendly replies by default through signed Marketplace delivery
This commit is contained in:
1 parent
efff238a75
commit
e70b6eab91
8 files changed
+276
-18
No files matched your search
@@ -545,8 +545,10 @@ export function createCodingComposition(
|
||||
});
|
||||
void gameResourceDelivery.resumePending().catch(() => undefined);
|
||||
void gameAudioDelivery.resumePending().catch(() => undefined);
|
||||
void pluginMarketplace.readLibrary().catch(() => undefined);
|
||||
const unsubscribeMarketplaceSession = subscribeWorksSquareSession(() => {
|
||||
void invalidateManagedResources();
|
||||
void pluginMarketplace.readLibrary().catch(() => undefined);
|
||||
void gameResourceDelivery.resumePending().catch(() => undefined);
|
||||
void gameAudioDelivery.resumePending().catch(() => undefined);
|
||||
});
|
||||
|
||||
@@ -28,6 +28,8 @@ import type {
|
||||
PluginBackendProjection,
|
||||
} from '../coding-plugins/registry';
|
||||
import type { ProjectPluginService } from '../coding-plugins/project-service';
|
||||
import { DEFAULT_CHILD_REPLIES_PLUGIN_ID, installDefaultChildReplies } from '../coding-plugins/default-installation';
|
||||
import { MarketplaceClientError } from '../coding-plugins/marketplace-client';
|
||||
import type {
|
||||
PluginBillingPolicy,
|
||||
PluginCatalogOperation,
|
||||
@@ -170,6 +172,25 @@ async function publicLibrary(
|
||||
export function createCodingPluginMarketplaceService(
|
||||
options: CreateCodingPluginMarketplaceServiceOptions,
|
||||
): CodingPluginMarketplaceService {
|
||||
// Order automatic installation and explicit removal so the user's later action wins.
|
||||
let pending: Promise<unknown> = Promise.resolve();
|
||||
const forAccount = <T>(action: () => Promise<T>): Promise<T> => {
|
||||
const binding = options.marketplace.getCurrentAccountBinding();
|
||||
const run = pending.then(async () => {
|
||||
const assertAccount = () => {
|
||||
const current = options.marketplace.getCurrentAccountBinding();
|
||||
if (current?.accountKey !== binding?.accountKey || current?.epoch !== binding?.epoch) {
|
||||
throw new MarketplaceClientError('marketplace_account_changed', 409, 'Marketplace account changed');
|
||||
}
|
||||
};
|
||||
assertAccount();
|
||||
const result = await action();
|
||||
assertAccount();
|
||||
return result;
|
||||
});
|
||||
pending = run.catch(() => undefined);
|
||||
return run;
|
||||
};
|
||||
const install = async (pluginId: string, kind: 'install' | 'update'): Promise<PublicPluginInstallation> => {
|
||||
const snapshot = await options.packageStore.resolveAndInstall({
|
||||
pluginId,
|
||||
@@ -190,6 +211,7 @@ export function createCodingPluginMarketplaceService(
|
||||
return publicInstallation(snapshot);
|
||||
};
|
||||
const uninstall = async (pluginId: string): Promise<PublicPluginInstallation> => {
|
||||
if (pluginId === DEFAULT_CHILD_REPLIES_PLUGIN_ID) await options.marketplace.remove(pluginId);
|
||||
const snapshot = await options.packageStore.uninstall(pluginId);
|
||||
await options.onChanged?.({ pluginId, kind: 'uninstall' });
|
||||
return publicInstallation(snapshot);
|
||||
@@ -197,21 +219,28 @@ export function createCodingPluginMarketplaceService(
|
||||
return {
|
||||
readCatalog: (input = {}) => options.marketplace.readCatalog(input),
|
||||
readDetail: (pluginId) => options.marketplace.readDetail(pluginId),
|
||||
readLibrary: async () => publicLibrary(await options.marketplace.readLibrary(), options.packageStore),
|
||||
acquire: async (pluginId) => {
|
||||
readLibrary: () => forAccount(async () => {
|
||||
const binding = options.marketplace.getCurrentAccountBinding();
|
||||
const library = await installDefaultChildReplies({
|
||||
...options, binding, library: await options.marketplace.readLibrary(),
|
||||
onInstalled: async () => { await options.onChanged?.({ pluginId: DEFAULT_CHILD_REPLIES_PLUGIN_ID, kind: 'install' }); },
|
||||
});
|
||||
return publicLibrary(library, options.packageStore);
|
||||
}),
|
||||
acquire: (pluginId) => forAccount(async () => {
|
||||
const snapshot = await options.marketplace.acquire(pluginId);
|
||||
await options.onChanged?.({ pluginId, kind: 'acquire' });
|
||||
return publicLibrary(snapshot, options.packageStore);
|
||||
},
|
||||
remove: async (pluginId) => {
|
||||
}),
|
||||
remove: (pluginId) => forAccount(async () => {
|
||||
const snapshot = await options.marketplace.remove(pluginId);
|
||||
await options.onChanged?.({ pluginId, kind: 'remove' });
|
||||
return publicLibrary(snapshot, options.packageStore);
|
||||
},
|
||||
install: (pluginId) => install(pluginId, 'install'),
|
||||
installBeta,
|
||||
update: (pluginId) => install(pluginId, 'update'),
|
||||
uninstall,
|
||||
}),
|
||||
install: (pluginId) => forAccount(() => install(pluginId, 'install')),
|
||||
installBeta: (pluginId) => forAccount(() => installBeta(pluginId)),
|
||||
update: (pluginId) => forAccount(() => install(pluginId, 'update')),
|
||||
uninstall: (pluginId) => forAccount(() => uninstall(pluginId)),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { AccountBinding, MarketplaceLibrarySnapshot } from './account-plugin-cache';
|
||||
import { MarketplaceClientError, type MarketplaceClient } from './marketplace-client';
|
||||
import type { PluginPackageStore } from './package-store';
|
||||
import { isMakeLoreVersionCompatible } from './release-descriptor';
|
||||
|
||||
export const DEFAULT_CHILD_REPLIES_PLUGIN_ID = 'makelore.child-friendly-replies';
|
||||
|
||||
/** Defaults use the normal signed Artifact flow and the account's removal receipt. */
|
||||
export async function installDefaultChildReplies(options: {
|
||||
marketplace: MarketplaceClient;
|
||||
packageStore: PluginPackageStore;
|
||||
clientVersion: string;
|
||||
binding: AccountBinding | null;
|
||||
library: MarketplaceLibrarySnapshot;
|
||||
onInstalled(): Promise<void>;
|
||||
}): Promise<MarketplaceLibrarySnapshot> {
|
||||
let library = options.library;
|
||||
const binding = options.binding;
|
||||
if (!binding || library.stale) return library;
|
||||
const assertAccount = () => {
|
||||
const current = options.marketplace.getCurrentAccountBinding();
|
||||
if (current?.accountKey !== binding.accountKey || current?.epoch !== binding.epoch) {
|
||||
throw new MarketplaceClientError('marketplace_account_changed', 409, 'Marketplace account changed');
|
||||
}
|
||||
};
|
||||
const pluginId = DEFAULT_CHILD_REPLIES_PLUGIN_ID;
|
||||
const entry = library.items.find((item) => item.pluginId === pluginId);
|
||||
if (entry?.removedAt || entry?.runtimeStatus === 'suspended' || entry?.catalogStatus === 'retired') return library;
|
||||
try {
|
||||
assertAccount();
|
||||
if (entry && await options.packageStore.getInstalled(pluginId)) return library;
|
||||
const detail = await options.marketplace.readDetail(pluginId);
|
||||
assertAccount();
|
||||
const release = detail.stableRelease;
|
||||
if (detail.stale || detail.runtimeStatus !== 'enabled' || detail.acquisition !== 'free'
|
||||
|| release?.deliveryKind !== 'artifact' || !release.signingKeyId || !release.descriptorSignature
|
||||
|| !isMakeLoreVersionCompatible(options.clientVersion, release.minMakeloreVersion, release.maxMakeloreVersion)) return library;
|
||||
if (!entry) {
|
||||
library = await options.marketplace.acquire(pluginId);
|
||||
assertAccount();
|
||||
}
|
||||
await options.packageStore.resolveAndInstall({
|
||||
pluginId, makeloreVersion: options.clientVersion, channel: 'stable',
|
||||
});
|
||||
assertAccount();
|
||||
await options.onInstalled();
|
||||
} catch (error) {
|
||||
assertAccount();
|
||||
if (error instanceof MarketplaceClientError && error.code === 'marketplace_account_changed') throw error;
|
||||
// An unavailable default must not hide the rest of the Library. A later
|
||||
// session/list refresh retries; no local success or removal receipt is invented.
|
||||
}
|
||||
return library;
|
||||
}
|
||||
Reference in new issue
Block a user