Skip to content

Commit

Permalink
Version user agent (#71)
Browse files Browse the repository at this point in the history
* Updated docs on Recurring types

* Add version to user agent
  • Loading branch information
tomas-zijdemans-vipps authored Oct 14, 2024
1 parent 12f66ba commit 1babb0d
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 145 deletions.
9 changes: 7 additions & 2 deletions src/base_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@ import { fetchRetry } from "./fetch.ts";
* @param {ClientConfig} cfg - The client configuration.
* @returns {BaseClient} The base client.
*/
export const baseClient = (cfg: ClientConfig): BaseClient =>
export const baseClient = (cfg: ClientConfig, sdkVersion: string): BaseClient =>
({
/**
* The version of the SDK.
* @type {string}
*/
sdkVersion,
/**
* Makes a request to the server.
*
Expand All @@ -34,7 +39,7 @@ export const baseClient = (cfg: ClientConfig): BaseClient =>
}

// Build the request
const request = buildRequest(cfg, requestData);
const request = buildRequest(cfg, requestData, this.sdkVersion);

// Make the request with retry logic
const response = await fetchRetry<TOk, TErr>(
Expand Down
134 changes: 38 additions & 96 deletions src/base_client_helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,23 @@ import type {
import type { ClientConfig } from "./types_external.ts";
import { uuid } from "./deps.ts";

/**
* Builds a Request object based on the provided configuration and request data.
*
* @param {ClientConfig} cfg - The client configuration.
* @param {RequestData<unknown, unknown>} requestData - The request data containing method, headers, token, body, and URL.
* @returns {Request} A Request object.
*/
export const buildRequest = (
cfg: ClientConfig,
requestData: RequestData<unknown, unknown>,
sdkVersion: string = "unknown",
): Request => {
const baseURL = cfg.useTestMode
? "https://apitest.vipps.no"
: "https://api.vipps.no";

const userAgent = getUserAgent(sdkVersion, import.meta.url);

const reqInit: RequestInit = {
method: requestData.method,
headers: getHeaders(
headers: buildHeaders(
cfg,
requestData.token,
userAgent,
requestData.additionalHeaders,
requestData.omitHeaders,
),
Expand All @@ -34,25 +31,17 @@ export const buildRequest = (
return new Request(`${baseURL}${requestData.url}`, reqInit);
};

/**
* Returns a headers object based on the provided client configuration.
*
* @param {ClientConfig} cfg - The client configuration.
* @param {string} [token] - The token to use in the Authorization header.
* @param {Record<string, string>} [additionalHeaders] - Additional headers to include, these will not override default headers.
* @param {OmitHeaders} [omitHeaders=[]] - Headers to omit from the returned object.
* @returns {Record<string, string>} A headers object.
*/
export const getHeaders = (
export const buildHeaders = (
cfg: ClientConfig,
token?: string,
userAgent: string = "unknown",
additionalHeaders?: Record<string, string>,
omitHeaders: OmitHeaders = [],
): Record<string, string> => {
const defaultHeaders: DefaultHeaders = {
"Content-Type": "application/json",
"Authorization": `Bearer ${token || ""}`,
"User-Agent": getUserAgent(),
"User-Agent": userAgent,
"Ocp-Apim-Subscription-Key": cfg.subscriptionKey,
"Merchant-Serial-Number": cfg.merchantSerialNumber,
"Vipps-System-Name": cfg.systemName || "",
Expand All @@ -62,16 +51,17 @@ export const getHeaders = (
"Idempotency-Key": uuid.generate(),
};

return createHeaders(defaultHeaders, omitHeaders, additionalHeaders);
const combinedHeaders: Record<string, string> = {
...additionalHeaders,
...defaultHeaders,
};

if (omitHeaders.length === 0) {
return combinedHeaders;
}
return filterHeaders(combinedHeaders, omitHeaders);
};

/**
* Filters out specified headers from the default headers.
*
* @param {Record<string, string>} headers - The headers object to filter.
* @param {string[]} omitHeaders - The list of headers to omit.
* @returns {Record<string, string>} The filtered headers object.
*/
export const filterHeaders = (
headers: Record<string, string>,
omitHeaders: string[],
Expand All @@ -81,78 +71,30 @@ export const filterHeaders = (
);
};

/**
* Adds additional headers to the default headers without overwriting existing headers.
*
* @param {Record<string, string>} defaultHeaders - The default headers object.
* @param {Record<string, string>} additionalHeaders - The additional headers to add.
* @returns {Record<string, string>} The combined headers object.
*/
export const addHeaders = (
defaultHeaders: Record<string, string>,
additionalHeaders: Record<string, string>,
): Record<string, string> => {
return { ...additionalHeaders, ...defaultHeaders };
};

/**
* Creates a new headers object by omitting specified headers from the default headers
* and adding additional headers.
*
* @param {Record<string, string>} defaultHeaders - The default headers object.
* @param {string[]} omitHeaders - The list of headers to omit.
* @param {Record<string, string>} [additionalHeaders={}] - The additional headers to add.
* @returns {Record<string, string>} The new headers object.
*/
export const createHeaders = (
defaultHeaders: Record<string, string>,
omitHeaders: string[],
additionalHeaders: Record<string, string> = {},
): Record<string, string> => {
const combinedHeaders = addHeaders(defaultHeaders, additionalHeaders);
return filterHeaders(combinedHeaders, omitHeaders);
};

/**
* Returns the user agent string for the client.
*
* @returns {string} The user agent string.
*/
export const getUserAgent = (): string => {
// If the sdk is loaded using require, import.meta.url will be undefined
const metaUrl: string | undefined = import.meta.url;
export const getUserAgent = (
version: string,
moduleURL: string | undefined,
): string => {
if (!moduleURL) {
return `Vipps/Deno-SDK/npm-require/${version}`;
}

const userAgent = createSDKUserAgent(metaUrl);
return userAgent;
return `Vipps/Deno-SDK/${getModuleSource(moduleURL)}/${version}`;
};

/**
* Creates a user agent string based on the provided meta URL.
* The function is meant to receive import.meta.url (that will returns the URL of the current module).
* Read more in the Deno docs in Import Meta
*
* @param {string | undefined} metaUrl - The meta URL of the module.
* @returns {string} The user agent string.
*/
export const createSDKUserAgent = (metaUrl: string | undefined): string => {
if (!metaUrl) {
return "Vipps/Deno SDK/npm-require";
}

const url = new URL(metaUrl);
export const getModuleSource = (moduleUrl: string): string => {
const url = new URL(moduleUrl);

// Check if the module was loaded from deno.land
if (
url.host === "deno.land" &&
url.pathname.includes("vipps_mobilepay_sdk")
) {
// Extract the module version from the URL
const sdkVersion = url.pathname.split("@")[1].split("/")[0];
return `Vipps/Deno SDK/${sdkVersion}`;
} // Or if the module was loaded from npm
else if (url.pathname.includes("node_modules")) {
return `Vipps/Deno SDK/npm-module`;
switch (true) {
case url.pathname.includes("node_modules"):
return "npm-module";
case url.host === "npm.jsr.io":
return "jsr-npm";
case url.host === "jsr.io":
return "jsr";
case url.host === "deno.land":
return "deno-land";
default:
return "unknown";
}
// Otherwise, we don't know where the module was loaded from
return `Vipps/Deno SDK/unknown`;
};
6 changes: 3 additions & 3 deletions src/generated_types/recurring/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1543,7 +1543,7 @@ export type ParameterChargeStatusQuery = ChargeStatus;
export type ParameterChargeStatusQueryV3 = ChargeStatus;

/**
* Page number for paginating.
* Page number for paginating (should be used in combination with pageSize).
*/
export type ParameterPageNumberQuery = number;

Expand Down Expand Up @@ -1948,7 +1948,7 @@ export type ListAgreementsV3Data = {
*/
ocpApimSubscriptionKey: string;
/**
* Page number for paginating.
* Page number for paginating (should be used in combination with pageSize).
*/
pageNumber?: number;
/**
Expand Down Expand Up @@ -3412,7 +3412,7 @@ export type $OpenApiTs = {
/**
* None, some or all charges passed API level validation.
*/
202: Array<CreateChargeAsyncV3>;
202: AsyncChargeResponse;
};
};
};
Expand Down
5 changes: 4 additions & 1 deletion src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ export type * from "./types_external.ts";
* ```
*/
export const Client = (options: ClientConfig): SDKClient => {
// Add the SDK version to the client configuration
const version = "2.4.1";

// Create the base client
const client = baseClient(options);
const client = baseClient(options, version);

// Proxify the base client with the API request factories
return proxifyClient(client);
Expand Down
3 changes: 2 additions & 1 deletion src/types_internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ export type ClientResponse<TOk, TErr> =
| (SDKError<TErr> & { retry?: boolean });

/**
* Represents the base client with a method to make requests.
* Represents a base client for making requests.
*/
export type BaseClient = {
readonly sdkVersion: string;
readonly makeRequest: (
requestData: RequestData<unknown, unknown>,
) => Promise<ClientResponse<unknown, unknown>>;
Expand Down
10 changes: 8 additions & 2 deletions tests/api_proxy_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { assertEquals } from "@std/assert";
import type { RequestData } from "../src/types_internal.ts";

Deno.test("proxifyFactory - Should return a Proxy object with method", () => {
const client = baseClient({ merchantSerialNumber: "", subscriptionKey: "" });
const client = baseClient({
merchantSerialNumber: "",
subscriptionKey: "",
}, "1.0.0");

const factory = {
foo(): RequestData<unknown, unknown> {
Expand All @@ -21,7 +24,10 @@ Deno.test("proxifyFactory - Should return a Proxy object with method", () => {
});

Deno.test("proxifyFactory - Should return the original property if it is not a function", () => {
const client = baseClient({ merchantSerialNumber: "", subscriptionKey: "" });
const client = baseClient({
merchantSerialNumber: "",
subscriptionKey: "",
}, "1.0.0");

const factory = {
foo(): RequestData<unknown, unknown> {
Expand Down
Loading

0 comments on commit 1babb0d

Please sign in to comment.