Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
16 changes: 16 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

134 changes: 134 additions & 0 deletions cli/src/notifications/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { spawnSync } from 'node:child_process'
import { existsSync, mkdirSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
import { cwd } from 'node:process'
import { intro, log, outro, spinner } from '@clack/prompts'
import { formatRunnerCommand, splitRunnerCommand } from '../runner-command'
import { defaultApiHost, formatError, getConfig, getPMAndCommand, updateConfigbyKey } from '../utils'
import { writeFileAtomic } from '../utils/safeWrites'

const notificationPackages = [
'@capgo/capacitor-notifications',
'@capacitor/push-notifications',
'@capacitor/preferences',
'@capacitor/app',
'@capacitor/device',
]

interface NotificationSetupOptions {
serverUrl?: string
file?: string
force?: boolean
install?: boolean
sync?: boolean
}

function getConfigAppId(config: Awaited<ReturnType<typeof getConfig>>) {
return String(config.config?.plugins?.CapacitorUpdater?.appId || config.config?.appId || '')
}

function renderNotificationHelper(appId: string, serverUrl: string) {
return `import { CapgoNotifications } from '@capgo/capacitor-notifications'

export interface CapgoNotificationIdentity {
externalId: string
identityProof: string
tags?: string[]
attributes?: Record<string, unknown>
consent?: boolean
}

export async function setupCapgoNotifications(identity: CapgoNotificationIdentity) {
if (!identity.externalId)
return
if (!identity.identityProof)
throw new Error('Capgo notification identityProof is required')

await CapgoNotifications.configure({
appId: '${appId}',
serverUrl: '${serverUrl}',
})

return CapgoNotifications.register({
externalId: identity.externalId,
identityProof: identity.identityProof,
tags: identity.tags ?? [],
attributes: identity.attributes ?? {},
consent: identity.consent ?? true,
})
}

export { CapgoNotifications }
`
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

function runCommand(command: string, args: string[], failureMessage: string) {
const result = spawnSync(command, args, { stdio: 'inherit' })
if (result.error)
throw result.error
if (result.status !== 0)
throw new Error(`${failureMessage} exited with code ${result.status}`)
}

function runInstall() {
const pm = getPMAndCommand()
log.info(`Installing notification packages with ${pm.installCommand}`)
runCommand(pm.pm, [pm.command, ...notificationPackages], 'Notification package install')
}

function runSync() {
const pm = getPMAndCommand()
const runner = splitRunnerCommand(pm.runner)
const displayCommand = formatRunnerCommand(pm.runner, ['cap', 'sync'])
log.info(`Running ${displayCommand}`)
runCommand(runner.command, [...runner.args, 'cap', 'sync'], 'Capacitor sync')
}

async function writeHelperFile(filePath: string, appId: string, serverUrl: string, force: boolean | undefined) {
const absolutePath = resolve(cwd(), filePath)
if (existsSync(absolutePath) && !force)
throw new Error(`${filePath} already exists. Re-run with --force to overwrite it.`)

mkdirSync(dirname(absolutePath), { recursive: true })
await writeFileAtomic(absolutePath, renderNotificationHelper(appId, serverUrl), { mode: 0o644 })
return absolutePath
}

export async function setupNotifications(appIdArg: string | undefined, options: NotificationSetupOptions) {
intro('Capgo native notifications setup')
const progress = spinner()

try {
const config = await getConfig()
const appId = appIdArg || getConfigAppId(config)
if (!appId)
throw new Error('Missing appId. Pass it as `notifications setup com.example.app` or set it in capacitor.config.')

const serverUrl = options.serverUrl || defaultApiHost
const helperFile = options.file || 'src/capgo-notifications.ts'

if (options.install !== false)
runInstall()

progress.start('Saving Capacitor notification config')
await updateConfigbyKey('CapgoNotifications', { appId, serverUrl })
progress.stop('Capacitor notification config saved')

const writtenPath = await writeHelperFile(helperFile, appId, serverUrl, options.force)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
log.success(`Created ${writtenPath}`)

if (options.sync !== false)
runSync()

log.info('Import setupCapgoNotifications(...) after your user is known, and pass your stable customer external ID.')
log.info('Then configure FCM/APNs in the Capgo app Notifications tab before sending production notifications.')
outro('Notifications setup done')
}
catch (error) {
progress.stop('Notifications setup failed')
log.error(formatError(error))
throw error
}
}

export { renderNotificationHelper }
4 changes: 4 additions & 0 deletions cloudflare_workers/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { app as bundle } from '../../supabase/functions/_backend/public/bundle/i
import { app as channel } from '../../supabase/functions/_backend/public/channel/index.ts'
import { app as check_cpu_usage } from '../../supabase/functions/_backend/public/check_cpu_usage.ts'
import { app as device } from '../../supabase/functions/_backend/public/device/index.ts'
import { app as notifications } from '../../supabase/functions/_backend/public/notifications/index.ts'
import { app as ok } from '../../supabase/functions/_backend/public/ok.ts'
import { app as pluginRegions } from '../../supabase/functions/_backend/public/plugin_regions.ts'
import { app as organization } from '../../supabase/functions/_backend/public/organization/index.ts'
Expand Down Expand Up @@ -74,6 +75,7 @@ import { app as stripe_event } from '../../supabase/functions/_backend/triggers/
import { app as webhook_delivery } from '../../supabase/functions/_backend/triggers/webhook_delivery.ts'
import { app as webhook_dispatcher } from '../../supabase/functions/_backend/triggers/webhook_dispatcher.ts'
import { createAllCatch, createHono } from '../../supabase/functions/_backend/utils/hono.ts'
import { processNativeNotificationQueueBatch } from '../../supabase/functions/_backend/utils/nativeNotificationSender.ts'
import { version } from '../../supabase/functions/_backend/utils/version.ts'

// Public API
Expand All @@ -85,6 +87,7 @@ app.route('/bundle', bundle)
app.route('/channel', channel)
app.route('/device', device)
app.route('/organization', organization)
app.route('/notifications', notifications)
app.route('/statistics', statistics)
app.route('/webhooks', webhooks)
app.route('/app', appEndpoint)
Expand Down Expand Up @@ -173,4 +176,5 @@ createAllCatch(appTriggers, functionNameTriggers)

export default {
fetch: app.fetch,
queue: processNativeNotificationQueueBatch,
}
81 changes: 78 additions & 3 deletions cloudflare_workers/api/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@
{
"binding": "DEVICE_INFO",
"dataset": "device_info"
},
{
"binding": "NOTIFICATION_REGISTRY",
"dataset": "notification_registry"
},
{
"binding": "NOTIFICATION_EVENTS",
"dataset": "notification_events"
}
],
"d1_databases": [
Expand All @@ -70,7 +78,24 @@
"database_name": "capgo_prod_storeapps",
"database_id": "81236a0c-db6e-454d-87da-944fa9bc100c"
}
]
],
"queues": {
"producers": [
{
"binding": "NOTIFICATION_QUEUE",
"queue": "capgo-native-notifications-prod"
}
],
"consumers": [
{
"queue": "capgo-native-notifications-prod",
"max_batch_size": 10,
"max_batch_timeout": 5,
"max_retries": 5,
"dead_letter_queue": "capgo-native-notifications-prod-dlq"
}
]
}
},
"preprod": {
"name": "capgo_api-preprod",
Expand Down Expand Up @@ -117,6 +142,14 @@
{
"binding": "DEVICE_INFO",
"dataset": "device_info"
},
{
"binding": "NOTIFICATION_REGISTRY",
"dataset": "notification_registry"
},
{
"binding": "NOTIFICATION_EVENTS",
"dataset": "notification_events"
}
],
"d1_databases": [
Expand All @@ -125,7 +158,24 @@
"database_name": "capgo_prod_storeapps",
"database_id": "81236a0c-db6e-454d-87da-944fa9bc100c"
}
]
],
"queues": {
"producers": [
{
"binding": "NOTIFICATION_QUEUE",
"queue": "capgo-native-notifications-preprod"
}
],
"consumers": [
{
"queue": "capgo-native-notifications-preprod",
"max_batch_size": 10,
"max_batch_timeout": 5,
"max_retries": 5,
"dead_letter_queue": "capgo-native-notifications-preprod-dlq"
}
]
}
},
"alpha": {
"name": "capgo_api-alpha",
Expand Down Expand Up @@ -172,6 +222,14 @@
{
"binding": "DEVICE_INFO",
"dataset": "device_info_alpha"
},
{
"binding": "NOTIFICATION_REGISTRY",
"dataset": "notification_registry_alpha"
},
{
"binding": "NOTIFICATION_EVENTS",
"dataset": "notification_events_alpha"
}
],
"d1_databases": [
Expand All @@ -180,7 +238,24 @@
"database_name": "capgo_prod_storeapps",
"database_id": "81236a0c-db6e-454d-87da-944fa9bc100c"
}
]
],
"queues": {
"producers": [
{
"binding": "NOTIFICATION_QUEUE",
"queue": "capgo-native-notifications-alpha"
}
],
"consumers": [
{
"queue": "capgo-native-notifications-alpha",
"max_batch_size": 10,
"max_batch_timeout": 5,
"max_retries": 5,
"dead_letter_queue": "capgo-native-notifications-alpha-dlq"
}
]
}
},
"local": {
"name": "capgo_api-local",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading