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

Allow reading unknown Enum values and increase unit testing coverage #36

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
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