Skip to content

Commit 7271619

Browse files
committed
Updates to allows oslc-client to run in the browser or in node.js
1 parent c59c450 commit 7271619

12 files changed

Lines changed: 156 additions & 448 deletions

.idea/.gitignore

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/modules.xml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/oslc-client.iml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/vcs.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

OSLCClient.js

Lines changed: 88 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,30 @@
11
import axios from 'axios';
2-
import { wrapper } from 'axios-cookiejar-support';
3-
import https from 'https';
4-
import { sym, literal } from 'rdflib';
2+
import { sym } from 'rdflib';
53
import * as $rdf from "rdflib";
6-
import { DOMParser } from '@xmldom/xmldom';
7-
import { CookieJar , Cookie} from 'tough-cookie';
8-
import { rdf, dcterms, rdfs, oslc, oslc_cm1, oslc_rm, oslc_qm1 } from './namespaces.js';
4+
import { rdfs, oslc, oslc_cm1, oslc_rm, oslc_qm1 } from './namespaces.js';
95
import OSLCResource from './OSLCResource.js';
106
import Compact from './Compact.js';
117
import RootServices from './RootServices.js';
128
import ServiceProviderCatalog from './ServiceProviderCatalog.js';
139
import ServiceProvider from './ServiceProvider.js';
1410

11+
// Conditional imports for Node.js only
12+
let wrapper, CookieJar, DOMParser;
13+
const isNodeEnvironment = typeof window === 'undefined';
14+
15+
if (isNodeEnvironment) {
16+
// Node.js imports
17+
const cookiejarSupport = await import('axios-cookiejar-support');
18+
wrapper = cookiejarSupport.wrapper;
19+
const toughCookie = await import('tough-cookie');
20+
CookieJar = toughCookie.CookieJar;
21+
const xmldom = await import('@xmldom/xmldom');
22+
DOMParser = xmldom.DOMParser;
23+
} else {
24+
// Browser: use native DOMParser
25+
DOMParser = window.DOMParser;
26+
}
27+
1528
// Service providers properties
1629
const serviceProviders = {
1730
'CM': oslc_cm1('cmServiceProviders'),
@@ -35,31 +48,34 @@ export default class OSLCClient {
3548
this.spc = null;
3649
this.sp = null;
3750
this.ownerMap = new Map();
38-
this.jar = new CookieJar();
51+
this.isNodeEnvironment = isNodeEnvironment;
52+
53+
if (isNodeEnvironment) {
54+
this.jar = new CookieJar();
55+
}
3956

4057
// Create a base configuration
4158
const baseConfig = {
4259
timeout: 30000,
43-
keepAlive: true,
4460
headers: {
4561
'Accept': 'application/rdf+xml, text/turtle;q=0.9, application/ld+json;q=0.8, application/json;q=0.7, application/xml;q=0.6, text/xml;q=0.5, */*;q=0.1',
4662
'OSLC-Core-Version': '2.0'
4763
},
4864
validateStatus: status => status === 401 || status < 400 // Accept all 2xx responses
4965
};
5066

51-
// Check if we're in Node.js (where 'window' is undefined)
52-
if (typeof window === 'undefined') {
53-
// We are in Node.js -> use a cookie jar
67+
// Configure for Node.js or Browser
68+
if (isNodeEnvironment) {
69+
// Node.js: use a cookie jar and keep-alive
70+
baseConfig.keepAlive = true;
5471
baseConfig.jar = this.jar;
72+
this.client = wrapper(axios.create(baseConfig));
5573
} else {
56-
// We are in the Browser -> use withCredentials
74+
// Browser: use withCredentials for automatic cookie handling
5775
baseConfig.withCredentials = true;
76+
this.client = axios.create(baseConfig);
5877
}
5978

60-
// Create axios instance with cookie agents
61-
this.client = wrapper(axios.create(baseConfig));
62-
6379
// Add the Configuration-Context header if one is given
6480
if (configuration_context) {
6581
this.client.defaults.headers.common['Configuration-Context'] = configuration_context;
@@ -70,37 +86,35 @@ export default class OSLCClient {
7086
async response => {
7187
const originalRequest = response.config;
7288
const wwwAuthenticate = response?.headers?.['www-authenticate'];
73-
/*
74-
const allCookies = await originalRequest.jar.store.getAllCookies();
75-
console.log('\n--- ALL COOKIES IN JAR ---');
76-
allCookies.forEach((cookie, index) => {
77-
console.log(`\n[${index + 1}] ${cookie.key}=${cookie.value}`);
78-
console.log(` Domain: ${cookie.domain}`);
79-
console.log(` Path: ${cookie.path}`);
80-
console.log(` Expires: ${cookie.expires}`);
81-
console.log(` HTTP Only: ${cookie.httpOnly}`);
82-
console.log(` Secure: ${cookie.secure}`);
83-
});
84-
*/
89+
8590
// Check if this is a JEE Forms authentication challenge
8691
if (response?.headers['x-com-ibm-team-repository-web-auth-msg'] === 'authrequired') {
8792
try {
8893
// Perform the login (JEE form auth typically uses j_username and j_password)
8994
let url = new URL(response.config.url);
9095
const paths = url.pathname.split('/');
9196
url.pathname = paths[1] ? `/${paths[1]}/j_security_check` : '/j_security_check';
92-
response = await this.client.post(url.toString(), {
93-
'j_username': this.userid,
94-
'j_password': this.password
95-
}, {
96-
headers: {
97-
'Content-Type': 'application/x-www-form-urlencoded'
98-
},
99-
maxRedirects: 0,
100-
validateStatus: (status) => status === 302 // for successful login
101-
});
97+
98+
// In browser, form-based auth may require a backend proxy due to CORS
99+
if (!isNodeEnvironment) {
100+
console.warn('Form-based authentication in browser requires CORS-enabled backend or proxy');
101+
}
102+
103+
response = await this.client.post(url.toString(),
104+
new URLSearchParams({
105+
'j_username': this.userid,
106+
'j_password': this.password
107+
}).toString(),
108+
{
109+
headers: {
110+
'Content-Type': 'application/x-www-form-urlencoded'
111+
},
112+
maxRedirects: 0,
113+
validateStatus: (status) => status === 302 // for successful login
114+
}
115+
);
102116
// After successful login, retry the original request with updated cookies
103-
response = await this.client(originalRequest);
117+
response = await this.client.request(originalRequest);
104118
return response;
105119
} catch (error) {
106120
console.error('Error during JEE authentication:', error.message);
@@ -110,30 +124,33 @@ export default class OSLCClient {
110124
const token_uri = wwwAuthenticate.match(/token_uri="([^"]+)"/)[1];
111125
try {
112126
// Refresh the token using the provided token_uri
113-
const response = await axios.post(token_uri,
114-
new URLSearchParams({
115-
username: this.userid,
116-
password: this.password,
117-
}), {
118-
headers: {
119-
'Content-Type': 'application/x-www-form-urlencoded',
120-
'Accept': 'test/plain',
121-
}});
127+
const tokenResponse = await this.client.post(token_uri,
128+
new URLSearchParams({
129+
username: this.userid,
130+
password: this.password,
131+
}).toString(),
132+
{
133+
headers: {
134+
'Content-Type': 'application/x-www-form-urlencoded',
135+
'Accept': 'text/plain',
136+
}
137+
}
138+
);
122139
// retry the original request with the new token
123-
const newToken = response.data; // Refresh token
124-
originalRequest.config.headers['Authorization'] = `Bearer ${newToken}`;
125-
return await axios.client(originalRequest); // Retry the request
140+
const newToken = tokenResponse.data; // Refresh token
141+
originalRequest.headers['Authorization'] = `Bearer ${newToken}`;
142+
return await this.client.request(originalRequest); // Retry the request
126143
} catch (error) {
127144
console.error('Error during jauth realm authentication:', error.message);
128145
return Promise.reject(error);
129146
}
130147
} else if (response.status === 401) {
131-
// Retry with basic authentication for e.g., Jazz Authorizaiton Server
132-
response.config.auth = {
148+
// Retry with basic authentication for e.g., Jazz Authorization Server
149+
originalRequest.auth = {
133150
username: this.userid,
134151
password: this.password
135152
};
136-
return await axios.request(response.config);
153+
return await this.client.request(originalRequest);
137154
} else {
138155
// No authentication challenge, proceed with the response
139156
return response;
@@ -322,8 +339,23 @@ export default class OSLCClient {
322339
const headers = {
323340
'Accept': 'application/rdf+xml; charset=utf-8',
324341
'OSLC-Core-Version': oslc_version,
325-
'X-Jazz-CSRF-Prevent': this.jar.getCookiesSync(url).find(cookie => cookie.key === 'JSESSIONID').value
342+
'X-Jazz-CSRF-Prevent': '1'
326343
};
344+
345+
// In Node.js, try to get JSESSIONID from cookie jar
346+
if (isNodeEnvironment && this.jar) {
347+
try {
348+
const cookies = this.jar.getCookiesSync(url);
349+
const sessionCookie = cookies.find(cookie => cookie.key === 'JSESSIONID');
350+
if (sessionCookie) {
351+
headers['X-Jazz-CSRF-Prevent'] = sessionCookie.value;
352+
}
353+
} catch (error) {
354+
// If cookie retrieval fails, continue with default value
355+
console.debug('Could not retrieve JSESSIONID from cookie jar:', error.message);
356+
}
357+
}
358+
327359
try {
328360
const response = await this.client.delete(url, { headers });
329361
if (response.status !== 200 && response.status !== 204) {
@@ -410,7 +442,8 @@ export default class OSLCClient {
410442
if (response.status !== 200) {
411443
return 'Unknown';
412444
}
413-
const contentLocation = response.headers['content-location'] || url;
445+
const contentLocation = response.headers['content-location'] || url;
446+
const contentType = response.headers['content-type'];
414447
const store = $rdf.graph();
415448
try {
416449
$rdf.parse(response.data, store, url, contentType)

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@
66

77
An OSLC client API Node.js module
88

9-
oslc-client is a JavaScript Node.js module supporting OSLC client and server development. The client API exposes the OSLC core and domain capabilities through a simple JavaScript API on the OSLC REST services.
9+
oslc-client is a JavaScript Node.js module supporting OSLC client and server development. The client API exposes the OSLC
10+
core and domain capabilities through a simple JavaScript API on the OSLC REST services.
1011

11-
oslc-client exploits the dynamic and asynchronous capabilities of JavaScript and Node.js to build and API that can easily adapt to any OSLC domain, extensions to domains, and/or integrations between domains.
12+
oslc-client exploits the dynamic and asynchronous capabilities of JavaScript and Node.js to build and API that can easily
13+
adapt to any OSLC domain, extensions to domains, and/or integrations between domains.
1214

13-
This vesion updates previous 2.x.x versions to use axios for HTTP access and modern JavaScript async/await to handle
15+
This version updates previous 2.x.x versions to use axios for HTTP access and modern JavaScript async/await to handle
1416
asynchronous operations.
1517

18+
Version 3.0.1 is a maintenance release that alows oslc-client run in the browser or in a node.js environment.
19+
20+
*
21+
1622
## Usage
1723

1824
To use oslc-client, include a dependency in your OSLC client app's package.json file:

examples/oslcClientExample.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import OSLCClient from '../OSLCClient.js';
2-
import * as $rdf from "rdflib";
32

43
// process command line arguments
54
var args = process.argv.slice(2)
6-
if (args.length != 5) {
5+
if (args.length !== 5) {
76
console.log('Usage: node oslcReauestGet.js baseURI resourceURI projectAreaName userId password')
87
process.exit(1)
98
}

examples/simpleCMRead.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@
44
'use strict';
55

66
import OSLCClient from '../OSLCClient.js';
7-
import { oslc_cm } from '../namespaces.js';
8-
import Compact from '../Compact.js';
97

108
// process command line arguments
119
var args = process.argv.slice(2)
12-
if (args.length != 3) {
10+
if (args.length !== 3) {
1311
console.log("Usage: node simpleCMRead.js baseURL resourceURI userId password")
1412
process.exit(1)
1513
}

examples/simpleRMRead.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@
55

66
import OSLCClient from '../OSLCClient.js';
77
import { oslc_cm } from '../namespaces.js';
8-
import Compact from '../Compact.js';
98

109
// process command line arguments
1110
var args = process.argv.slice(2)
12-
if (args.length != 3) {
11+
if (args.length !== 3) {
1312
console.log("Usage: node simpleCMRead.js baseURL resourceURI userId password")
1413
process.exit(1)
1514
}

0 commit comments

Comments
 (0)