diff --git a/providers/ic/src/providers/publish/publish.providers.ts b/providers/ic/src/providers/publish/publish.providers.ts index 345d5c1..b38efea 100644 --- a/providers/ic/src/providers/publish/publish.providers.ts +++ b/providers/ic/src/providers/publish/publish.providers.ts @@ -22,7 +22,7 @@ export const deckPublish: DeckPublish = async ({ await uploadResources({ meta: deck.data.meta, - hoisted: { + ids: { ...canisterIds, data_id: deck.id } @@ -53,7 +53,7 @@ export const docPublish: DocPublish = async ({ await uploadResources({ meta: doc.data.meta, - hoisted: { + ids: { ...canisterIds, data_id: doc.id } diff --git a/providers/ic/src/publish/common.publish.ts b/providers/ic/src/publish/common.publish.ts index 2dd45b3..e9b080e 100644 --- a/providers/ic/src/publish/common.publish.ts +++ b/providers/ic/src/publish/common.publish.ts @@ -2,7 +2,8 @@ import {log, Meta, PublishData} from '@deckdeckgo/editor'; import {encodeFilename, getStorageActor, upload} from '../api/storage.api'; import {_SERVICE as StorageBucketActor} from '../canisters/storage/storage.did'; import {EnvStore} from '../stores/env.store'; -import {PublishCanisterIds} from '../types/publish.types'; +import {PublishCanisterIds, PublishIds} from '../types/publish.types'; +import {digestMessage, sha256ToBase64String} from '../utils/crypto.utils'; import {BucketActor} from '../utils/manager.utils'; import {updateTemplateSocialImage} from './social.publish'; @@ -16,16 +17,17 @@ export interface StorageUpload { folder: 'p' | 'd'; } -export const updateTemplate = ({ +export const updateTemplate = async ({ template, data, - canisterIds + ids }: { template: string; data: Partial; - canisterIds: PublishCanisterIds; -}): string => - [...Object.entries(data), ...Object.entries(canisterIds)].reduce( + ids: PublishIds | PublishCanisterIds; +}): Promise => { + // 1. Replace all keys + const updatedTemplate: string = [...Object.entries(data), ...Object.entries(ids)].reduce( (acc: string, [key, value]: [string, string]) => acc .replaceAll(`{{DECKDECKGO_${key.toUpperCase()}}}`, value || '') @@ -34,6 +36,24 @@ export const updateTemplate = ({ template ); + // 2. Build a script that injects canisters and data ids + const {data_canister_id, storage_canister_id} = ids; + const {data_id} = ids as PublishIds; + + const idsScript: string = `window.data_canister_id = "${data_canister_id}";window.storage_canister_id = "${storage_canister_id}";window.data_id = "${data_id ?? ''}";`; + + const templateWithScript: string = updatedTemplate.replaceAll( + '', + `` + ); + + // 3. Calculate a sha256 for the above script and parse it in the CSP + const sha256: string = sha256ToBase64String(new Uint8Array(await digestMessage(idsScript))); + const templateWithCSP: string = templateWithScript.replaceAll('{{DECKDECKGO_IDS_SHAS}}', `'sha256-${sha256}'`); + + return templateWithCSP; +}; + export const initUpload = async ({ indexHTML, folder, @@ -110,21 +130,21 @@ const uploadPaths = ({ export const initIndexHTML = async ({ publishData, - canisterIds, + ids, updateTemplateContent, sourceFolder }: { publishData: PublishData; - canisterIds: PublishCanisterIds; + ids: PublishIds; updateTemplateContent: ({attr, template}: {attr: string | undefined; template: string}) => string; sourceFolder: 'p' | 'd'; }): Promise<{html: string}> => { const template: string = await htmlTemplate(sourceFolder); - const updatedTemplate: string = updateTemplate({ + const updatedTemplate: string = await updateTemplate({ template, data: publishData, - canisterIds + ids }); const {attributes} = publishData; diff --git a/providers/ic/src/publish/deck.publish.ts b/providers/ic/src/publish/deck.publish.ts index 2b2c5b0..5391bf2 100644 --- a/providers/ic/src/publish/deck.publish.ts +++ b/providers/ic/src/publish/deck.publish.ts @@ -102,7 +102,10 @@ const initDeckIndexHTML = async ({ const {html}: {html: string} = await initIndexHTML({ publishData, - canisterIds, + ids: { + ...canisterIds, + data_id: deck.id + }, updateTemplateContent, sourceFolder: 'p' }); diff --git a/providers/ic/src/publish/doc.publish.ts b/providers/ic/src/publish/doc.publish.ts index 6ed4912..138a986 100644 --- a/providers/ic/src/publish/doc.publish.ts +++ b/providers/ic/src/publish/doc.publish.ts @@ -111,7 +111,10 @@ const initDocIndexHTML = async ({ const {html}: {html: string} = await initIndexHTML({ publishData, - canisterIds, + ids: { + ...canisterIds, + data_id: doc.id + }, updateTemplateContent, sourceFolder: 'd' }); diff --git a/providers/ic/src/publish/index-html.publish.ts b/providers/ic/src/publish/index-html.publish.ts index 8979115..83cfbaf 100644 --- a/providers/ic/src/publish/index-html.publish.ts +++ b/providers/ic/src/publish/index-html.publish.ts @@ -18,7 +18,7 @@ export const prepareIndexHtml = async ({ const {photo_url, ...data} = publishData; - let html: string = updateTemplate({template, data, canisterIds}); + let html: string = await updateTemplate({template, data, ids: canisterIds}); html = updatePhotoUrl({html, photo_url}); html = updatePostsList({ diff --git a/providers/ic/src/publish/resources.publish.ts b/providers/ic/src/publish/resources.publish.ts index 240874d..a3977fa 100644 --- a/providers/ic/src/publish/resources.publish.ts +++ b/providers/ic/src/publish/resources.publish.ts @@ -2,9 +2,9 @@ import {log, Meta} from '@deckdeckgo/editor'; import {getStorageActor, upload} from '../api/storage.api'; import {AssetKey, _SERVICE as StorageBucketActor} from '../canisters/storage/storage.did'; import {EnvStore} from '../stores/env.store'; -import {PublishHoistedData} from '../types/publish.types'; +import {PublishIds} from '../types/publish.types'; import {HeaderField} from '../types/storage.types'; -import {digestMessage} from '../utils/crypto.utils'; +import {digestMessage, sha256ToBase64String} from '../utils/crypto.utils'; import {fromNullable, toNullable} from '../utils/did.utils'; import {BucketActor} from '../utils/manager.utils'; import {getAuthor} from './common.publish'; @@ -13,7 +13,7 @@ type KitMimeType = 'text/javascript' | 'text/plain' | 'application/manifest+json interface KitUpdateContent { meta: Meta | undefined; - hoisted: PublishHoistedData; + ids: PublishIds; content: string; } @@ -28,16 +28,7 @@ interface Kit { const getKitPath = (): string => EnvStore.getInstance().get().kitPath; -const sha256ToBase64String = (sha256: Iterable): string => - btoa([...sha256].map((c) => String.fromCharCode(c)).join('')); - -export const uploadResources = async ({ - meta, - hoisted -}: { - meta: Meta | undefined; - hoisted: PublishHoistedData; -}) => { +export const uploadResources = async ({meta, ids}: {meta: Meta | undefined; ids: PublishIds}) => { // 1. Get actor const {actor}: BucketActor = await getStorageActor(); @@ -48,7 +39,7 @@ export const uploadResources = async ({ const kit: Kit[] = await getKit(); const promises: Promise[] = kit.map((kit: Kit) => - addKitIC({kit, actor, meta, hoisted, assetKeys}) + addKitIC({kit, actor, meta, ids, assetKeys}) ); await Promise.all(promises); }; @@ -75,19 +66,19 @@ const addDynamicKitIC = async ({ kit, actor, meta, - hoisted, + ids, assetKeys }: { kit: Kit; actor: StorageBucketActor; meta: Meta | undefined; - hoisted: PublishHoistedData; + ids: PublishIds; assetKeys: AssetKey[]; }) => { const {src, filename, mimeType, updateContent, headers} = kit; const content: string = await downloadKit(src); - const updatedContent: string = updateContent({content, meta, hoisted}); + const updatedContent: string = updateContent({content, meta, ids: ids}); const sha256: string = sha256ToBase64String(new Uint8Array(await digestMessage(updatedContent))); if (!updatedResource({src, sha256, assetKeys})) { @@ -108,13 +99,13 @@ const addKitIC = async ({ kit, actor, meta, - hoisted, + ids, assetKeys }: { kit: Kit; actor: StorageBucketActor; meta: Meta | undefined; - hoisted: PublishHoistedData; + ids: PublishIds; assetKeys: AssetKey[]; }) => { const {updateContent} = kit; @@ -122,7 +113,7 @@ const addKitIC = async ({ // If updateContent is defined we have to compare the sha256 value of the content that will be updated first // e.g. avoiding uploading the manifest at each publish if (updateContent !== undefined) { - await addDynamicKitIC({kit, actor, meta, hoisted, assetKeys}); + await addDynamicKitIC({kit, actor, meta, ids, assetKeys}); return; } @@ -135,7 +126,7 @@ const addKitIC = async ({ const content: string = await downloadKit(src); - const updatedContent: string = updateContent ? updateContent({content, meta, hoisted}) : content; + const updatedContent: string = updateContent ? updateContent({content, meta, ids: ids}) : content; await uploadKit({ filename, @@ -201,9 +192,13 @@ const getKit = async (): Promise => { src, mimeType: 'text/javascript', sha256, - updateContent: ({content, hoisted: {data_canister_id, data_id}}: KitUpdateContent) => + updateContent: ({ + content, + ids: {data_canister_id, data_id, storage_canister_id} + }: KitUpdateContent) => content .replace('{{DECKDECKGO_DATA_CANISTER_ID}}', data_canister_id) + .replace('{{DECKDECKGO_STORAGE_CANISTER_ID}}', storage_canister_id) .replace('{{DECKDECKGO_DATA_ID}}', data_id) }; } diff --git a/providers/ic/src/types/publish.types.ts b/providers/ic/src/types/publish.types.ts index e4d908a..9eae895 100644 --- a/providers/ic/src/types/publish.types.ts +++ b/providers/ic/src/types/publish.types.ts @@ -10,6 +10,6 @@ export interface PublishCanisterIds { storage_canister_id: string; } -export interface PublishHoistedData extends PublishCanisterIds { +export interface PublishIds extends PublishCanisterIds { data_id: string; } diff --git a/providers/ic/src/utils/crypto.utils.ts b/providers/ic/src/utils/crypto.utils.ts index 3648385..68afbf2 100644 --- a/providers/ic/src/utils/crypto.utils.ts +++ b/providers/ic/src/utils/crypto.utils.ts @@ -4,3 +4,6 @@ export const digestMessage = (message?: string): Promise => { const data = encoder.encode(message); return crypto.subtle.digest('SHA-256', data); }; + +export const sha256ToBase64String = (sha256: Iterable): string => + btoa([...sha256].map((c) => String.fromCharCode(c)).join(''));