Skip to content

Commit

Permalink
✨ Add encryption option for ebook files (#510)
Browse files Browse the repository at this point in the history
* ✨ Add AES encrypt for ebook files

* ✨ Add key at end of ar:// link

* 🐛 Fix url escape for arweave key

* ✅ Mock arweavekit in test
  • Loading branch information
williamchong authored Feb 7, 2025
1 parent 620ae71 commit a4613c8
Show file tree
Hide file tree
Showing 12 changed files with 1,402 additions and 123 deletions.
63 changes: 31 additions & 32 deletions components/IscnRegisterForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,9 @@
class="mb-[12px] max-w-full"
>
<ContentFingerprintLink
v-for="ipfs of ipfsHashList"
v-for="ipfs of ipfsLinkList"
:key="ipfs"
:item="formatIpfs(ipfs)"
:item="ipfs"
/>
<ContentFingerprintLink
v-for="link of combinedArweaveLinks"
Expand Down Expand Up @@ -694,6 +694,7 @@ import {
} from '~/constant/api'
import { getAccountBalance } from '~/utils/cosmos'
import { logTrackerEvent } from '~/utils/logger'
import { formatArweave, formatIpfs } from '~/utils/ui'
const walletModule = namespace('wallet')
Expand All @@ -719,8 +720,7 @@ export enum AuthorDialogType {
@Component
export default class IscnRegisterForm extends Vue {
@Prop({ default: [] }) readonly fileRecords!: any[]
@Prop({ default: [] }) readonly uploadArweaveIdList!: string[]
@Prop({ default: [] }) readonly uploadArweaveLinkList!: string[]
@Prop({ default: [] }) readonly uploadArweaveInfoList!: any[]
@Prop() readonly epubMetadata!: any | null
@Prop(String) readonly ipfsHash!: string
Expand Down Expand Up @@ -836,6 +836,10 @@ export default class IscnRegisterForm extends Vue {
language: string = ''
shouldShowMoreSettings: boolean = false
get uploadArweaveLinkList() {
return this.uploadArweaveInfoList.map((info) => info.link)
}
get isUseArweaveLink() {
return this.shouldShowDRMOption && this.isUseArweaveLinkChecked
}
Expand All @@ -849,6 +853,10 @@ export default class IscnRegisterForm extends Vue {
return list
}
get ipfsLinkList() {
return this.ipfsHashList.map((ipfs) => formatIpfs(ipfs))
}
get shouldShowContentFingerprintInput() {
return !this.fileRecords.some((file) => file.fileData)
}
Expand Down Expand Up @@ -931,11 +939,19 @@ export default class IscnRegisterForm extends Vue {
if (this.isUseArweaveLink) {
const list = [...this.uploadArweaveLinkList]
if (this.arweaveId) {
list.push(this.formatArweave(this.arweaveId) as string)
list.push(formatArweave(this.arweaveId) as string)
}
return list;
}
return this.combinedArweaveIdList.map((link) => this.formatArweave(link))
let arweaveLinkList: string[] = []
if (this.uploadArweaveInfoList && this.uploadArweaveInfoList.length) {
arweaveLinkList = this.uploadArweaveInfoList.map((info) => formatArweave(info.id, info.key))
}
if (this.arweaveId) {
arweaveLinkList.push(formatArweave(this.arweaveId))
}
return arweaveLinkList
}
get contentFingerprintLinks() {
Expand All @@ -944,7 +960,7 @@ export default class IscnRegisterForm extends Vue {
array.push(...this.combinedArweaveLinks)
}
if (!this.isUseArweaveLink && this.ipfsHashList.length) {
array.push(...this.ipfsHashList.map((ipfs) => this.formatIpfs(ipfs)))
array.push(...this.ipfsHashList.map((ipfs) => formatIpfs(ipfs)))
}
if (this.customContentFingerprints.length) {
array.push(...this.customContentFingerprints)
Expand All @@ -957,7 +973,9 @@ export default class IscnRegisterForm extends Vue {
?.filter((items: any) => items.filename && items.url)
?.map((sameAs: { filename: any; filetype: any; url: any }) => {
if (sameAs.filename && sameAs.filetype) {
return `${sameAs.url}?name=${sameAs.filename}.${sameAs.filetype}`
const parsed = new URL(sameAs.url)
parsed.searchParams.set('name', `${sameAs.filename}.${sameAs.filetype}`)
return parsed.toString()
}
return ''
})
Expand Down Expand Up @@ -1071,11 +1089,11 @@ export default class IscnRegisterForm extends Vue {
if (this.arweaveFee.lte(0)) {
return 1
}
return this.combinedArweaveIdList.length ? 2 : 1
return this.combinedArweaveLinks.length ? 2 : 1
}
get totalSignStep() {
if (this.combinedArweaveIdList.length && this.arweaveFee.lte(0)) return 1
if (this.combinedArweaveLinks.length && this.arweaveFee.lte(0)) return 1
return 2
}
Expand All @@ -1089,7 +1107,7 @@ export default class IscnRegisterForm extends Vue {
if (this.isUploadingArweave) {
return this.$t('IscnRegisterForm.signDialog.closeWarning')
}
if (this.combinedArweaveIdList.length) {
if (this.combinedArweaveLinks.length) {
return this.$t('IscnRegisterForm.signDialog.sign.iscn.register')
}
return this.$t('IscnRegisterForm.signDialog.sign.arweave.upload')
Expand Down Expand Up @@ -1126,17 +1144,6 @@ export default class IscnRegisterForm extends Vue {
)
}
get combinedArweaveIdList() {
let arweaveIdList: string[] = []
if (this.uploadArweaveIdList && this.uploadArweaveIdList.length) {
arweaveIdList = [...this.uploadArweaveIdList]
}
if (this.arweaveId) {
arweaveIdList.push(this.arweaveId)
}
return arweaveIdList
}
get isBookType() {
return this.type === 'Book'
}
Expand Down Expand Up @@ -1165,7 +1172,7 @@ export default class IscnRegisterForm extends Vue {
this.author.name = this.epubMetadata.author || ''
this.author.authorDescription = ''
this.tags = this.epubMetadata.tags || []
this.thumbnailUrl = this.formatArweave(
this.thumbnailUrl = formatArweave(
this.epubMetadata.thumbnailArweaveId,
) as string
if (this.author.name) {
Expand Down Expand Up @@ -1346,14 +1353,6 @@ export default class IscnRegisterForm extends Vue {
this.license = value
}
formatIpfs(ipfsHash: string) {
return this.$t('IscnRegisterForm.ipfs.link', { hash: ipfsHash }) as string
}
formatArweave(arweaveId: string) {
return this.$t('IscnRegisterForm.arweave.link', { arweaveId }) as string
}
async getLikerIdsAddresses() {
let likerIdsAddresses: any[] = []
try {
Expand Down Expand Up @@ -1488,7 +1487,7 @@ estimation,
}
if (
!this.fileRecords.some((file) => file.fileBlob) ||
this.combinedArweaveIdList.length
this.combinedArweaveLinks.length
)
await this.submitToISCN()
}
Expand Down
49 changes: 38 additions & 11 deletions components/IscnUploadForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@
</table>
</div>
</div>
<FormField v-if="hasEbookInRecords">
<CheckBox v-model="isEncryptEBookData">{{ $t('UploadForm.label.encryptEBookData') }}</CheckBox>
</FormField>
<FormField v-if="showAddISCNPageOption">
<CheckBox v-model="isAddISCNPageToEbook">{{ $t('UploadForm.label.insertISCNPage') }}</CheckBox>
</FormField>
Expand Down Expand Up @@ -244,6 +247,7 @@ import exifr from 'exifr'
import Hash from 'ipfs-only-hash'
import BigNumber from 'bignumber.js'
import ePub from 'epubjs';
import { encryptDataWithAES } from 'arweavekit/dist/lib/encryption';
import { OfflineSigner } from '@cosmjs/proto-signing'
Expand Down Expand Up @@ -310,6 +314,7 @@ export default class IscnUploadForm extends Vue {
isOpenSignDialog = false
isOpenWarningSnackbar = false
isSizeExceeded = false
isEncryptEBookData = true
isAddISCNPageToEbook = false
uploadSizeLimit: number = UPLOAD_FILESIZE_MAX
Expand All @@ -318,7 +323,7 @@ export default class IscnUploadForm extends Vue {
arweaveFee = new BigNumber(0)
arweaveFeeMap: Record<string, string> = {}
sentArweaveTransactionInfo = new Map<
string, { transactionHash?: string, arweaveId?: string, arweaveLink?: string }
string, { transactionHash?: string, arweaveId?: string, arweaveLink?: string, arweaveKey?: string }
>()
likerId: string = ''
Expand Down Expand Up @@ -402,14 +407,18 @@ export default class IscnUploadForm extends Vue {
}
}
get showAddISCNPageOption() {
return this.mode === MODE.EDIT
&& this.fileRecords.some(file => [
get hasEbookInRecords() {
return this.fileRecords.some(file => [
'application/epub+zip',
'application/pdf',
].includes(file.fileType))
}
get showAddISCNPageOption() {
return this.mode === MODE.EDIT
&& this.hasEbookInRecords
}
get modifiedFileRecords() {
if (!this.isAddISCNPageToEbook || this.mode !== MODE.EDIT) return this.fileRecords
return this.fileRecords.map((record) => {
Expand Down Expand Up @@ -844,26 +853,40 @@ export default class IscnUploadForm extends Vue {
}
}
let key;
try {
const arrayBuffer = await tempRecord.fileBlob.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
let buffer = Buffer.from(arrayBuffer);
if ([
'application/epub+zip',
'application/pdf',
].includes(record.fileType)
&& this.isEncryptEBookData) {
const {
rawEncryptedKeyAsBase64,
combinedArrayBuffer,
} = await encryptDataWithAES({ data: arrayBuffer });
buffer = Buffer.from(combinedArrayBuffer);
key = rawEncryptedKeyAsBase64
}
const { arweaveId, arweaveLink } = await uploadSingleFileToBundlr(buffer, {
fileSize: tempRecord.fileBlob?.size || 0,
ipfsHash: tempRecord.ipfsHash,
fileType: tempRecord.fileType as string,
txHash: tempRecord.transactionHash,
token: this.getToken,
key,
});
if (arweaveId) {
const uploadedData = this.sentArweaveTransactionInfo.get(record.ipfsHash) || {};
this.sentArweaveTransactionInfo.set(record.ipfsHash, { ...uploadedData, arweaveId, arweaveLink });
this.sentArweaveTransactionInfo.set(record.ipfsHash, { ...uploadedData, arweaveId, arweaveLink, arweaveKey: key });
if (tempRecord.fileName.includes('cover.jpeg')) {
const metadata = this.epubMetadataList.find((file: any) => file.thumbnailIpfsHash === record.ipfsHash)
if (metadata) {
metadata.thumbnailArweaveId = arweaveId
}
}
this.$emit('arweaveUploaded', { arweaveId, arweaveLink })
this.$emit('arweaveUploaded', { arweaveId, arweaveLink, arweaveKey: key })
this.isOpenSignDialog = false
} else {
this.isOpenWarningSnackbar = true
Expand Down Expand Up @@ -987,8 +1010,11 @@ export default class IscnUploadForm extends Vue {
this.uploadStatus = '';
}
const uploadArweaveIdList = Array.from(this.sentArweaveTransactionInfo.values()).map(entry => entry.arweaveId);
const uploadArweaveLinkList = Array.from(this.sentArweaveTransactionInfo.values()).map(entry => entry.arweaveLink);
const uploadArweaveInfoList = Array.from(this.sentArweaveTransactionInfo.values())
.map(entry => {
const { arweaveId, arweaveLink, arweaveKey } = entry;
return { id: arweaveId, link: arweaveLink, key: arweaveKey };
});
this.modifiedFileRecords.forEach((record: any, index:number) => {
if (this.sentArweaveTransactionInfo.has(record.ipfsHash)) {
const info = this.sentArweaveTransactionInfo.get(
Expand All @@ -998,17 +1024,18 @@ export default class IscnUploadForm extends Vue {
const {
arweaveId,
arweaveLink,
arweaveKey,
} = info;
if (arweaveId) this.modifiedFileRecords[index].arweaveId = arweaveId
if (arweaveLink) this.modifiedFileRecords[index].arweaveLink = arweaveLink
if (arweaveKey) this.modifiedFileRecords[index].arweaveKey = arweaveKey
}
}
})
this.$emit('submit', {
fileRecords: this.modifiedFileRecords,
arweaveIds: uploadArweaveIdList,
arweaveInfos: uploadArweaveInfoList,
epubMetadata: this.epubMetadataList[0],
arweaveLinks: uploadArweaveLinkList,
})
}
Expand Down
2 changes: 2 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ module.exports = {
'^~/(.*)$': '<rootDir>/$1',
'^vue$': 'vue/dist/vue.common.js',
"axios": "axios/dist/node/axios.cjs", // https://stackoverflow.com/a/74297004
'^arweavekit$': '<rootDir>/test/mocks/arweavekit.js',
'^arweavekit/(.*)$': '<rootDir>/test/mocks/arweavekit.js',
},
moduleFileExtensions: [
'ts',
Expand Down
4 changes: 1 addition & 3 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,6 @@
"IscnEditInfo.updating": "Updating ISCN...",
"IscnEditInfo.button.confirm": "Update ISCN",
"IscnEditInfo.button.edit": "Edit ISCN",
"IscnRegisterForm.arweave.link": "ar://{arweaveId}",
"IscnRegisterForm.button.back": "Back",
"IscnRegisterForm.button.confirm": "Confirm",
"IscnRegisterForm.button.loading": "Loading files...",
Expand Down Expand Up @@ -219,8 +218,6 @@
"IscnRegisterForm.error.walletConnect": "Wallet Connect is temporarily unavailable for minting. Please try the other signing methods",
"IscnRegisterForm.guide.uploadOnly": "Upload only mode without ISCN",
"IscnRegisterForm.guide.review": "Review and edit metadata",
"IscnRegisterForm.ipfs.link": "ipfs://{hash}",
"IscnRegisterForm.fileSHA256.link": "hash://sha256/{hash}",
"IscnRegisterForm.label.author": "Author",
"IscnRegisterForm.label.stakeholder": "Stakeholders",
"IscnRegisterForm.label.datePublished": "Original Publication Date",
Expand Down Expand Up @@ -386,6 +383,7 @@
"UploadForm.button.size.limit": "(Recommended file size: < 20 MB)",
"UploadForm.button.skip": "Skip Upload",
"UploadForm.button": "Start Upload",
"UploadForm.label.encryptEBookData": "Encrypt EPUB & PDF on upload",
"UploadForm.label.insertISCNPage": "Add ISCN Page to EPUB & PDF",
"UploadForm.guide.dropFile": "Drop your file here, or",
"UploadForm.guide.selectFile": "Select your file and publish to IPFS",
Expand Down
1 change: 1 addition & 0 deletions nuxt.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ export default {
'@bundlr-network',
'@noble/curves',
'arbundle',
'arweavekit',
({ isLegacy }) => (isLegacy ? 'axios' : undefined),
],
extend(config, ctx) {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@walletconnect/client": "^1.6.6",
"@walletconnect/qrcode-modal": "^1.6.6",
"@walletconnect/utils": "^1.6.6",
"arweavekit": "^1.5.1",
"axios": "^1.7.4",
"bignumber.js": "^9.0.1",
"cheerio": "^1.0.0-rc.11",
Expand Down
Loading

0 comments on commit a4613c8

Please sign in to comment.