Skip to content

Commit

Permalink
feat: upload picture to eps with working example
Browse files Browse the repository at this point in the history
  • Loading branch information
dantio committed Aug 26, 2021
1 parent 33b23de commit 2d6d46c
Show file tree
Hide file tree
Showing 12 changed files with 122 additions and 61 deletions.
4 changes: 3 additions & 1 deletion examples/restful/sell/getOrders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ eBay.OAuth2.setScope([
'https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly'
])

eBay.sell.fulfillment.getOrders().then(order => {
eBay.sell.fulfillment.getOrders({
limit: 5
}).then(order => {
console.log('order', JSON.stringify(order, null, 2));
}).catch(e => {
console.error(JSON.stringify(e, null, 2));
Expand Down
6 changes: 2 additions & 4 deletions examples/traditional/trading.GetMyMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import eBayApi from '../../src';
const eBay = eBayApi.fromEnv();

eBay.trading.GetMyMessages({
MessageIDs: {
MessageID: [117475106841]
},
DetailLevel: 'ReturnMessages'
Folder: 0,
DetailLevel: 'ReturnHeaders'
}).then(result => {
console.log(JSON.stringify(result, null, 2));
}).catch(e => {
Expand Down
28 changes: 28 additions & 0 deletions examples/traditional/trading.UploadSiteHostedPictures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// tslint:disable:no-console
import eBayApi from '../../src';
import FormData from 'form-data';
import * as fs from 'fs';
import * as path from 'path';

const eBay = eBayApi.fromEnv();

(async () => {
try {
const image = fs.readFileSync(path.resolve(__dirname, 'upload_ok.png'));

// const image = fs.readFileSync(path.resolve(__dirname, 'upload_bad_quality.jpg'));
// --> To reduce possible issues with picture display quality, eBay recommends that pictures you upload have a JPEG quality value of 90 or greater.
const response = await eBay.trading.UploadSiteHostedPictures({
ExtensionInDays: 1,
}, {
multipart: {
formData: new FormData(), // pass FormData instance here
file: image
}
});

console.log(response);
} catch (e) {
console.log(JSON.stringify(e, null, 2));
}
})();
Binary file added examples/traditional/upload_bad_quality.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/traditional/upload_ok.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 2 additions & 3 deletions src/api/restful/sell/feed/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Restful from '../../';
import {multipartHeader} from "../../../../request";
import {SellFeedParams} from '../../../../types';

/**
Expand Down Expand Up @@ -239,9 +240,7 @@ export default class Feed extends Restful {
public uploadFile(taskId: string, data?: any) {
taskId = encodeURIComponent(taskId)
return this.post(`/task/${taskId}/upload_file`, data, {
headers: {
'Content-Type': 'multipart/form-data'
},
headers: multipartHeader,
});
}

Expand Down
110 changes: 66 additions & 44 deletions src/api/traditional/XMLRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import debug from 'debug';
import xmlParser, {j2xParser} from 'fast-xml-parser';

import {eBayHandleEBayJsonResponse, EbayNoCallError} from '../../errors';
import {IEBayApiRequest} from '../../request';
import {IEBayApiRequest, multipartHeader} from '../../request';
import {Fields} from './fields';

const HEADING = '<?xml version="1.0" encoding="utf-8"?>';
const log = debug('ebay:xml:request');

export interface IFormData {
append: (name: string, value: string, filename?: string) => void
getHeaders?: () => Headers
append: (name: string, value: string | Buffer, filename?: string) => void
getHeaders?: () => Headers,
}

const defaultJSON2XMLOptions = {
Expand Down Expand Up @@ -41,12 +40,17 @@ type Headers = {
[key: string]: string | number | undefined;
};

export type Multipart = {
formData?: IFormData,
file?: any
}

export type Options = {
raw?: boolean,
parseOptions?: object,
useIaf?: boolean,
headers?: Headers,
formData?: IFormData
multipart?: Multipart
};

export type XMLReqConfig = Options & {
Expand All @@ -56,13 +60,17 @@ export type XMLReqConfig = Options & {
eBayAuthToken?: string | null,
};

export const defaultOptions: Required<Omit<Options, 'formData'>> = {
export const defaultOptions: Required<Omit<Options, 'multipart'>> = {
raw: false,
parseOptions: defaultXML2JSONParseOptions,
useIaf: true,
headers: {}
};

export const defaultHeaders = {
'Content-Type': 'text/xml'
};

/**
* XML request for making eBay API call.
*/
Expand All @@ -71,13 +79,10 @@ export default class XMLRequest {
private readonly fields: Fields;
private readonly config: XMLReqConfig;
private readonly req: any;
private readonly _form: IFormData | null = null;

public static j2x = new j2xParser(defaultJSON2XMLOptions);

private readonly defaultHeaders = {
'Content-Type': 'text/xml'
};

/**
* Creates the new Request object
*
Expand All @@ -96,6 +101,10 @@ export default class XMLRequest {
this.fields = fields || {};
this.config = {...defaultOptions, ...config};
this.req = req;

if (this.config.multipart?.formData) {
this._form = this.config.multipart.formData;
}
}

/**
Expand All @@ -122,35 +131,36 @@ export default class XMLRequest {
} : {};
}

get requestConfig() {
let headers;
if (this.config.formData) {
headers = typeof this.config.formData.getHeaders === 'function' ? this.config.formData.getHeaders() : {
'content-type': 'multipart/form-data'
}
} else {
headers = this.defaultHeaders
}

public getRequestConfig() {
return {
headers: {
...headers,
...this.getHeaders(),
...this.config.headers,
}
}
}

public getHeaders() {
if (this._form) {
return {
...(typeof this._form.getHeaders === 'function' ? this._form.getHeaders() : multipartHeader),
}
}

return defaultHeaders
}

get parseOptions() {
return {
...defaultXML2JSONParseOptions,
...this.config.parseOptions
}
}

getData(xml: string) {
if (this.config.formData) {
this.config.formData.append('XML Payload', xml, 'payload.xml');
return this.config.formData
public getData(xml: string) {
if (this._form) {
this._form.append('XML Payload', xml, 'payload.xml');
return this._form
}

return xml
Expand All @@ -160,10 +170,11 @@ export default class XMLRequest {
* converts an XML response to JSON
*
* @param {string} xml The xml
* @param {object} parseOptions The parse options
* @return {JSON} resolves to a JSON representation of the HTML
*/
public static toJSON(xml: string, parseOptions: object) {
public toJSON(xml: string) {
const parseOptions = this.parseOptions;
log('parseOption', parseOptions);
return xmlParser.parse(xml, parseOptions);
}

Expand All @@ -175,7 +186,7 @@ export default class XMLRequest {
* @return {String} The XML string of the Request
*/
public toXML(fields: Fields) {
log('JSON2XML:parseOptions', defaultJSON2XMLOptions);
const HEADING = '<?xml version="1.0" encoding="utf-8"?>';
return HEADING + XMLRequest.j2x.parse({
[this.callName + 'Request']: {
'@_xmlns': this.config.xmlns,
Expand All @@ -193,46 +204,57 @@ export default class XMLRequest {
*
*/
public async request() {
log('XML:config', this.config);
const xml = this.toXML(this.fields);
log('XML:xml', xml);
log('xml', xml);

try {
const data = this.getData(xml);
const config = this.requestConfig;

log('XML:request:' + this.config.endpoint, config);
this.addFile();

const config = this.getRequestConfig();
log('config', config);
const response = await this.req.post(this.config.endpoint, data, config);

log('XML:response', response);
log('response', response);

// resolve to raw XML
if (this.config.raw) {
return response;
}

log('XML:parseOption', this.parseOptions);
let json = XMLRequest.toJSON(response, this.parseOptions);

log('XML:JSON', json);

// Unwrap
if (json[this.responseWrapper]) {
json = json[this.responseWrapper]
}
const json = this.xml2JSON(response);

eBayHandleEBayJsonResponse(json);

return json;
} catch (error) {
log('XML:error', error);
log('error', error);

if (error.response?.data) {
const data = XMLRequest.toJSON(error.response.data, this.parseOptions);
const data = this.toJSON(error.response.data);
eBayHandleEBayJsonResponse(data);
}

throw error;
}
}

private addFile() {
if (this.config.multipart?.file && this._form) {
this._form.append('dummy', this.config.multipart?.file)
log('added file');
}
}

public xml2JSON(xml: string) {
const json = this.toJSON(xml);

// Unwrap
if (json[this.responseWrapper]) {
return json[this.responseWrapper]
}

return json;
}
}
4 changes: 2 additions & 2 deletions src/api/traditional/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {stringify} from 'qs';
import Api from '../';
import {EBayIAFTokenExpired, handleEBayError} from '../../errors';
import {EBayIAFTokenExpired, EBayIAFTokenInvalid, handleEBayError} from '../../errors';
import {ClientAlerts, Finding, Shopping, Trading, TraditionalApi} from '../../types';
import ClientAlertsCalls from './clientAlerts';
import {Fields} from './fields';
Expand Down Expand Up @@ -130,7 +130,7 @@ export default class Traditional extends Api {
return await this.request(options, api, callName, fields);
} catch (error) {
// Try to refresh the token.
if (error.name === EBayIAFTokenExpired.name && this.config.autoRefreshToken) {
if (this.config.autoRefreshToken && (error.name === EBayIAFTokenExpired.name || error.name === EBayIAFTokenInvalid.name)) {
return await this.request(options, api, callName, fields, true);
}

Expand Down
6 changes: 6 additions & 0 deletions src/errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ export class EBayIAFTokenExpired extends EbayApiError {
public static readonly code = 21917053;
}

export class EBayIAFTokenInvalid extends EbayApiError {
public static readonly code = 21916984;
}

export class EBayTokenRequired extends EbayApiError {
public static readonly code = 930;
}
Expand Down Expand Up @@ -184,6 +188,8 @@ export const eBayHandleEBayJsonResponse = (data: any) => {
switch (data.Errors.ErrorCode) {
case EBayIAFTokenExpired.code:
throw new EBayIAFTokenExpired(data);
case EBayIAFTokenInvalid.code:
throw new EBayIAFTokenInvalid(data);
case EBayTokenRequired.code:
throw new EBayTokenRequired(data);
}
Expand Down
10 changes: 5 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import OAuth2 from './auth/oAuth2';
import {ContentLanguage, Locale, MarketplaceId, SiteId} from './enums';
import {ApiEnvError} from './errors';
import {IEBayApiRequest} from './request';
import {AppConfig, ClientAlerts, Finding, Shopping, Trading} from './types';
import {AppConfig, ClientAlerts, Finding, Keyset, Shopping, Trading} from './types';

const defaultConfig: Omit<AppConfig, 'appId' | 'certId'> = {
const defaultConfig: Omit<AppConfig, keyof Keyset> = {
sandbox: false,
autoRefreshToken: true,
siteId: SiteId.EBAY_US,
marketplaceId: MarketplaceId.EBAY_US,
autoRefreshToken: true,
acceptLanguage: Locale.en_US,
contentLanguage: ContentLanguage.en_US
};
Expand All @@ -29,7 +29,7 @@ class eBayApi extends Api {
public static Locale = Locale;

/**
* Loads settings from `process.env`
* Loads config from `process.env`
*
* @return {eBayApi} a new eBayApi instance
* @param {request} req request
Expand All @@ -54,7 +54,7 @@ class eBayApi extends Api {
siteId: process.env.EBAY_SITE_ID ? parseInt(process.env.EBAY_SITE_ID, 10) : SiteId.EBAY_US,
marketplaceId: process.env.EBAY_MARKETPLACE_ID && process.env.EBAY_MARKETPLACE_ID in MarketplaceId ?
MarketplaceId[process.env.EBAY_MARKETPLACE_ID as keyof typeof MarketplaceId] as MarketplaceId :
MarketplaceId.EBAY_DE,
MarketplaceId.EBAY_US,
ruName: process.env.EBAY_RU_NAME,
sandbox: (process.env.EBAY_SANDBOX === 'true')
},
Expand Down
6 changes: 5 additions & 1 deletion src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export const defaultGlobalHeaders = {
'Access-Control-Allow-Methods': 'GET, PUT, POST, DELETE'
}

export const multipartHeader = {
'Content-Type': 'multipart/form-data'
}

export interface IEBayApiRequest<T = AxiosInstance> {
readonly instance: T;

Expand All @@ -31,7 +35,7 @@ export class AxiosRequest implements IEBayApiRequest {
this.instance = axios.create({
headers: {
...defaultGlobalHeaders,
'User-Agent': 'hendt/ebay-api (axios)',
'User-Agent': 'hendt-ebay-api/' + process.env.npm_package_version,
},
...config
});
Expand Down
Loading

0 comments on commit 2d6d46c

Please sign in to comment.