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

feat: add notification (un-)registration endpoints #128

Merged
merged 4 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@safe-global/safe-gateway-typescript-sdk",
"version": "3.9.0",
"version": "3.10.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
Expand Down
13 changes: 11 additions & 2 deletions src/endpoint.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { fetchData, insertParams, stringifyQuery } from './utils'
import type { GetEndpoint, paths, PostEndpoint, Primitive } from './types/api'
import { deleteData, fetchData, insertParams, stringifyQuery } from './utils'
import type { DeleteEndpoint, GetEndpoint, paths, PostEndpoint, Primitive } from './types/api'

function makeUrl(
baseUrl: string,
Expand Down Expand Up @@ -33,3 +33,12 @@ export function getEndpoint<T extends keyof paths>(
const url = makeUrl(baseUrl, path as string, params?.path, params?.query)
return fetchData(url)
}

export function deleteEndpoint<T extends keyof paths>(
baseUrl: string,
path: T,
params?: paths[T] extends DeleteEndpoint ? paths[T]['delete']['parameters'] : never,
): Promise<paths[T] extends DeleteEndpoint ? paths[T]['delete']['responses'][200]['schema'] : never> {
const url = makeUrl(baseUrl, path as string, params?.path)
return deleteData(url)
}
29 changes: 28 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getEndpoint, postEndpoint } from './endpoint'
import { deleteEndpoint, getEndpoint, postEndpoint } from './endpoint'
import type { operations } from './types/api'
import type {
SafeTransactionEstimation,
Expand Down Expand Up @@ -355,4 +355,31 @@ export function getDelegates(chainId: string, query: DelegatesRequest = {}): Pro
})
}

/**
* Registers a device/Safe for notifications
*/
export function registerDevice(body: operations['register_device']['parameters']['body']): Promise<void> {
return postEndpoint(baseUrl, '/v1/register/notifications', {
body,
})
}

/**
* Unregisters a Safe from notifications
*/
export function unregisterSafe(chainId: string, address: string, uuid: string): Promise<void> {
return deleteEndpoint(baseUrl, '/v1/chains/{chainId}/notifications/devices/{uuid}/safes/{safe_address}', {
path: { chainId, safe_address: address, uuid },
})
}

/**
* Unregisters a device from notifications
*/
export function unregisterDevice(chainId: string, uuid: string): Promise<void> {
return deleteEndpoint(baseUrl, '/v1/chains/{chainId}/notifications/devices/{uuid}', {
path: { chainId, uuid },
})
}

/* eslint-enable @typescript-eslint/explicit-module-boundary-types */
75 changes: 73 additions & 2 deletions src/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,15 @@ import type {
SafeMessageListPage,
} from './safe-messages'
import type { DelegateResponse, DelegatesRequest } from './delegates'
import type { RegisterNotificationsRequest } from './notifications'

export type Primitive = string | number | boolean | null

interface GetParams {
interface DeleteParams {
path?: { [key: string]: Primitive }
}

interface GetParams extends DeleteParams {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels a little weird. I would define a common type that both GetParams and DeleteParams extend.

query?: { [key: string]: Primitive }
}

Expand Down Expand Up @@ -64,8 +68,15 @@ export interface PostEndpoint extends Endpoint {
}
}

export interface DeleteEndpoint extends Endpoint {
delete: {
parameters: DeleteParams | null
responses: Responses
}
}

interface PathRegistry {
[key: string]: GetEndpoint | PostEndpoint | (GetEndpoint & PostEndpoint)
[key: string]: DeleteEndpoint | GetEndpoint | PostEndpoint | (GetEndpoint & PostEndpoint)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add it to the end of the list, not the beginning?

}

export interface paths extends PathRegistry {
Expand Down Expand Up @@ -270,6 +281,29 @@ export interface paths extends PathRegistry {
query: DelegatesRequest
}
}
'/v1/register/notifications': {
post: operations['register_device']
parameters: null
}
'/v1/chains/{chainId}/notifications/devices/{uuid}/safes/{safe_address}': {
delete: operations['unregister_safe']
parameters: {
path: {
uuid: string
chainId: string
safe_address: string
}
}
}
'/v1/chains/{chainId}/notifications/devices/{uuid}': {
delete: operations['unregister_device']
parameters: {
path: {
uuid: string
chainId: string
}
}
}
}

export interface operations {
Expand Down Expand Up @@ -689,4 +723,41 @@ export interface operations {
}
}
}
register_device: {
parameters: {
body: RegisterNotificationsRequest
}
responses: {
200: {
schema: void
}
}
}
unregister_safe: {
parameters: {
path: {
uuid: string
chainId: string
safe_address: string
}
}
responses: {
200: {
schema: void
}
}
}
unregister_device: {
parameters: {
path: {
uuid: string
chainId: string
}
}
responses: {
200: {
schema: void
}
}
}
}
22 changes: 22 additions & 0 deletions src/types/notifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export enum DeviceType {
ANDROID = 'ANDROID',
IOS = 'IOS',
WEB = 'WEB',
}

type SafeRegistration = {
chainId: string
safes: Array<string>
signatures: Array<string>
}

export type RegisterNotificationsRequest = {
uuid?: string
cloudMessagingToken: string
buildNumber: string
bundle: string
deviceType: DeviceType
version: string
timestamp?: string
safeRegistrations: Array<SafeRegistration>
}
39 changes: 27 additions & 12 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,25 @@ export function stringifyQuery(query?: Params): string {
return searchString ? `?${searchString}` : ''
}

async function parseResponse<T>(resp: Response): Promise<T> {
let json

try {
json = await resp.json()
} catch {
if (resp.headers && resp.headers.get('content-length') !== '0') {
throw new Error(`Invalid response content: ${resp.statusText}`)
}
}

if (!resp.ok) {
const errTxt = isErrorResponse(json) ? `${json.code}: ${json.message}` : resp.statusText
throw new Error(errTxt)
}

return json
}

export async function fetchData<T>(url: string, body?: unknown): Promise<T> {
let options:
| {
Expand All @@ -56,20 +75,16 @@ export async function fetchData<T>(url: string, body?: unknown): Promise<T> {
}

const resp = await fetch(url, options)
let json

try {
json = await resp.json()
} catch {
if (resp.headers && resp.headers.get('content-length') !== '0') {
throw new Error(`Invalid response content: ${resp.statusText}`)
}
}
return parseResponse<T>(resp)
}

if (!resp.ok) {
const errTxt = isErrorResponse(json) ? `${json.code}: ${json.message}` : resp.statusText
throw new Error(errTxt)
export async function deleteData<T>(url: string): Promise<T> {
const options = {
method: 'DELETE',
}

return json
const resp = await fetch(url, options)

return parseResponse<T>(resp)
}
Loading