UNPKG

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
"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