Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC connection to argus tunnel #5124

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ export type Scalars = {
Boolean: {input: boolean; output: boolean}
Int: {input: number; output: number}
Float: {input: number; output: number}
AccessRoleAssignee: {input: any; output: any}
/** The ID for a AccessRole. */
AccessRoleID: {input: any; output: any}
AccessRoleRecordId: {input: any; output: any}
Expand Down Expand Up @@ -46,4 +45,4 @@ export type Scalars = {
URL: {input: string; output: string}
}

export type Store = 'APP_DEVELOPMENT' | 'DEVELOPMENT' | 'DEVELOPMENT_SUPERSET' | 'PRODUCTION'
export type Store = 'APP_DEVELOPMENT' | 'DEVELOPMENT' | 'PRODUCTION'
172 changes: 172 additions & 0 deletions packages/app/src/cli/ws.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// TODO: Should probably be a tunnel plugin?

import {SampleWebhook} from './services/webhook/request-sample.js'
import {triggerLocalWebhook} from './services/webhook/trigger-local-webhook.js'
import WebSocket from 'ws'
import {FetchError} from '@shopify/cli-kit/node/http'
import {sleep} from '@shopify/cli-kit/node/system'
import crypto from 'crypto'
import {stdout} from 'node:process'
import {Writable} from 'stream'

// TODO: For prod will be te URL of Argus
const websocketURL = process.env.WS_GRAPHQL_ENDPOINT ?? 'ws://localhost:48935/graphql'
// const websocketURL = 'ws://localhost:48935/graphql'

// TODO: Port would be passed in from CLI
const webhookEndpoint = process.env.WEBHOOK_ENDPOINT ?? 'http://localhost:4000/webhooks'
// const webhookEndpoint = 'http://localhost:4000/webhooks'

// TODO: STORE_FQDN provided by App toml
const storeFQDN = process.env.STORE_FQDN ?? 'test.myshopify.com'

const ws = new WebSocket(websocketURL)

// TODO: Keep the connection alive
// TODO: Refresh the token if it expires afer 60 minutes
ws.on('error', console.error)

ws.on('open', function open() {
console.log('connected')
authenticate()
})

ws.on('message', function message(data) {
console.log('received: %s', data)
const event = JSON.parse(data.toString())

switch (event.type) {
case 'connection_ack':
subscribe('webhook')
break
case 'data':
processWebhookEvent(event)
break
}
})

function authenticate() {
const token = getToken(
{
exp: 2524626000,
bucket_id: 'gid://shopify/Shop/12345',
staff_id: 123,
permissions: ['full'],
},
// TODO: API call to partners/Core API to get a valid token
// Core would decide which bucket ID to use for Argus
'so_secret',
)
return ws.send(
JSON.stringify({
type: 'connection_init',
payload: {Authorization: token},
}),
)
}

function subscribe(eventName: string) {
const msg = JSON.stringify({
id: '1',
type: 'start',
payload: {
query: `
subscription {
eventReceived(eventName: "${eventName}") {
payload
eventName
eventScope
eventSerialId
eventSerialGroup
eventSourceApp
eventSourceHost
eventTimestamp
eventUuid
internalSessionId
remoteIp
schemaVersion
bucketId
userId
}
}
`,
},
})
console.log('sending', msg)
return ws.send(msg)
}

function getToken(
tokenPayload: {exp: number; bucket_id: string; staff_id: number; permissions: string[]},
secret: string,
) {
const encodedPayload = Buffer.from(JSON.stringify(tokenPayload)).toString('base64')
const signature = crypto.createHmac('sha256', secret).update(encodedPayload).digest('hex')
return `${encodedPayload}--${signature}`
}

interface WebhookEvent {
payload: {
data: {
eventReceived: {
payload: string
}
}
}
}

function processWebhookEvent(event: WebhookEvent) {
const webhook = JSON.parse(event.payload.data.eventReceived.payload)
// TODO: Switch on different types of events (e.g: flow, app proxy, payment apps etc)
const payload: SampleWebhook = {
headers: webhook.headers,
samplePayload: webhook.payload,
success: true,
userErrors: [],
}
console.log(payload)
triggerWebhook({address: webhookEndpoint, storeFqdn: storeFQDN, stdout}, payload)
.then(console.log)
.catch(console.error)
}

async function triggerWebhook(
options: {address: string; storeFqdn: string; stdout: Writable},
sample: SampleWebhook,
): Promise<boolean> {
let tries = 0

/* eslint-disable no-await-in-loop */
while (tries < 3) {
try {
const result = await triggerLocalWebhook(
options.address,
sample.samplePayload,
JSON.stringify({
...JSON.parse(sample.headers),

// TODO: Should come from the Argus message
'X-Shopify-Shop-Domain': options.storeFqdn,
}),
)

return result
} catch (error) {
if (error instanceof FetchError && error.code === 'ECONNREFUSED') {
if (tries < 3) {
options.stdout.write("App isn't responding yet, retrying in 5 seconds")
await sleep(5)
}
} else {
throw error
}
}

tries++
}
/* eslint-enable no-await-in-loop */

options.stdout.write("App hasn't started in time, giving up")

return false
}
4 changes: 3 additions & 1 deletion packages/plugin-cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@
},
"dependencies": {
"@oclif/core": "3.26.5",
"@shopify/cli-kit": "3.70.0"
"@shopify/cli-kit": "3.70.0",
"@types/ws": "^8.5.5",
"ws": "8.13.0"
},
"devDependencies": {
"@vitest/coverage-istanbul": "^1.6.0"
Expand Down
164 changes: 0 additions & 164 deletions packages/plugin-cloudflare/src/install-cloudflared.test.ts

This file was deleted.

Loading
Loading