2026-02-17 11:28:41 -08:00
#!/usr/bin/env node
const REST _API _KEY = process . env . ONESIGNAL _REST _API _KEY
const APP _ID = process . env . ONESIGNAL _APP _ID
const BASE _URL = 'https://api.onesignal.com'
if ( ! REST _API _KEY ) {
console . error ( JSON . stringify ( { error : 'ONESIGNAL_REST_API_KEY environment variable required' } ) )
process . exit ( 1 )
}
if ( ! APP _ID ) {
console . error ( JSON . stringify ( { error : 'ONESIGNAL_APP_ID environment variable required' } ) )
process . exit ( 1 )
}
async function api ( method , path , body ) {
2026-02-17 11:47:12 -08:00
const headers = {
2026-02-17 22:40:03 -08:00
'Authorization' : ` Basic ${ REST _API _KEY } ` ,
2026-02-17 11:47:12 -08:00
'Content-Type' : 'application/json' ,
'Accept' : 'application/json' ,
}
if ( args [ 'dry-run' ] ) {
return { _dry _run : true , method , url : ` ${ BASE _URL } ${ path } ` , headers : { ... headers , Authorization : '***' } , body : body || undefined }
}
2026-02-17 11:28:41 -08:00
const res = await fetch ( ` ${ BASE _URL } ${ path } ` , {
method ,
2026-02-17 11:47:12 -08:00
headers ,
2026-02-17 11:28:41 -08:00
body : body ? JSON . stringify ( body ) : undefined ,
} )
const text = await res . text ( )
try {
return JSON . parse ( text )
} catch {
return { status : res . status , body : text }
}
}
function parseArgs ( args ) {
const result = { _ : [ ] }
for ( let i = 0 ; i < args . length ; i ++ ) {
const arg = args [ i ]
if ( arg . startsWith ( '--' ) ) {
const key = arg . slice ( 2 )
const next = args [ i + 1 ]
if ( next && ! next . startsWith ( '--' ) ) {
result [ key ] = next
i ++
} else {
result [ key ] = true
}
} else {
result . _ . push ( arg )
}
}
return result
}
const args = parseArgs ( process . argv . slice ( 2 ) )
const [ cmd , sub , ... rest ] = args . _
async function main ( ) {
let result
const limit = args . limit ? Number ( args . limit ) : 50
const offset = args . offset ? Number ( args . offset ) : 0
switch ( cmd ) {
case 'notifications' :
switch ( sub ) {
case 'send' : {
const message = args . message
if ( ! message ) { result = { error : '--message required' } ; break }
const payload = {
app _id : APP _ID ,
contents : { en : message } ,
}
if ( args . heading ) payload . headings = { en : args . heading }
if ( args . url ) payload . url = args . url
if ( args . data ) {
try { payload . data = JSON . parse ( args . data ) } catch { payload . data = { value : args . data } }
}
if ( args . segment ) {
payload . included _segments = args . segment . split ( ',' )
} else if ( args . emails ) {
payload . include _email _tokens = args . emails . split ( ',' )
} else if ( args [ 'player-ids' ] ) {
payload . include _player _ids = args [ 'player-ids' ] . split ( ',' )
} else if ( args . aliases ) {
try {
payload . include _aliases = JSON . parse ( args . aliases )
} catch {
payload . include _aliases = { external _id : args . aliases . split ( ',' ) }
}
payload . target _channel = args . channel || 'push'
} else {
payload . included _segments = [ 'Subscribed Users' ]
}
if ( args [ 'send-after' ] ) payload . send _after = args [ 'send-after' ]
if ( args . ttl ) payload . ttl = Number ( args . ttl )
result = await api ( 'POST' , '/api/v1/notifications' , payload )
break
}
case 'list' : {
result = await api ( 'GET' , ` /api/v1/notifications?app_id= ${ APP _ID } &limit= ${ limit } &offset= ${ offset } ` )
break
}
case 'get' : {
const id = args . id
if ( ! id ) { result = { error : '--id required' } ; break }
result = await api ( 'GET' , ` /api/v1/notifications/ ${ id } ?app_id= ${ APP _ID } ` )
break
}
case 'cancel' : {
const id = args . id
if ( ! id ) { result = { error : '--id required' } ; break }
result = await api ( 'DELETE' , ` /api/v1/notifications/ ${ id } ?app_id= ${ APP _ID } ` )
break
}
default :
result = { error : 'Unknown notifications subcommand. Use: send, list, get, cancel' }
}
break
case 'segments' :
switch ( sub ) {
case 'list' : {
result = await api ( 'GET' , ` /api/v1/apps/ ${ APP _ID } /segments?offset= ${ offset } &limit= ${ limit } ` )
break
}
case 'create' : {
const name = args . name
if ( ! name ) { result = { error : '--name required' } ; break }
2026-02-17 22:44:06 -08:00
let filters
try { filters = args . filters ? JSON . parse ( args . filters ) : [ { field : 'session_count' , relation : '>' , value : '0' } ] } catch { result = { error : 'Invalid JSON in --filters' } ; break }
2026-02-17 11:28:41 -08:00
result = await api ( 'POST' , ` /api/v1/apps/ ${ APP _ID } /segments ` , { name , filters } )
break
}
case 'delete' : {
const id = args . id
if ( ! id ) { result = { error : '--id required' } ; break }
result = await api ( 'DELETE' , ` /api/v1/apps/ ${ APP _ID } /segments/ ${ id } ` )
break
}
default :
result = { error : 'Unknown segments subcommand. Use: list, create, delete' }
}
break
case 'users' :
switch ( sub ) {
case 'get' : {
const aliasLabel = args [ 'alias-label' ] || 'external_id'
const aliasId = args [ 'alias-id' ]
if ( ! aliasId ) { result = { error : '--alias-id required' } ; break }
result = await api ( 'GET' , ` /api/v1/apps/ ${ APP _ID } /users/by/ ${ aliasLabel } / ${ aliasId } ` )
break
}
case 'create' : {
const payload = { }
if ( args [ 'external-id' ] ) {
payload . identity = { external _id : args [ 'external-id' ] }
}
if ( args . email ) {
payload . subscriptions = [ { type : 'Email' , token : args . email } ]
}
if ( args . tags ) {
try { payload . tags = JSON . parse ( args . tags ) } catch { result = { error : 'Invalid --tags JSON' } ; break }
}
result = await api ( 'POST' , ` /api/v1/apps/ ${ APP _ID } /users ` , payload )
break
}
case 'delete' : {
const aliasLabel = args [ 'alias-label' ] || 'external_id'
const aliasId = args [ 'alias-id' ]
if ( ! aliasId ) { result = { error : '--alias-id required' } ; break }
result = await api ( 'DELETE' , ` /api/v1/apps/ ${ APP _ID } /users/by/ ${ aliasLabel } / ${ aliasId } ` )
break
}
default :
result = { error : 'Unknown users subcommand. Use: get, create, delete' }
}
break
case 'templates' :
switch ( sub ) {
case 'list' : {
result = await api ( 'GET' , ` /api/v1/templates?app_id= ${ APP _ID } &limit= ${ limit } &offset= ${ offset } ` )
break
}
case 'get' : {
const id = args . id
if ( ! id ) { result = { error : '--id required' } ; break }
result = await api ( 'GET' , ` /api/v1/templates/ ${ id } ?app_id= ${ APP _ID } ` )
break
}
case 'create' : {
const name = args . name
if ( ! name ) { result = { error : '--name required' } ; break }
const payload = { app _id : APP _ID , name }
if ( args . message ) payload . contents = { en : args . message }
if ( args . heading ) payload . headings = { en : args . heading }
result = await api ( 'POST' , '/api/v1/templates' , payload )
break
}
default :
result = { error : 'Unknown templates subcommand. Use: list, get, create' }
}
break
case 'app' :
switch ( sub ) {
case 'get' : {
result = await api ( 'GET' , ` /api/v1/apps/ ${ APP _ID } ` )
break
}
default :
result = { error : 'Unknown app subcommand. Use: get' }
}
break
default :
result = {
error : 'Unknown command' ,
usage : {
notifications : 'notifications [send --message <msg> --segment <s> | list | get --id <id> | cancel --id <id>]' ,
segments : 'segments [list | create --name <n> --filters <json> | delete --id <id>]' ,
users : 'users [get --alias-id <id> | create --external-id <id> --email <e> | delete --alias-id <id>]' ,
templates : 'templates [list | get --id <id> | create --name <n> --message <msg>]' ,
app : 'app [get]' ,
options : '--limit <n> --offset <n>' ,
}
}
}
console . log ( JSON . stringify ( result , null , 2 ) )
}
main ( ) . catch ( err => {
console . error ( JSON . stringify ( { error : err . message } ) )
process . exit ( 1 )
} )