epo-ops-sdk
Version:
TypeScript SDK for the European Patent Office's Open Patent Services (OPS) API with OAuth support
369 lines • 16.8 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EpoOpsClient = void 0;
const axios_1 = __importDefault(require("axios"));
const retry_1 = require("./utils/retry");
const errors_1 = require("./errors");
const schemas_1 = require("./schemas");
const zod_1 = require("zod");
const xml2js_1 = require("xml2js");
class EpoOpsClient {
constructor(config) {
this.token = null;
this.tokenCreatedAt = null;
this.baseUrl = config.baseUrl || 'https://ops.epo.org/3.2/rest-services';
this.maxRetries = config.maxRetries || 3;
this.clientId = config.clientId;
this.clientSecret = config.clientSecret;
// Initialize HTTP client
this.httpClient = axios_1.default.create({
baseURL: this.baseUrl,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/xml' // Request XML responses
}
});
// Add response interceptor for error handling
this.httpClient.interceptors.response.use(response => response, this.handleError.bind(this));
}
async initialize() {
await this.initializeOAuthClient();
}
handleError(error) {
if (error.response) {
const { status, data } = error.response;
switch (status) {
case 401:
throw new errors_1.AuthenticationError('Invalid or expired token');
case 403:
throw new errors_1.AuthenticationError('Insufficient permissions');
case 429:
throw new errors_1.RateLimitError('Rate limit exceeded');
case 400:
throw new errors_1.ValidationError('Invalid request parameters');
default:
throw new errors_1.EpoOpsError('API request failed', status, data?.code, data);
}
}
throw new errors_1.EpoOpsError('Network error occurred');
}
async initializeOAuthClient() {
try {
// Create Basic Auth header
const auth = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64');
// Make request to token endpoint
const response = await axios_1.default.post('https://ops.epo.org/3.2/auth/accesstoken', 'grant_type=client_credentials&scope=ops', {
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json',
'Connection': 'keep-alive',
'Host': 'ops.epo.org',
'X-Target-URI': 'https://ops.epo.org/3.2/rest-services'
}
});
if (!response.data || !response.data.access_token) {
throw new errors_1.AuthenticationError('Invalid token response');
}
// Store token and creation time
this.token = {
access_token: response.data.access_token,
token_type: response.data.token_type || 'Bearer',
expires_in: response.data.expires_in || 3600,
scope: response.data.scope || 'ops'
};
this.tokenCreatedAt = Date.now();
// Update HTTP client with new token
if (this.httpClient) {
this.httpClient.defaults.headers.common['Authorization'] = `Bearer ${this.token.access_token}`;
}
}
catch (error) {
if (axios_1.default.isAxiosError(error)) {
console.error('OAuth initialization error:', error);
throw new errors_1.AuthenticationError(`Failed to initialize OAuth client: ${error.response?.data?.message || 'Unknown error'}`);
}
throw error;
}
}
async parseXMLResponse(response) {
try {
if (typeof response === 'string') {
return await (0, xml2js_1.parseStringPromise)(response);
}
return response;
}
catch (error) {
console.error('XML parsing error:', error);
throw new errors_1.EpoOpsError('Failed to parse XML response');
}
}
async searchPatents(query, options = {}) {
await this.ensureToken();
return (0, retry_1.withRetry)(async () => {
// Validate options
schemas_1.SearchOptionsSchema.parse(options);
const response = await this.httpClient.get('/published-data/search', {
params: {
q: query,
...options
}
});
const parsedData = await this.parseXMLResponse(response.data);
const transformedData = this.transformSearchResults(parsedData);
// Validate response
return schemas_1.SearchResponseSchema.parse(transformedData);
}, { maxRetries: this.maxRetries });
}
async getBibliographicData(reference) {
await this.ensureToken();
return (0, retry_1.withRetry)(async () => {
// Validate reference
schemas_1.PatentReferenceSchema.parse(reference);
const response = await this.httpClient.get(`/published-data/${reference.type}/${reference.format}/${reference.number}/biblio`);
const parsedData = await this.parseXMLResponse(response.data);
const transformedData = this.transformBibliographicData(parsedData);
// Validate transformed data
return schemas_1.BibliographicDataSchema.parse(transformedData);
}, { maxRetries: this.maxRetries });
}
async getClaims(reference) {
await this.ensureToken();
return (0, retry_1.withRetry)(async () => {
// Validate reference
schemas_1.PatentReferenceSchema.parse(reference);
const response = await this.httpClient.get(`/published-data/${reference.type}/${reference.format}/${reference.number}/claims`);
const transformedData = this.transformClaims(response.data);
// Validate transformed data
return schemas_1.ClaimsSchema.parse(transformedData);
}, { maxRetries: this.maxRetries });
}
async getFamily(reference) {
await this.ensureToken();
return (0, retry_1.withRetry)(async () => {
// Validate reference
schemas_1.PatentReferenceSchema.parse(reference);
const response = await this.httpClient.get(`/family/${reference.type}/${reference.format}/${reference.number}`);
const transformedData = this.transformFamilyData(response.data);
// Validate transformed data
return zod_1.z.array(schemas_1.FamilyMemberSchema).parse(transformedData);
}, { maxRetries: this.maxRetries });
}
async getLegalStatus(reference) {
await this.ensureToken();
return (0, retry_1.withRetry)(async () => {
// Validate reference
schemas_1.PatentReferenceSchema.parse(reference);
const response = await this.httpClient.get(`/legal/${reference.type}/${reference.format}/${reference.number}`);
const transformedData = this.transformLegalStatus(response.data);
// Validate transformed data
return zod_1.z.array(schemas_1.LegalStatusSchema).parse(transformedData);
}, { maxRetries: this.maxRetries });
}
// New API methods
async getClassification(cpcClass, options = {}) {
await this.ensureToken();
return (0, retry_1.withRetry)(async () => {
// Validate options
schemas_1.ClassificationOptionsSchema.parse(options);
const response = await this.httpClient.get(`/classification/${cpcClass}`, {
params: options
});
const transformedData = this.transformClassificationData(response.data);
// Validate response
return schemas_1.ClassificationResponseSchema.parse(transformedData);
}, { maxRetries: this.maxRetries });
}
async convertNumber(type, sourceFormat, number, targetFormat) {
await this.ensureToken();
return (0, retry_1.withRetry)(async () => {
const response = await this.httpClient.get(`/number/convert/${type}/${sourceFormat}/${number}/${targetFormat}`);
const transformedData = this.transformNumberConversion(response.data);
// Validate response
return schemas_1.NumberConversionResponseSchema.parse(transformedData);
}, { maxRetries: this.maxRetries });
}
async searchClassification(query) {
await this.ensureToken();
return (0, retry_1.withRetry)(async () => {
const response = await this.httpClient.get('/classification/cpc/search', {
params: { q: query }
});
const transformedData = this.transformClassificationSearchData(response.data);
// Validate response
return schemas_1.ClassificationResponseSchema.parse(transformedData);
}, { maxRetries: this.maxRetries });
}
transformBibliographicData(data) {
try {
const doc = data['ops:world-patent-data']['ops:document'][0];
const biblio = doc['bibliographic-data'][0];
return {
title: biblio['invention-title']?.[0]?.['$'] || '',
abstract: biblio['abstract']?.[0]?.['$'] || '',
inventors: biblio['parties']?.[0]?.['inventors']?.[0]?.['inventor']?.map((inv) => inv['inventor-name']?.[0]?.['$'] || '') || [],
applicants: biblio['parties']?.[0]?.['applicants']?.[0]?.['applicant']?.map((app) => app['applicant-name']?.[0]?.['$'] || '') || [],
publicationDate: biblio['publication-reference']?.[0]?.['document-id']?.[0]?.['date']?.[0] || '',
applicationDate: biblio['application-reference']?.[0]?.['document-id']?.[0]?.['date']?.[0] || '',
classification: biblio['patent-classifications']?.[0]?.['patent-classification']?.map((pc) => `${pc['section']?.[0] || ''}${pc['class']?.[0] || ''}${pc['subclass']?.[0] || ''}`) || []
};
}
catch (error) {
console.error('Error transforming bibliographic data:', error);
throw new errors_1.EpoOpsError('Failed to transform bibliographic data');
}
}
transformClaims(data) {
const claims = data['ops:world-patent-data']['ops:document']['claims']['claim'];
return {
independent: claims.filter((c) => c['@type'] === 'independent').map((c) => c['$']),
dependent: claims.filter((c) => c['@type'] === 'dependent').map((c) => c['$'])
};
}
transformFamilyData(data) {
const members = data['ops:world-patent-data']['ops:patent-family']['family-member'];
return members.map((member) => {
const docId = member['publication-reference']['document-id'][0];
return {
publicationNumber: `${docId.country}${docId['doc-number']}${docId.kind}`,
publicationDate: docId.date,
title: member['invention-title']?.['$'] || '',
country: docId.country,
kind: docId.kind
};
});
}
transformLegalStatus(data) {
const events = data['ops:world-patent-data']['ops:legal-status-data']['legal-status'];
return events.map((event) => {
const legalEvent = event['legal-event'];
return {
status: legalEvent.code,
date: legalEvent.date,
description: legalEvent.title?.['$'] || '',
country: legalEvent.country
};
});
}
transformSearchResults(data) {
try {
const searchData = data['ops:world-patent-data']['ops:biblio-search'][0];
const results = searchData['ops:search-result'].map((result) => {
const pubRef = result['ops:publication-reference'][0];
const docId = pubRef['document-id'][0];
return {
id: `${docId['country'][0]}${docId['doc-number'][0]}${docId['kind'][0]}`,
title: result['invention-title']?.['$'] || 'No title available',
familyId: pubRef.$.family_id,
country: docId['country'][0],
docNumber: docId['doc-number'][0],
kind: docId['kind'][0]
};
});
return {
status: 200,
data: {
query: searchData['ops:query'][0]._,
results
}
};
}
catch (error) {
console.error('Error transforming search results:', error);
throw new errors_1.EpoOpsError('Failed to transform search results');
}
}
transformClassificationData(data) {
const classData = data?.['ops:world-patent-data']?.['ops:classification-data']?.['classification-item'] || {};
return {
status: 200,
data: {
class: classData?.['classification-symbol'] || '',
title: classData?.['title-part']?.['$'] || '',
description: classData?.['description-part']?.['$'] || '',
subclasses: (classData?.['child-items'] || []).map((item) => ({
code: item?.['classification-symbol'] || '',
title: item?.['title-part']?.['$'] || ''
}))
}
};
}
transformNumberConversion(data) {
const standardization = data['ops:world-patent-data']['ops:standardization'];
return {
status: 200,
data: {
input: {
type: standardization.input['@type'],
format: standardization.input['@format'],
number: standardization.input['$']
},
output: {
format: standardization.output['@format'],
number: standardization.output['$']
}
}
};
}
transformClassificationSearchData(data) {
try {
const searchData = data['ops:world-patent-data']['ops:classification-search'][0];
const results = searchData['ops:search-result']?.[0]?.['classification-item'] || [];
// If no results, return a default response
if (!results.length) {
return {
status: 200,
data: {
class: '',
title: 'No results found',
description: '',
subclasses: []
}
};
}
// Use the first result as the main classification
const firstResult = results[0];
return {
status: 200,
data: {
class: firstResult['classification-symbol']?.[0] || '',
title: firstResult['title-part']?.[0]?.['$'] || '',
description: firstResult['description-part']?.[0]?.['$'] || '',
subclasses: results.slice(1).map((item) => ({
code: item['classification-symbol']?.[0] || '',
title: item['title-part']?.[0]?.['$'] || ''
}))
}
};
}
catch (error) {
console.error('Error transforming classification search data:', error);
throw new errors_1.EpoOpsError('Failed to transform classification search data');
}
}
async ensureToken() {
if (!this.token) {
throw new errors_1.AuthenticationError('OAuth token not initialized');
}
if (this.isTokenExpired()) {
try {
await this.initializeOAuthClient();
}
catch (error) {
throw new errors_1.AuthenticationError('Failed to refresh OAuth token');
}
}
}
isTokenExpired() {
if (!this.token || !this.tokenCreatedAt)
return true;
// Add a small buffer (5 minutes) to prevent edge cases
const expirationTime = this.tokenCreatedAt + (this.token.expires_in * 1000);
return Date.now() >= expirationTime - 300000; // 5 minutes buffer
}
}
exports.EpoOpsClient = EpoOpsClient;
//# sourceMappingURL=client.js.map