Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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.

130 changes: 130 additions & 0 deletions cli/src/notifications/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
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
tags?: string[]
attributes?: Record<string, unknown>
consent?: boolean
}

export async function setupCapgoNotifications(identity: CapgoNotificationIdentity) {
if (!identity.externalId)
return

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

return CapgoNotifications.register({
externalId: identity.externalId,
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.
50 changes: 49 additions & 1 deletion messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@
"6-characters-minimum": "6 characters minimum, 1 uppercase, 1 lowercase, 1 special character",
"90-days": "Last 90 Days",
"Bandwidth": "Bandwidth",
"badge": "Badge",
"Current": "Current",
"configured": "Configured",
"Filters": "Filters",
"MAU": "MAU",
"Storage": "Storage",
Expand Down Expand Up @@ -912,6 +914,7 @@
"dont-have-an-account": "Don't have an account?",
"downgrade": "Downgrade",
"download": "Download",
"draft": "Draft",
"download-csv": "Download CSV",
"edit-role": "Edit role",
"edit-webhook": "Edit Webhook",
Expand Down Expand Up @@ -1268,6 +1271,33 @@
"not-logged-in": "Not logged in",
"not-set": "Not set",
"notifications": "notifications",
"notification-action-error": "Notification action failed",
"notification-audience-json": "Audience JSON",
"notification-body": "Body",
"notification-campaign-name": "Campaign name",
"notification-campaigns": "Notification campaigns",
"notification-create-campaign": "Create campaign",
"notification-create-success": "Campaign created",
"notification-invalid-json": "Invalid JSON",
"notification-load-error": "Failed to load notifications",
"notification-lookup": "Lookup",
"notification-lookup-success": "Recipient loaded",
"notification-no-campaigns": "No campaigns yet",
"notification-no-devices": "No active devices found",
"notification-payload-json": "Payload JSON",
"notification-provider-config-json": "Provider config JSON",
"notification-provider-secret-ref": "Secret env name",
"notification-provider-setup": "Provider setup",
"notification-quick-send": "Test send",
"notification-recipient-external-id": "Customer external ID",
"notification-recipient-lookup": "Recipient lookup",
"notification-save-provider": "Save provider",
"notification-save-settings": "Save settings",
"notification-save-success": "Provider saved",
"notification-send-success": "Notification queued",
"notification-send-test": "Send test",
"notification-stats": "Notification stats",
"notification-title": "Notifications",
"notifications-activity": "Activity Notifications",
"notifications-billing-period-stats": "Billing period statistics",
"notifications-billing-period-stats-desc": "Receive usage statistics on your billing anniversary date with plan upgrade recommendations",
Expand Down Expand Up @@ -1967,5 +1997,23 @@
"restore-account": "Restore account",
"restoring-account": "Restoring account...",
"translation-not-ready": "Translation is being prepared. Try again in a bit.",
"translation-unavailable": "This language is not available right now."
"translation-unavailable": "This language is not available right now.",
"provider": "Provider",
"notification-configured-providers": "Configured providers",
"notification-events-30d": "Events in 30 days",
"notification-device-results": "Devices found",
"notification-active-providers": "Active providers",
"notification-no-providers": "No providers configured",
"notification-provider-secret-ref-placeholder": "NOTIFICATIONS_FCM_SERVICE_ACCOUNT",
"notification-message-title": "Title",
"notification-message-body": "Body",
"notification-no-stats": "No notification events yet",
"notification-push-update": "Push update",
"notification-push-update-enabled": "Enable push update",
"notification-update-install-mode": "Install mode",
"notification-update-install-next": "Next launch",
"notification-update-install-now": "Immediately",
"notification-push-update-now": "Push now",
"notification-settings-save-success": "Notification settings saved",
"notification-update-push-success": "Update push queued"
}
Loading
Loading