Skip to content

Commit

Permalink
wip(service): integrates build service
Browse files Browse the repository at this point in the history
  • Loading branch information
heapwolf committed Apr 30, 2024
1 parent b343080 commit cd5fe02
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 38 deletions.
11 changes: 4 additions & 7 deletions src/components/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class DialogAccount extends TonicDialog {
const app = this.props.app

if (!event.data.success) {
console.error('Unsuccessful data returned', event)
console.error(event)
this.resolve(event.data)
await this.hide()
return
Expand All @@ -44,15 +44,12 @@ class DialogAccount extends TonicDialog {
dataUser.buildKeys = await res.json()
await app.db.state.put('user', dataUser)
await this.hide()
return this.resolve({ data: true })
this.resolve({ data: true })
}
} catch (err) {
if (err.name === "AbortError") {
this.resolve({ data: event.data })
await this.hide()
return this.resolve({ err: true })
}
console.log(err)
this.resolve({ err: true })
await this.hide()
}
}

Expand Down
96 changes: 67 additions & 29 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import components from '@socketsupply/components'
import Indexed from '@socketsupply/indexed'

import { Patch } from './git-data.js'
import { rm } from './lib/fs.js'
import { rm, ls } from './lib/fs.js'

import { RelativeDate } from './components/relative-date.js'
import { GitStatus } from './components/git-status.js'
Expand Down Expand Up @@ -492,6 +492,9 @@ class AppView extends Tonic {
async buildProject () {
const { data: dataUser } = await this.db.state.get('user')

//
// Gather all the archs for the build and determine if it can be done locally.
//
const architectures = [...document.querySelectorAll('#build-target tonic-checkbox')]
.filter(co => co.value)
.map(co => co.dataset.arch)
Expand All @@ -506,6 +509,9 @@ class AppView extends Tonic {
if (process.platform === 'win32' && architectures.some(s => invalidWin32.has(s))) needsBuildService = true
if (process.platform === 'linux' && architectures.some(s => invalidLinux.has(s))) needsBuildService = true

//
// If the user needs a build service, ask them first.
//
if (needsBuildService) {
const platform = process.platform === 'darwin' ? 'MacOS' : process.platform
const coDialogConfirm = document.querySelector('dialog-confirm')
Expand All @@ -514,7 +520,7 @@ class AppView extends Tonic {
message: `
You're using ${platform} but you're trying to build for other operating systems or compute architectures.
<br><br>
Do you want to use our <b>Build Service</b> to handle this for you?
Do you want to upload your files and have our <b>Build Service</b> generate the builds for you?
`,
buttons: [
{ label: 'cancel', value: 'abandon' },
Expand All @@ -526,7 +532,7 @@ class AppView extends Tonic {

if (result.consent) {
//
// Check if the user has an account if not, sign up for one
// Check if the user has an account if not, ask them if they want one
//
if (!dataUser.buildKeys) {
const coDialogAccount = document.querySelector('dialog-account')
Expand All @@ -546,41 +552,33 @@ class AppView extends Tonic {
}
}

//
// Apparently tar has been available on windows since v10. Tar it,
// base64 encode it, and add it to the manifest as a payload.
//
const term = document.querySelector('app-terminal')
const tar = spawn('tar', ['-cvf', '-', this.state.currentProject.id])
const buffer = Buffer.alloc(0)

tar.stdout.on('data', data => {
buffer = Buffer.concat([buffer, data])
})

tar.stderr.on('data', data => {
term.error(data.toString())
})

tar.on('close', (code) => {
term.info(`tar process exited with code ${code}`)
})
const files = await ls(this.state.currentProject.id, { ignoreList: [/^build$/, /\..*/] })

//
// This tells us what architectures you want to build for, what time
// you're trying to send it, your public key and the payload you want built.
//
const { data: dataUserUpdated } = await this.db.state.get('user')

const manifest = {
architectures,
ctime: Date.now(),
pk: dataUser.buildKeys.pk,
data: buffer.toString('base64')
pk: dataUserUpdated.buildKeys.pk,
files
}

const bytes = Buffer.from(JSON.stringify(manifest))
manifest.sig = Encryption.sign(bytes, dataUser.buildKeys.sk)
const key = Buffer.from(dataUserUpdated.buildKeys.sk, 'base64')

manifest.sig = Buffer.from(Encryption.sign(bytes, key)).toString('base64')

try {
//
// Asking for a build will give the user an array of auth objects
// they can use to upload files to s3. after that they can kick off
// the build.
//
const res = await fetch('https://api.socketsupply.co/build', {
method: 'POST',
mode: 'cors',
Expand All @@ -590,9 +588,48 @@ class AppView extends Tonic {

if (res.ok) {
const app = this.props.parent
const { data: dataUser } = await app.db.state.get('user')
dataUser.buildKeys = await res.json()
await app.db.state.put('user', dataUser)
const job = await res.json()

//
// The user might close their laptop in the middle of an upload
// so let's store this data and we can resume it when they open
// the lid and come online again.
//
await app.db.jobs.put(job.id, job)

//
// let the upload go into the background.
//
const uploader = new Worker('uploader.js')
uploader.addEventListener('message', async event => {
const { status, message } = event.data

if (status === 'success') {
const result = await coDialogConfirm.prompt({
type: 'question',
message: `Your files have been uploaded. Do you want to start the build?`,
buttons: [
{ label: 'cancel', value: 'abandon' },
{ label: 'ok', value: 'consent' }
]
})

if (!result.abandon && !result.consent) return

if (result.consent) {
// send a request to kick off the actual build job
}
} else if (status === 'error') {
await coDialogConfirm.prompt({
type: 'question',
message: `There was a problem: ${message}`,
buttons: [
{ label: 'ok', value: 'close' }
]
})
}
})

await this.hide()
return this.resolve({ data: true })
}
Expand All @@ -604,8 +641,9 @@ class AppView extends Tonic {
}

//
// run the build locally. also, it's going to be ready quite
// quickly, so just reveal it when it's ready.
// just run the build locally. also, it's going to be
// ready quite quickly, so just reveal it when it's ready.
//
await this.spawnSSC('build')

const w = await application.getCurrentWindow()
Expand Down
17 changes: 17 additions & 0 deletions src/lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,20 @@ export async function cp (srcDir, destDir) {
}
}
}

export async function ls (dir, { root = dir, ignoreList = [] }) {
let fileList = []
const entries = await fs.promises.readdir(dir, { withFileTypes: true })

for (const entry of entries) {
const fullPath = path.join(dir, entry.name)
if (entry.isDirectory()) {
const subDirFiles = await ls(fullPath, { root, ignoreList })
fileList = fileList.concat(subDirFiles)
} else {
fileList.push(path.relative(root, fullPath))
}
}

return fileList
}
6 changes: 4 additions & 2 deletions src/pages/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ class AppView extends Tonic {

async show () {
super.show()

const coForm = this.querySelector('tonic-form')
const elButton = Tonic.match(e.target, '#form-submit')
elButton.loading(false)

if (coForm) coForm.setValid()
}

Expand All @@ -41,8 +45,6 @@ class AppView extends Tonic {
const coForm = this.querySelector('#form-stripe')
const result = await this.stripe.createToken(this.card, coForm.value)

el.loading(false)

const data = {
...result,
success: !result.error,
Expand Down
36 changes: 36 additions & 0 deletions src/uploader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import fs from 'socket:fs'

self.addEventListener('message', async event => {
const files = event.data.files

for (const file of files) {
try {
const formData = new FormData()

Object.entries(file.fields).forEach(([key, value]) => {
formData.append(key, value)
})

formData.append('file', await fs.readFile(file.path))

const response = await fetch(url, {
method: 'POST',
body: formData
})

if (!response.ok) {
throw new Error(`Failed to upload file: ${response.statusText}`)
}
} catch (error) {
return self.postMessage({
status: 'error',
message: `Error uploading ${file.name}: ${error.message}`
})
}
}

self.postMessage({
status: 'success',
message: 'All files uploaded successfully!'
})
})

0 comments on commit cd5fe02

Please sign in to comment.