Skip to content

Commit

Permalink
Allow reading unknown Enum values and increase unit testing coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
alexanderjordanbaker committed Nov 15, 2023
1 parent 4e2d707 commit 60fae07
Show file tree
Hide file tree
Showing 42 changed files with 596 additions and 204 deletions.
46 changes: 28 additions & 18 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,14 @@ export { AppTransaction } from './models/AppTransaction'
import jsonwebtoken = require('jsonwebtoken');
import { NotificationHistoryRequest } from './models/NotificationHistoryRequest';
import { NotificationHistoryResponse, NotificationHistoryResponseValidator } from './models/NotificationHistoryResponse';
import { URLSearchParams } from 'url';

export class AppStoreServerAPIClient {
private static PRODUCTION_URL = "https://api.storekit.itunes.apple.com";
private static SANDBOX_URL = "https://api.storekit-sandbox.itunes.apple.com";
private static USER_AGENT = "app-store-server-library/node/0.1";

private issueId: string
private issuerId: string
private keyId: string
private signingKey: string
private bundleId: string
Expand All @@ -97,31 +98,32 @@ export class AppStoreServerAPIClient {
* @param environment The environment to target
*/
public constructor(signingKey: string, keyId: string, issuerId: string, bundleId: string, environment: Environment) {
this.issueId = issuerId
this.issuerId = issuerId
this.keyId = keyId
this.bundleId = bundleId
this.signingKey = signingKey
this.urlBase = environment === "Sandbox" ? AppStoreServerAPIClient.SANDBOX_URL : AppStoreServerAPIClient.PRODUCTION_URL
}

protected async makeRequest<T>(path: string, method: string, queryParameters: { [key: string]: [string]}, body: object | null, validator: Validator<T> | null): Promise<T> {
protected async makeRequest<T>(path: string, method: string, queryParameters: { [key: string]: string[]}, body: object | null, validator: Validator<T> | null): Promise<T> {
const headers: { [key: string]: string } = {
'User-Agent': AppStoreServerAPIClient.USER_AGENT,
'Authorization': 'Bearer ' + this.createBearerToken(),
'Accept': 'application/json',
}
const parsedQueryParameters = new URLSearchParams(queryParameters)
const parsedQueryParameters = new URLSearchParams()
for (const queryParam in queryParameters) {
for (const queryVal of queryParameters[queryParam]) {
parsedQueryParameters.append(queryParam, queryVal)
}
}
let stringBody = undefined
if (body != null) {
stringBody = JSON.stringify(body)
headers['Content-Type'] = 'application/json'
}

const response = await fetch(this.urlBase + path + '?' + parsedQueryParameters, {
method: method,
body: stringBody,
headers: headers
})
const response = await this.makeFetchRequest(path, parsedQueryParameters, method, stringBody, headers)

if(response.ok) {
// Success
Expand All @@ -142,8 +144,8 @@ export class AppStoreServerAPIClient {
const responseBody = await response.json()
const errorCode = responseBody['errorCode']

if (Object.values(APIError).includes(errorCode)) {
throw new APIException(response.status, errorCode as APIError)
if (errorCode) {
throw new APIException(response.status, errorCode)
}

throw new APIException(response.status)
Expand All @@ -156,6 +158,14 @@ export class AppStoreServerAPIClient {
}
}

protected async makeFetchRequest(path: string, parsedQueryParameters: URLSearchParams, method: string, stringBody: string | undefined, headers: { [key: string]: string; }) {
return await fetch(this.urlBase + path + '?' + parsedQueryParameters, {
method: method,
body: stringBody,
headers: headers
});
}

/**
* Uses a subscription’s product identifier to extend the renewal date for all of its eligible active subscribers.
*
Expand All @@ -165,7 +175,7 @@ export class AppStoreServerAPIClient {
* {@link https://developer.apple.com/documentation/appstoreserverapi/extend_subscription_renewal_dates_for_all_active_subscribers Extend Subscription Renewal Dates for All Active Subscribers}
*/
public async extendRenewalDateForAllActiveSubscribers(massExtendRenewalDateRequest: MassExtendRenewalDateRequest): Promise<MassExtendRenewalDateResponse> {
return await this.makeRequest("/inApps/v1/subscriptions/extend/mass/", "POST", {}, massExtendRenewalDateRequest, new MassExtendRenewalDateResponseValidator());
return await this.makeRequest("/inApps/v1/subscriptions/extend/mass", "POST", {}, massExtendRenewalDateRequest, new MassExtendRenewalDateResponseValidator());
}

/**
Expand All @@ -190,7 +200,7 @@ export class AppStoreServerAPIClient {
* @throws APIException If a response was returned indicating the request could not be processed
* {@link https://developer.apple.com/documentation/appstoreserverapi/get_all_subscription_statuses Get All Subscription Statuses}
*/
public async getAllSubscriptionStatuses(transactionId: string, status: [Status] | undefined = undefined): Promise<StatusResponse> {
public async getAllSubscriptionStatuses(transactionId: string, status: Status[] | undefined = undefined): Promise<StatusResponse> {
const queryParameters: { [key: string]: [string]} = {}
if (status != null) {
queryParameters["status"] = status.map(s => s.toString()) as [string];
Expand Down Expand Up @@ -269,7 +279,7 @@ export class AppStoreServerAPIClient {
* {@link https://developer.apple.com/documentation/appstoreserverapi/get_transaction_history Get Transaction History}
*/
public async getTransactionHistory(transactionId: string, revision: string | null, transactionHistoryRequest: TransactionHistoryRequest): Promise<HistoryResponse> {
const queryParameters: { [key: string]: [string]} = {}
const queryParameters: { [key: string]: string[]} = {}
if (revision != null) {
queryParameters["revision"] = [revision];
}
Expand All @@ -294,7 +304,7 @@ export class AppStoreServerAPIClient {
if (transactionHistoryRequest.inAppOwnershipType) {
queryParameters["inAppOwnershipType"] = [transactionHistoryRequest.inAppOwnershipType];
}
if (transactionHistoryRequest.revoked) {
if (transactionHistoryRequest.revoked !== undefined) {
queryParameters["revoked"] = [transactionHistoryRequest.revoked.toString()];
}
return await this.makeRequest("/inApps/v1/history/" + transactionId, "GET", queryParameters, null, new HistoryResponseValidator());
Expand Down Expand Up @@ -351,16 +361,16 @@ export class AppStoreServerAPIClient {
const payload = {
bid: this.bundleId
}
return jsonwebtoken.sign(payload, this.signingKey, { algorithm: 'ES256', keyid: this.keyId, issuer: this.issueId, audience: 'appstoreconnect-v1', expiresIn: '5m'});
return jsonwebtoken.sign(payload, this.signingKey, { algorithm: 'ES256', keyid: this.keyId, issuer: this.issuerId, audience: 'appstoreconnect-v1', expiresIn: '5m'});
}
}


export class APIException extends Error {
public httpStatusCode: number
public apiError: APIError | null
public apiError: number | APIError | null

constructor(httpStatusCode: number, apiError: APIError | null = null) {
constructor(httpStatusCode: number, apiError: number | null = null) {
super()
this.httpStatusCode = httpStatusCode
this.apiError = apiError
Expand Down
8 changes: 2 additions & 6 deletions models/AccountTenure.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) 2023 Apple Inc. Licensed under MIT License.

import { Validator } from "./Validator";
import { NumberValidator } from "./Validator";

/**
* The age of the customer’s account.
Expand All @@ -18,8 +18,4 @@ export enum AccountTenure {
GREATER_THAN_THREE_HUNDRED_SIXTY_FIVE_DAYS = 7,
}

export class AccountTenureValidator implements Validator<AccountTenure> {
validate(obj: any): obj is AccountTenure {
return Object.values(AccountTenure).includes(obj)
}
}
export class AccountTenureValidator extends NumberValidator {}
2 changes: 1 addition & 1 deletion models/AppTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface AppTransaction {
*
* {@link https://developer.apple.com/documentation/storekit/apptransaction/3963901-environment environment}
*/
receiptType?: Environment
receiptType?: Environment | string

/**
* The unique identifier the App Store uses to identify the app.
Expand Down
8 changes: 2 additions & 6 deletions models/AutoRenewStatus.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) 2023 Apple Inc. Licensed under MIT License.

import { Validator } from "./Validator";
import { NumberValidator } from "./Validator";

/**
* The renewal status for an auto-renewable subscription.
Expand All @@ -12,8 +12,4 @@ export enum AutoRenewStatus {
ON = 1,
}

export class AutoRenewStatusValidator implements Validator<AutoRenewStatus> {
validate(obj: any): obj is AutoRenewStatus {
return Object.values(AutoRenewStatus).includes(obj)
}
}
export class AutoRenewStatusValidator extends NumberValidator {}
16 changes: 8 additions & 8 deletions models/ConsumptionRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ export interface ConsumptionRequest {
*
* {@link https://developer.apple.com/documentation/appstoreserverapi/consumptionstatus consumptionStatus}
**/
consumptionStatus?: ConsumptionStatus
consumptionStatus?: ConsumptionStatus | number

/**
* A value that indicates the platform on which the customer consumed the in-app purchase.
*
* {@link https://developer.apple.com/documentation/appstoreserverapi/platform platform}
**/
platform?: Platform
platform?: Platform | number

/**
* A Boolean value that indicates whether you provided, prior to its purchase, a free sample or trial of the content, or information about its functionality.
Expand All @@ -49,7 +49,7 @@ export interface ConsumptionRequest {
*
* {@link https://developer.apple.com/documentation/appstoreserverapi/deliverystatus deliveryStatus}
**/
deliveryStatus?: DeliveryStatus
deliveryStatus?: DeliveryStatus | number

/**
* The UUID that an app optionally generates to map a customer’s in-app purchase with its resulting App Store transaction.
Expand All @@ -63,33 +63,33 @@ export interface ConsumptionRequest {
*
* {@link https://developer.apple.com/documentation/appstoreserverapi/accounttenure accountTenure}
**/
accountTenure?: AccountTenure
accountTenure?: AccountTenure | number

/**
* A value that indicates the amount of time that the customer used the app.
*
* {@link https://developer.apple.com/documentation/appstoreserverapi/consumptionrequest ConsumptionRequest}
**/
playTime?: PlayTime
playTime?: PlayTime | number

/**
* A value that indicates the total amount, in USD, of refunds the customer has received, in your app, across all platforms.
*
* {@link https://developer.apple.com/documentation/appstoreserverapi/lifetimedollarsrefunded lifetimeDollarsRefunded}
**/
lifetimeDollarsRefunded?: LifetimeDollarsRefunded
lifetimeDollarsRefunded?: LifetimeDollarsRefunded | number

/**
* A value that indicates the total amount, in USD, of in-app purchases the customer has made in your app, across all platforms.
*
* {@link https://developer.apple.com/documentation/appstoreserverapi/lifetimedollarspurchased lifetimeDollarsPurchased}
**/
lifetimeDollarsPurchased?: LifetimeDollarsPurchased
lifetimeDollarsPurchased?: LifetimeDollarsPurchased | number

/**
* The status of the customer’s account.
*
* {@link https://developer.apple.com/documentation/appstoreserverapi/userstatus userStatus}
**/
userStatus?: UserStatus
userStatus?: UserStatus | number
}
8 changes: 2 additions & 6 deletions models/ConsumptionStatus.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) 2023 Apple Inc. Licensed under MIT License.

import { Validator } from "./Validator";
import { NumberValidator } from "./Validator";

/**
* A value that indicates the extent to which the customer consumed the in-app purchase.
Expand All @@ -14,8 +14,4 @@ export enum ConsumptionStatus {
FULLY_CONSUMED = 3,
}

export class ConsumptionStatusValidator implements Validator<ConsumptionStatus> {
validate(obj: any): obj is ConsumptionStatus {
return Object.values(ConsumptionStatus).includes(obj)
}
}
export class ConsumptionStatusValidator extends NumberValidator {}
4 changes: 2 additions & 2 deletions models/Data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface Data {
*
* {@link https://developer.apple.com/documentation/appstoreservernotifications/environment environment}
**/
environment?: Environment
environment?: Environment | string

/**
* The unique identifier of an app in the App Store.
Expand Down Expand Up @@ -58,7 +58,7 @@ export interface Data {
*
* {@link https://developer.apple.com/documentation/appstoreservernotifications/status status}
**/
status?: Status
status?: Status | number
}


Expand Down
8 changes: 2 additions & 6 deletions models/DeliveryStatus.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) 2023 Apple Inc. Licensed under MIT License.

import { Validator } from "./Validator";
import { NumberValidator } from "./Validator";

/**
* A value that indicates whether the app successfully delivered an in-app purchase that works properly.
Expand All @@ -16,8 +16,4 @@ export enum DeliveryStatus {
DID_NOT_DELIVER_FOR_OTHER_REASON = 5,
}

export class DeliveryStatusValidator implements Validator<DeliveryStatus> {
validate(obj: any): obj is DeliveryStatus {
return Object.values(DeliveryStatus).includes(obj)
}
}
export class DeliveryStatusValidator extends NumberValidator {}
8 changes: 2 additions & 6 deletions models/Environment.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) 2023 Apple Inc. Licensed under MIT License.

import { Validator } from "./Validator";
import { StringValidator } from "./Validator";

/**
* The server environment, either sandbox or production.
Expand All @@ -14,8 +14,4 @@ export enum Environment {
LOCAL_TESTING = "LocalTesting",
}

export class EnvironmentValidator implements Validator<Environment> {
validate(obj: any): obj is Environment {
return Object.values(Environment).includes(obj)
}
}
export class EnvironmentValidator extends StringValidator {}
8 changes: 2 additions & 6 deletions models/ExpirationIntent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) 2023 Apple Inc. Licensed under MIT License.

import { Validator } from "./Validator";
import { NumberValidator } from "./Validator";

/**
* The reason an auto-renewable subscription expired.
Expand All @@ -15,8 +15,4 @@ export enum ExpirationIntent {
OTHER = 5,
}

export class ExpirationIntentValidator implements Validator<ExpirationIntent> {
validate(obj: any): obj is ExpirationIntent {
return Object.values(ExpirationIntent).includes(obj)
}
}
export class ExpirationIntentValidator extends NumberValidator {}
8 changes: 2 additions & 6 deletions models/ExtendReasonCode.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) 2023 Apple Inc. Licensed under MIT License.

import { Validator } from "./Validator";
import { NumberValidator } from "./Validator";

/**
* The code that represents the reason for the subscription-renewal-date extension.
Expand All @@ -14,8 +14,4 @@ export enum ExtendReasonCode {
SERVICE_ISSUE_OR_OUTAGE = 3,
}

export class ExtendReasonCodeValidator implements Validator<ExtendReasonCode> {
validate(obj: any): obj is ExtendReasonCode {
return Object.values(ExtendReasonCode).includes(obj)
}
}
export class ExtendReasonCodeValidator extends NumberValidator {}
8 changes: 2 additions & 6 deletions models/FirstSendAttemptResult.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) 2023 Apple Inc. Licensed under MIT License.

import { Validator } from "./Validator";
import { StringValidator, Validator } from "./Validator";

/**
* An error or result that the App Store server receives when attempting to send an App Store server notification to your server.
Expand All @@ -21,8 +21,4 @@ export enum FirstSendAttemptResult {
OTHER = "OTHER",
}

export class FirstSendAttemptResultValidator implements Validator<FirstSendAttemptResult> {
validate(obj: any): obj is FirstSendAttemptResult {
return Object.values(FirstSendAttemptResult).includes(obj)
}
}
export class FirstSendAttemptResultValidator extends StringValidator {}
2 changes: 1 addition & 1 deletion models/HistoryResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export interface HistoryResponse {
*
* {@link https://developer.apple.com/documentation/appstoreserverapi/environment environment}
**/
environment?: Environment
environment?: Environment | string

/**
* An array of in-app purchase transactions for the customer, signed by Apple, in JSON Web Signature format.
Expand Down
Loading

0 comments on commit 60fae07

Please sign in to comment.