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: sdk: add setHeaders for setting global headers and make sure all client calls have a headers arg #2697

Merged
merged 8 commits into from
May 13, 2024
7 changes: 7 additions & 0 deletions .changeset/spicy-trains-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@nhost/hasura-storage-js': minor
'@nhost/graphql-js': minor
'@nhost/nhost-js': minor
---

feat: add `setHeaders` method enabling global configuration of storage, graphql, and functions client headers, alongside added support for passing specific headers with individual calls
22 changes: 20 additions & 2 deletions packages/graphql-js/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export class NhostGraphqlClient {
readonly _url: string
private accessToken: string | null
private adminSecret?: string
private headers?: Record<string, string>

constructor(params: NhostGraphqlConstructorParams) {
const { url, adminSecret } = params
Expand Down Expand Up @@ -101,7 +102,7 @@ export class NhostGraphqlClient {
const [variables, config] = variablesAndRequestHeaders
const requestOptions = parseRequestArgs(documentOrOptions, variables, config)

const { headers, ...otherOptions } = config || {}
const { headers: extraHeaders, ...otherOptions } = config || {}
const { query, operationName } = resolveRequestDocument(requestOptions.document)

if (typeof process !== 'undefined' && !process.env.TEST_MODE) {
Expand All @@ -120,7 +121,8 @@ export class NhostGraphqlClient {
headers: {
'Content-Type': 'application/json',
...this.generateAccessTokenHeaders(),
...headers
...this.headers, // graphql client headers to be sent with all `request` calls
...extraHeaders // extra headers to be sent with a specific call
},
...otherOptions
})
Expand Down Expand Up @@ -228,6 +230,22 @@ export class NhostGraphqlClient {
this.accessToken = accessToken
}

/**
* Use `nhost.graphql.setHeaders` to set global headers to be sent in all subsequent graphql requests
*
* @example
* ```ts
* nhost.graphql.setHeaders({
* 'x-hasura-role': 'admin'
* })
* ```
*
* @docs https://docs.nhost.io/reference/javascript/graphql/set-headers
*/
setHeaders(headers?: Record<string, string>) {
this.headers = headers
}

private generateAccessTokenHeaders(): NhostGraphqlRequestConfig['headers'] {
if (this.adminSecret) {
return {
Expand Down
60 changes: 46 additions & 14 deletions packages/hasura-storage-js/src/hasura-storage-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,25 @@ export class HasuraStorageApi {
private url: string
private accessToken?: string
private adminSecret?: string
private headers?: Record<string, string>

constructor({ url }: { url: string }) {
this.url = url
}

async uploadFormData({
formData,
headers,
bucketId
bucketId,
headers: extraHeaders
}: StorageUploadFormDataParams): Promise<StorageUploadFormDataResponse> {
const { error, fileMetadata } = await fetchUpload(this.url, formData, {
accessToken: this.accessToken,
adminSecret: this.adminSecret,
bucketId,
headers
headers: {
...this.headers, // global nhost storage client headers to be sent with all `uploadFormData` calls
...extraHeaders // extra headers to be sent with a specific call
},
accessToken: this.accessToken,
adminSecret: this.adminSecret
})

if (error) {
Expand All @@ -67,7 +71,8 @@ export class HasuraStorageApi {
file,
bucketId,
id,
name
name,
headers: extraHeaders
}: StorageUploadFileParams): Promise<StorageUploadFileResponse> {
const formData = typeof window === 'undefined' ? new LegacyFormData() : new FormData()

Expand All @@ -79,7 +84,11 @@ export class HasuraStorageApi {
adminSecret: this.adminSecret,
bucketId,
fileId: id,
name
name,
headers: {
...this.headers, // global nhost storage client headers to be sent with all `uploadFile` calls
...extraHeaders // extra headers to be sent with a specific call
}
})

if (error) {
Expand All @@ -98,7 +107,7 @@ export class HasuraStorageApi {

async downloadFile(params: StorageDownloadFileParams): Promise<StorageDownloadFileResponse> {
try {
const { fileId, headers: customHeaders = {}, ...imageTransformationParams } = params
const { fileId, headers: extraHeaders, ...imageTransformationParams } = params

const urlWithParams = appendImageTransformationParameters(
`${this.url}/files/${fileId}`,
Expand All @@ -107,7 +116,11 @@ export class HasuraStorageApi {

const response = await fetch(urlWithParams, {
method: 'GET',
headers: {...this.generateAuthHeaders(), ...customHeaders}
headers: {
...this.generateAuthHeaders(),
...this.headers, // global nhost storage client headers to be sent with all `downloadFile` calls
...extraHeaders // extra headers to be sent with a specific call
}
})

if (!response.ok) {
Expand All @@ -124,11 +137,15 @@ export class HasuraStorageApi {

async getPresignedUrl(params: ApiGetPresignedUrlParams): Promise<ApiGetPresignedUrlResponse> {
try {
const { fileId } = params
const { fileId, headers: extraHeaders } = params

const response = await fetch(`${this.url}/files/${fileId}/presignedurl`, {
method: 'GET',
headers: this.generateAuthHeaders()
headers: {
...this.generateAuthHeaders(),
...this.headers, // global nhost storage client headers to be sent with all `getPresignedUrl` calls
...extraHeaders // extra headers to be sent with a specific call
}
})
if (!response.ok) {
throw new Error(await response.text())
Expand All @@ -142,10 +159,14 @@ export class HasuraStorageApi {

async delete(params: ApiDeleteParams): Promise<ApiDeleteResponse> {
try {
const { fileId } = params
const { fileId, headers: extraHeaders } = params
const response = await fetch(`${this.url}/files/${fileId}`, {
method: 'DELETE',
headers: this.generateAuthHeaders()
headers: {
...this.generateAuthHeaders(),
...this.headers, // global nhost storage client headers to be sent with all `delete` calls
...extraHeaders // extra headers to be sent with a specific call
}
})
if (!response.ok) {
throw new Error(await response.text())
Expand Down Expand Up @@ -180,6 +201,17 @@ export class HasuraStorageApi {
return this
}

/**
* Set global headers to be sent with all requests.
*
* @param headers a key value pair headers object
* @returns Hasura Storage API instance
*/
setHeaders(headers?: Record<string, string>): HasuraStorageApi {
this.headers = headers
return this
}

private generateAuthHeaders(): HeadersInit | undefined {
if (!this.adminSecret && !this.accessToken) {
return undefined
Expand Down
20 changes: 20 additions & 0 deletions packages/hasura-storage-js/src/hasura-storage-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,24 @@ export class HasuraStorageClient {

return this
}

/**
* Use `nhost.storage.setHeaders` to set global headers to be sent for all subsequent storage requests.
*
* @example
* ```ts
* nhost.storage.setHeaders({
* 'x-hasura-role': 'admin'
* })
* ```
*
* @param headers key value headers object
*
* @docs https://docs.nhost.io/reference/javascript/storage/set-headers
*/
setHeaders(headers?: Record<string, string>): HasuraStorageClient {
this.api.setHeaders(headers)

return this
}
}
21 changes: 11 additions & 10 deletions packages/hasura-storage-js/src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,22 @@ export interface FileUploadConfig {
adminSecret?: string
}

export interface StorageHeadersParam {
headers?: Record<string, string>
}

// works only in browser. Used for for hooks
export interface StorageUploadFileParams {
export interface StorageUploadFileParams extends StorageHeadersParam {
file: File
id?: string
name?: string
bucketId?: string
}

// works in browser and server
export interface StorageUploadFormDataParams {
export interface StorageUploadFormDataParams extends StorageHeadersParam {
formData: FormData | LegacyFormData
bucketId?: string
headers?: Record<string, string>
}

// works in browser and server
Expand All @@ -53,12 +56,10 @@ export type StorageUploadFormDataResponse =

export type StorageUploadResponse = StorageUploadFileResponse | StorageUploadFormDataResponse

export interface StorageDownloadFileParams extends StorageImageTransformationParams {
export interface StorageDownloadFileParams
extends StorageImageTransformationParams,
StorageHeadersParam {
fileId: string
/**
* Optional headers to be sent with the request
*/
headers?: Record<string, string>
}

export type StorageDownloadFileResponse = { file: Blob; error: null } | { file: null; error: Error }
Expand Down Expand Up @@ -111,15 +112,15 @@ export interface FileResponse {

// TODO not implemented yet in hasura-storage
// export interface ApiGetPresignedUrlParams extends StorageImageTransformationParams {
export interface ApiGetPresignedUrlParams {
export interface ApiGetPresignedUrlParams extends StorageHeadersParam {
fileId: string
}

export type ApiGetPresignedUrlResponse =
| { presignedUrl: { url: string; expiration: number }; error: null }
| { presignedUrl: null; error: Error }

export interface ApiDeleteParams {
export interface ApiDeleteParams extends StorageHeadersParam {
fileId: string
}

Expand Down
24 changes: 20 additions & 4 deletions packages/nhost-js/src/clients/functions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ import {
*/
export function createFunctionsClient(params: NhostClientConstructorParams) {
const functionsUrl =
'subdomain' in params
? urlFromSubdomain(params, 'functions')
: params.functionsUrl
'subdomain' in params ? urlFromSubdomain(params, 'functions') : params.functionsUrl

if (!functionsUrl) {
throw new Error('Please provide `subdomain` or `functionsUrl`.')
Expand All @@ -29,6 +27,7 @@ export class NhostFunctionsClient {
readonly url: string
private accessToken: string | null
private adminSecret?: string
private headers?: Record<string, string>

constructor(params: NhostFunctionsConstructorParams) {
const { url, adminSecret } = params
Expand Down Expand Up @@ -87,7 +86,8 @@ export class NhostFunctionsClient {
const headers: HeadersInit = {
'Content-Type': 'application/json',
...this.generateAccessTokenHeaders(),
...config?.headers
...config?.headers,
...this.headers // nhost functions client headers to be sent with all calls
}

const fullUrl = buildUrl(this.url, url)
Expand Down Expand Up @@ -162,6 +162,22 @@ export class NhostFunctionsClient {
this.accessToken = accessToken
}

/**
* Use `nhost.functions.setHeaders` to a set global headers to be sent in all subsequent functions requests.
*
* @example
* ```ts
* nhost.functions.setHeaders({
* 'x-hasura-role': 'admin'
* })
* ```
*
* @docs https://docs.nhost.io/reference/javascript/nhost-js/functions/set-headers
*/
setHeaders(headers?: Record<string, string>) {
this.headers = headers
}

generateAccessTokenHeaders(): NhostFunctionCallConfig['headers'] {
if (this.adminSecret) {
return {
Expand Down
16 changes: 16 additions & 0 deletions packages/nhost-js/src/clients/nhost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,20 @@ export class NhostClient {
// this.functions.setAdminSecret(newValue)
// this.graphql.setAdminSecret(newValue)
}

/**
* Use `nhost.setRole` to set the user role for all subsequent graphql, storage and functions calls
onehassan marked this conversation as resolved.
Show resolved Hide resolved
*
* @example
* ```ts
* nhost.setRole('admin')
* ```
*
* @docs https://docs.nhost.io/reference/javascript/set-role
*/
setRole(role: string) {
this.graphql.setHeaders({ 'x-hasura-role': role })
onehassan marked this conversation as resolved.
Show resolved Hide resolved
this.storage.setHeaders({ 'x-hasura-role': role })
this.functions.setHeaders({ 'x-hasura-role': role })
}
}
Loading