@js-soft/node-red-eudiplo-connector 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/eudiplo-issuance.html +164 -0
- package/eudiplo-issuance.js +117 -0
- package/eudiplo-presentation.html +134 -0
- package/eudiplo-presentation.js +105 -0
- package/icons/eudiploLogo.svg +9359 -0
- package/index.js +4 -0
- package/package.json +47 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('eudiplo-issuance', {
|
|
3
|
+
category: 'eudiplo',
|
|
4
|
+
color: '#ffffff',
|
|
5
|
+
defaults: {
|
|
6
|
+
name: { value: '' },
|
|
7
|
+
baseUrl: { value: '' },
|
|
8
|
+
baseUrlOverwrite: { value: false },
|
|
9
|
+
clientId: { value: '' },
|
|
10
|
+
credentialsOverwrite: { value: false },
|
|
11
|
+
flow: { value: 'pre_authorized_code' },
|
|
12
|
+
responseType: { value: 'uri' },
|
|
13
|
+
credentialConfigurationId: { value: '', required: true },
|
|
14
|
+
credentialConfigurationIdSource: { value: 'msg' },
|
|
15
|
+
subjectData: { value: '{}' },
|
|
16
|
+
subjectDataSource: { value: 'json' },
|
|
17
|
+
},
|
|
18
|
+
credentials: {
|
|
19
|
+
clientSecret: { type: 'password' },
|
|
20
|
+
},
|
|
21
|
+
inputs: 1,
|
|
22
|
+
outputs: 1,
|
|
23
|
+
icon: 'eudiploLogo.svg',
|
|
24
|
+
label: function () { return this.name || 'eudiplo issuance'; },
|
|
25
|
+
paletteLabel: 'eudiplo issuance',
|
|
26
|
+
|
|
27
|
+
oneditprepare: function () {
|
|
28
|
+
$('#node-input-credentialConfigurationId').typedInput({
|
|
29
|
+
type: this.credentialConfigurationIdSource || 'msg',
|
|
30
|
+
types: ['msg', 'str'],
|
|
31
|
+
typeField: '#node-input-credentialConfigurationIdSource',
|
|
32
|
+
});
|
|
33
|
+
$('#node-input-subjectData').typedInput({
|
|
34
|
+
type: this.subjectDataSource || 'json',
|
|
35
|
+
types: ['msg', 'json'],
|
|
36
|
+
typeField: '#node-input-subjectDataSource',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
$('#node-input-baseUrlEnvVar').val('EUDIPLO_BASE_URL');
|
|
40
|
+
|
|
41
|
+
function toggleBaseUrlOverride() {
|
|
42
|
+
const checked = $('#node-input-baseUrlOverwrite').prop('checked');
|
|
43
|
+
$('#base-url-override-rows').toggle(checked);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function toggleCredentials() {
|
|
47
|
+
const checked = $('#node-input-credentialsOverwrite').prop('checked');
|
|
48
|
+
$('#credentials-override-rows').toggle(checked);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
toggleBaseUrlOverride();
|
|
52
|
+
toggleCredentials();
|
|
53
|
+
$('#node-input-baseUrlOverwrite').on('change', toggleBaseUrlOverride);
|
|
54
|
+
$('#node-input-credentialsOverwrite').on('change', toggleCredentials);
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<script type="text/html" data-template-name="eudiplo-issuance">
|
|
60
|
+
<div class="form-row">
|
|
61
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
62
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div class="form-row">
|
|
66
|
+
<label for="node-input-baseUrlEnvVar"><i class="fa fa-globe"></i> Base URL</label>
|
|
67
|
+
<input type="text" id="node-input-baseUrlEnvVar" readonly>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div class="form-row">
|
|
71
|
+
<input type="checkbox" id="node-input-baseUrlOverwrite" style="width:auto; margin-left: 107px;">
|
|
72
|
+
<label for="node-input-baseUrlOverwrite" style="width:auto;"> Override base URL</label>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div id="base-url-override-rows">
|
|
76
|
+
<div class="form-row">
|
|
77
|
+
<label for="node-input-baseUrl"><i class="fa fa-globe"></i> Base URL</label>
|
|
78
|
+
<input type="text" id="node-input-baseUrl" placeholder="https://eudiplo.example.com">
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<hr/>
|
|
83
|
+
|
|
84
|
+
<div class="form-row">
|
|
85
|
+
<label><i class="fa fa-lock"></i> OAuth2</label>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div class="form-row">
|
|
89
|
+
<input type="checkbox" id="node-input-credentialsOverwrite" style="width:auto; margin-left: 107px;">
|
|
90
|
+
<label for="node-input-credentialsOverwrite" style="width:auto;"> Override credentials</label>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div id="credentials-override-rows">
|
|
94
|
+
<div class="form-row">
|
|
95
|
+
<label for="node-input-clientId"><i class="fa fa-user"></i> Client ID</label>
|
|
96
|
+
<input type="text" id="node-input-clientId">
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div class="form-row">
|
|
100
|
+
<label for="node-input-clientSecret"><i class="fa fa-user-secret"></i> Client Secret</label>
|
|
101
|
+
<input type="password" id="node-input-clientSecret">
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<hr/>
|
|
106
|
+
|
|
107
|
+
<div class="form-row">
|
|
108
|
+
<label for="node-input-flow"><i class="fa fa-code-fork"></i> Flow</label>
|
|
109
|
+
<select id="node-input-flow" style="width:70%">
|
|
110
|
+
<option value="pre_authorized_code">pre_authorized_code</option>
|
|
111
|
+
<option value="authorization_code">authorization_code</option>
|
|
112
|
+
</select>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div class="form-row">
|
|
116
|
+
<label for="node-input-responseType"><i class="fa fa-qrcode"></i> Response Type</label>
|
|
117
|
+
<select id="node-input-responseType" style="width:70%">
|
|
118
|
+
<option value="uri">uri</option>
|
|
119
|
+
<option value="dc-api">dc-api</option>
|
|
120
|
+
</select>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div class="form-row">
|
|
124
|
+
<label for="node-input-credentialConfigurationId"><i class="fa fa-id-card"></i> Credential Configuration ID</label>
|
|
125
|
+
<input type="text" id="node-input-credentialConfigurationId">
|
|
126
|
+
<input type="hidden" id="node-input-credentialConfigurationIdSource">
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<div class="form-row">
|
|
130
|
+
<label for="node-input-subjectData"><i class="fa fa-user-circle"></i> Subject Data</label>
|
|
131
|
+
<input type="text" id="node-input-subjectData">
|
|
132
|
+
<input type="hidden" id="node-input-subjectDataSource">
|
|
133
|
+
</div>
|
|
134
|
+
</script>
|
|
135
|
+
|
|
136
|
+
<script type="text/html" data-help-name="eudiplo-issuance">
|
|
137
|
+
<p>Issues a Verifiable Credential via a Eudiplo server and outputs the <code>credentialOffer</code> URI for the holder's wallet.</p>
|
|
138
|
+
|
|
139
|
+
<h3>Inputs</h3>
|
|
140
|
+
<dl class="message-properties">
|
|
141
|
+
<dt>payload <span class="property-type">any</span></dt>
|
|
142
|
+
<dd>Used to resolve <b>Credential Configuration ID</b> and <b>Subject Data</b> when configured as <code>msg</code> properties.</dd>
|
|
143
|
+
</dl>
|
|
144
|
+
|
|
145
|
+
<h3>Outputs</h3>
|
|
146
|
+
<dl class="message-properties">
|
|
147
|
+
<dt>payload.credentialOffer <span class="property-type">string</span></dt>
|
|
148
|
+
<dd>The OID4VCI credential offer URI to present to the holder's wallet.</dd>
|
|
149
|
+
<dt>payload.sessionId <span class="property-type">string</span></dt>
|
|
150
|
+
<dd>Session ID for polling issuance status.</dd>
|
|
151
|
+
</dl>
|
|
152
|
+
|
|
153
|
+
<h3>Details</h3>
|
|
154
|
+
<ul>
|
|
155
|
+
<li><b>Base URL</b> — By default the node reads the server URL from the <code>EUDIPLO_BASE_URL</code> environment variable shown in the editor.</li>
|
|
156
|
+
<li><b>Base URL Override</b> — Enable this to provide a node-specific Base URL instead of using <code>EUDIPLO_BASE_URL</code>.</li>
|
|
157
|
+
<li><b>Client ID / Secret</b> — OAuth2 client credentials (Client Credentials grant). The secret is stored encrypted. The SDK uses <code>${baseUrl}/oauth2/token</code> as the token endpoint.</li>
|
|
158
|
+
<li><b>Credential Configuration ID</b> — The type identifier of the credential to issue. Can be read from a <code>msg</code> property or set as a static string.</li>
|
|
159
|
+
<li><b>Subject Data</b> — JSON object of subject claims/attributes. Can be a <code>msg</code> property or a static JSON object.</li>
|
|
160
|
+
</ul>
|
|
161
|
+
|
|
162
|
+
<h3>Notes</h3>
|
|
163
|
+
<p>Uses the <code>@eudiplo/sdk-core</code> package. The OAuth2 access token is cached and automatically refreshed by the SDK.</p>
|
|
164
|
+
</script>
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const { EudiploClient } = require('@eudiplo/sdk-core');
|
|
2
|
+
|
|
3
|
+
const BASE_URL_ENV_VAR = 'EUDIPLO_BASE_URL';
|
|
4
|
+
|
|
5
|
+
module.exports = function (RED) {
|
|
6
|
+
function EudiploIssuanceNode(config) {
|
|
7
|
+
RED.nodes.createNode(this, config);
|
|
8
|
+
const node = this;
|
|
9
|
+
|
|
10
|
+
let clientId = process.env.EUDIPLO_CLIENT_ID;
|
|
11
|
+
let password = process.env.EUDIPLO_CLIENT_SECRET;
|
|
12
|
+
|
|
13
|
+
if (config.credentialsOverwrite) {
|
|
14
|
+
node.log('Overwriting eudiplo-credentials from environment variables');
|
|
15
|
+
clientId = config.clientId;
|
|
16
|
+
password = node.credentials.clientSecret;
|
|
17
|
+
} else {
|
|
18
|
+
node.log('Using eudiplo-credentials from environment variables');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const baseUrl = config.baseUrlOverwrite ? config.baseUrl : process.env[BASE_URL_ENV_VAR];
|
|
22
|
+
|
|
23
|
+
if (config.baseUrlOverwrite) {
|
|
24
|
+
node.log('Overwriting eudiplo base URL from node configuration');
|
|
25
|
+
} else {
|
|
26
|
+
node.log(`Using eudiplo base URL from environment variable ${BASE_URL_ENV_VAR}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let configurationError = null;
|
|
30
|
+
if (!baseUrl) {
|
|
31
|
+
configurationError = `Missing Eudiplo base URL. Set ${BASE_URL_ENV_VAR} or enable the Base URL override in the node configuration.`;
|
|
32
|
+
node.status({ fill: 'red', shape: 'ring', text: 'missing base URL' });
|
|
33
|
+
node.error(configurationError);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// EudiploClient handles token acquisition and refresh internally.
|
|
37
|
+
// The SDK uses ${baseUrl}/oauth2/token as the token endpoint.
|
|
38
|
+
let sdkClient = null;
|
|
39
|
+
if (!configurationError) {
|
|
40
|
+
sdkClient = new EudiploClient({
|
|
41
|
+
baseUrl,
|
|
42
|
+
clientId: clientId,
|
|
43
|
+
clientSecret: password
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
node.on('input', async function (msg, send, done) {
|
|
48
|
+
if (configurationError) {
|
|
49
|
+
node.status({ fill: 'red', shape: 'ring', text: 'missing base URL' });
|
|
50
|
+
return done(new Error(configurationError));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
// ── Resolve credentialConfigurationIds ──────────────────────────
|
|
55
|
+
const rawId =
|
|
56
|
+
config.credentialConfigurationIdSource === 'msg'
|
|
57
|
+
? RED.util.getMessageProperty(msg, config.credentialConfigurationId)
|
|
58
|
+
: config.credentialConfigurationId;
|
|
59
|
+
|
|
60
|
+
if (!rawId) {
|
|
61
|
+
return done(new Error('Credential Configuration ID is empty'));
|
|
62
|
+
}
|
|
63
|
+
const credentialConfigurationIds = Array.isArray(rawId) ? rawId : [rawId];
|
|
64
|
+
|
|
65
|
+
// ── Resolve subject data / claims ──────────────────────────────
|
|
66
|
+
let subjectData;
|
|
67
|
+
if (config.subjectDataSource === 'msg') {
|
|
68
|
+
subjectData = RED.util.getMessageProperty(msg, config.subjectData);
|
|
69
|
+
} else {
|
|
70
|
+
try {
|
|
71
|
+
subjectData = JSON.parse(config.subjectData || '{}');
|
|
72
|
+
} catch {
|
|
73
|
+
subjectData = {};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let claims;
|
|
78
|
+
if (subjectData && Object.keys(subjectData).length > 0) {
|
|
79
|
+
claims = { [credentialConfigurationIds[0]]: subjectData };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
node.status({ fill: 'blue', shape: 'dot', text: 'issuing…' });
|
|
83
|
+
|
|
84
|
+
const result = await sdkClient.createIssuanceOffer({
|
|
85
|
+
credentialConfigurationIds,
|
|
86
|
+
claims,
|
|
87
|
+
flow: config.flow || 'pre_authorized_code',
|
|
88
|
+
responseType: config.responseType || 'uri',
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
node.log('Issuance result: ' + JSON.stringify(result));
|
|
92
|
+
|
|
93
|
+
if (!msg.payload || typeof msg.payload !== 'object') {
|
|
94
|
+
msg.payload = {};
|
|
95
|
+
}
|
|
96
|
+
msg.payload.credentialOffer = result.uri;
|
|
97
|
+
msg.payload.sessionId = result.sessionId;
|
|
98
|
+
node.status({});
|
|
99
|
+
send(msg);
|
|
100
|
+
done();
|
|
101
|
+
} catch (err) {
|
|
102
|
+
node.status({ fill: 'red', shape: 'ring', text: err.message });
|
|
103
|
+
done(err);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
node.on('close', function () {
|
|
108
|
+
sdkClient = null;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
RED.nodes.registerType('eudiplo-issuance', EudiploIssuanceNode, {
|
|
113
|
+
credentials: {
|
|
114
|
+
clientSecret: { type: 'password' },
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('eudiplo-presentation', {
|
|
3
|
+
category: 'eudiplo',
|
|
4
|
+
color: '#ffffff',
|
|
5
|
+
defaults: {
|
|
6
|
+
name: { value: '' },
|
|
7
|
+
baseUrl: { value: '' },
|
|
8
|
+
baseUrlOverwrite: { value: false },
|
|
9
|
+
clientId: { value: '' },
|
|
10
|
+
credentialsOverwrite: { value: false },
|
|
11
|
+
presentationConfigId: { value: '', required: true },
|
|
12
|
+
presentationConfigIdSource: { value: 'str' },
|
|
13
|
+
},
|
|
14
|
+
credentials: {
|
|
15
|
+
clientSecret: { type: 'password' },
|
|
16
|
+
},
|
|
17
|
+
inputs: 1,
|
|
18
|
+
outputs: 1,
|
|
19
|
+
icon: 'eudiploLogo.svg',
|
|
20
|
+
label: function () { return this.name || 'eudiplo presentation'; },
|
|
21
|
+
paletteLabel: 'eudiplo presentation',
|
|
22
|
+
|
|
23
|
+
oneditprepare: function () {
|
|
24
|
+
$('#node-input-presentationConfigId').typedInput({
|
|
25
|
+
type: this.presentationConfigIdSource || 'str',
|
|
26
|
+
types: ['msg', 'str'],
|
|
27
|
+
typeField: '#node-input-presentationConfigIdSource',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
$('#node-input-baseUrlEnvVar').val('EUDIPLO_BASE_URL');
|
|
31
|
+
|
|
32
|
+
function toggleBaseUrlOverride() {
|
|
33
|
+
const checked = $('#node-input-baseUrlOverwrite').prop('checked');
|
|
34
|
+
$('#base-url-override-rows').toggle(checked);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function toggleCredentials() {
|
|
38
|
+
const checked = $('#node-input-credentialsOverwrite').prop('checked');
|
|
39
|
+
$('#credentials-override-rows').toggle(checked);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
toggleBaseUrlOverride();
|
|
43
|
+
toggleCredentials();
|
|
44
|
+
$('#node-input-baseUrlOverwrite').on('change', toggleBaseUrlOverride);
|
|
45
|
+
$('#node-input-credentialsOverwrite').on('change', toggleCredentials);
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<script type="text/html" data-template-name="eudiplo-presentation">
|
|
51
|
+
<div class="form-row">
|
|
52
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
53
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div class="form-row">
|
|
57
|
+
<label for="node-input-baseUrlEnvVar"><i class="fa fa-globe"></i> Base URL</label>
|
|
58
|
+
<input type="text" id="node-input-baseUrlEnvVar" readonly>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="form-row">
|
|
62
|
+
<input type="checkbox" id="node-input-baseUrlOverwrite" style="width:auto; margin-left: 107px;">
|
|
63
|
+
<label for="node-input-baseUrlOverwrite" style="width:auto;"> Override base URL</label>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div id="base-url-override-rows">
|
|
67
|
+
<div class="form-row">
|
|
68
|
+
<label for="node-input-baseUrl"><i class="fa fa-globe"></i> Base URL</label>
|
|
69
|
+
<input type="text" id="node-input-baseUrl" placeholder="https://eudiplo.example.com">
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<hr/>
|
|
74
|
+
|
|
75
|
+
<div class="form-row">
|
|
76
|
+
<label><i class="fa fa-lock"></i> OAuth2</label>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div class="form-row">
|
|
80
|
+
<input type="checkbox" id="node-input-credentialsOverwrite" style="width:auto; margin-left: 107px;">
|
|
81
|
+
<label for="node-input-credentialsOverwrite" style="width:auto;"> Override credentials</label>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<div id="credentials-override-rows">
|
|
85
|
+
<div class="form-row">
|
|
86
|
+
<label for="node-input-clientId"><i class="fa fa-user"></i> Client ID</label>
|
|
87
|
+
<input type="text" id="node-input-clientId">
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div class="form-row">
|
|
91
|
+
<label for="node-input-clientSecret"><i class="fa fa-user-secret"></i> Client Secret</label>
|
|
92
|
+
<input type="password" id="node-input-clientSecret">
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<hr/>
|
|
97
|
+
|
|
98
|
+
<div class="form-row">
|
|
99
|
+
<label for="node-input-presentationConfigId"><i class="fa fa-id-card"></i> Presentation Config ID</label>
|
|
100
|
+
<input type="text" id="node-input-presentationConfigId">
|
|
101
|
+
<input type="hidden" id="node-input-presentationConfigIdSource">
|
|
102
|
+
</div>
|
|
103
|
+
</script>
|
|
104
|
+
|
|
105
|
+
<script type="text/html" data-help-name="eudiplo-presentation">
|
|
106
|
+
<p>Creates a Verifiable Presentation request via a Eudiplo server and outputs the presentation URI for the holder's wallet.</p>
|
|
107
|
+
|
|
108
|
+
<h3>Inputs</h3>
|
|
109
|
+
<dl class="message-properties">
|
|
110
|
+
<dt>payload <span class="property-type">any</span></dt>
|
|
111
|
+
<dd>Used to resolve the <b>Presentation Config ID</b> when configured as a <code>msg</code> property.</dd>
|
|
112
|
+
</dl>
|
|
113
|
+
|
|
114
|
+
<h3>Outputs</h3>
|
|
115
|
+
<dl class="message-properties">
|
|
116
|
+
<dt>payload.presentationUri <span class="property-type">string</span></dt>
|
|
117
|
+
<dd>Same-device OID4VP presentation request URI (wallet on same device, redirects back after completion).</dd>
|
|
118
|
+
<dt>payload.crossDeviceUri <span class="property-type">string</span></dt>
|
|
119
|
+
<dd>Cross-device URI suitable for displaying as a QR code (no redirect; poll <code>sessionId</code> for status).</dd>
|
|
120
|
+
<dt>payload.sessionId <span class="property-type">string</span></dt>
|
|
121
|
+
<dd>Session ID for polling presentation status.</dd>
|
|
122
|
+
</dl>
|
|
123
|
+
|
|
124
|
+
<h3>Details</h3>
|
|
125
|
+
<ul>
|
|
126
|
+
<li><b>Base URL</b> — By default the node reads the server URL from the <code>EUDIPLO_BASE_URL</code> environment variable shown in the editor.</li>
|
|
127
|
+
<li><b>Base URL Override</b> — Enable this to provide a node-specific Base URL instead of using <code>EUDIPLO_BASE_URL</code>.</li>
|
|
128
|
+
<li><b>Client ID / Secret</b> — OAuth2 client credentials (Client Credentials grant). The secret is stored encrypted. The SDK uses <code>${baseUrl}/oauth2/token</code> as the token endpoint.</li>
|
|
129
|
+
<li><b>Presentation Config ID</b> — The ID of the presentation configuration that defines which credentials are required. Can be read from a <code>msg</code> property or set as a static string.</li>
|
|
130
|
+
</ul>
|
|
131
|
+
|
|
132
|
+
<h3>Notes</h3>
|
|
133
|
+
<p>Uses the <code>@eudiplo/sdk-core</code> package. The OAuth2 access token is cached and automatically refreshed by the SDK.</p>
|
|
134
|
+
</script>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const { EudiploClient } = require('@eudiplo/sdk-core');
|
|
2
|
+
|
|
3
|
+
const BASE_URL_ENV_VAR = 'EUDIPLO_BASE_URL';
|
|
4
|
+
|
|
5
|
+
module.exports = function (RED) {
|
|
6
|
+
function EudiploPresentationNode(config) {
|
|
7
|
+
RED.nodes.createNode(this, config);
|
|
8
|
+
const node = this;
|
|
9
|
+
|
|
10
|
+
let clientId = process.env.EUDIPLO_CLIENT_ID;
|
|
11
|
+
let clientSecret = process.env.EUDIPLO_CLIENT_SECRET;
|
|
12
|
+
|
|
13
|
+
if (config.credentialsOverwrite) {
|
|
14
|
+
clientId = config.clientId;
|
|
15
|
+
clientSecret = node.credentials.clientSecret;
|
|
16
|
+
node.log('Overwriting eudiplo-credentials from environment variables ');
|
|
17
|
+
} else {
|
|
18
|
+
node.log('Using eudiplo-credentials from environment variables');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const baseUrl = config.baseUrlOverwrite ? config.baseUrl : process.env[BASE_URL_ENV_VAR];
|
|
22
|
+
|
|
23
|
+
if (config.baseUrlOverwrite) {
|
|
24
|
+
node.log('Overwriting eudiplo base URL from node configuration');
|
|
25
|
+
} else {
|
|
26
|
+
node.log(`Using eudiplo base URL from environment variable ${BASE_URL_ENV_VAR}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let configurationError = null;
|
|
30
|
+
if (!baseUrl) {
|
|
31
|
+
configurationError = `Missing Eudiplo base URL. Set ${BASE_URL_ENV_VAR} or enable the Base URL override in the node configuration.`;
|
|
32
|
+
node.status({ fill: 'red', shape: 'ring', text: 'missing base URL' });
|
|
33
|
+
node.error(configurationError);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// EudiploClient handles token acquisition and refresh internally.
|
|
37
|
+
// The SDK uses ${baseUrl}/oauth2/token as the token endpoint.
|
|
38
|
+
let sdkClient = null;
|
|
39
|
+
if (!configurationError) {
|
|
40
|
+
sdkClient = new EudiploClient({
|
|
41
|
+
baseUrl,
|
|
42
|
+
clientId: clientId,
|
|
43
|
+
clientSecret: clientSecret,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
node.on('input', async function (msg, send, done) {
|
|
48
|
+
if (configurationError) {
|
|
49
|
+
node.status({ fill: 'red', shape: 'ring', text: 'missing base URL' });
|
|
50
|
+
return done(new Error(configurationError));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
// ── Resolve presentation config ID ───────────────────────────────
|
|
55
|
+
const configId =
|
|
56
|
+
config.presentationConfigIdSource === 'msg'
|
|
57
|
+
? RED.util.getMessageProperty(msg, config.presentationConfigId)
|
|
58
|
+
: config.presentationConfigId;
|
|
59
|
+
|
|
60
|
+
if (!configId) {
|
|
61
|
+
return done(new Error('Presentation Config ID is empty'));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
node.status({ fill: 'blue', shape: 'dot', text: 'requesting…' });
|
|
65
|
+
let result;
|
|
66
|
+
try {
|
|
67
|
+
result = await sdkClient.createPresentationRequest({
|
|
68
|
+
configId,
|
|
69
|
+
responseType: 'uri',
|
|
70
|
+
});
|
|
71
|
+
} catch (error) {
|
|
72
|
+
msg.payload.eudiploSuccess = false;
|
|
73
|
+
send(msg);
|
|
74
|
+
return done(error);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
node.log('Presentation result: ' + JSON.stringify(result));
|
|
78
|
+
|
|
79
|
+
if (!msg.payload || typeof msg.payload !== 'object') {
|
|
80
|
+
msg.payload = {};
|
|
81
|
+
}
|
|
82
|
+
msg.payload.presentationUri = result.uri;
|
|
83
|
+
msg.payload.crossDeviceUri = result.crossDeviceUri;
|
|
84
|
+
msg.payload.sessionId = result.sessionId;
|
|
85
|
+
msg.payload.eudiploSuccess = true;
|
|
86
|
+
node.status({});
|
|
87
|
+
send(msg);
|
|
88
|
+
done();
|
|
89
|
+
} catch (err) {
|
|
90
|
+
node.status({ fill: 'red', shape: 'ring', text: err.message });
|
|
91
|
+
done(err);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
node.on('close', function () {
|
|
96
|
+
sdkClient = null;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
RED.nodes.registerType('eudiplo-presentation', EudiploPresentationNode, {
|
|
101
|
+
credentials: {
|
|
102
|
+
clientSecret: { type: 'password' },
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
};
|