@learncard/vc-plugin 1.5.13 → 1.6.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/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/issueCredential.d.ts +2 -2
- package/dist/issuePresentation.d.ts +6 -6
- package/dist/refreshCredential.d.ts +13 -0
- package/dist/refreshCredential.d.ts.map +1 -0
- package/dist/refreshCredential.fetch.d.ts +13 -0
- package/dist/refreshCredential.fetch.d.ts.map +1 -0
- package/dist/refreshCredential.fetch.test.d.ts +2 -0
- package/dist/refreshCredential.fetch.test.d.ts.map +1 -0
- package/dist/refreshCredential.test.d.ts +2 -0
- package/dist/refreshCredential.test.d.ts.map +1 -0
- package/dist/types.d.ts +32 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/vc-plugin.cjs.development.cjs +626 -1
- package/dist/vc-plugin.cjs.development.cjs.map +4 -4
- package/dist/vc-plugin.cjs.production.min.cjs +1 -1
- package/dist/vc-plugin.cjs.production.min.cjs.map +4 -4
- package/dist/vc-plugin.esm.js +635 -1
- package/dist/vc-plugin.esm.js.map +4 -4
- package/dist/vc.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/index.ts +1 -0
- package/src/refreshCredential.fetch.test.ts +110 -0
- package/src/refreshCredential.fetch.ts +159 -0
- package/src/refreshCredential.test.ts +1144 -0
- package/src/refreshCredential.ts +931 -0
- package/src/types.ts +36 -0
- package/src/vc.ts +2 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
export type PinnedAddress = { address: string; family: 4 | 6 };
|
|
2
|
+
|
|
3
|
+
type NodeLookupCallback = (
|
|
4
|
+
error: Error | null,
|
|
5
|
+
address: string | Array<PinnedAddress>,
|
|
6
|
+
family?: number
|
|
7
|
+
) => void;
|
|
8
|
+
|
|
9
|
+
type NodeResponse = {
|
|
10
|
+
statusCode?: number;
|
|
11
|
+
statusMessage?: string;
|
|
12
|
+
headers: Record<string, string | string[] | undefined>;
|
|
13
|
+
on: (event: string, listener: (...args: unknown[]) => void) => void;
|
|
14
|
+
destroy: (error?: Error) => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type NodeRequest = {
|
|
18
|
+
on: (event: string, listener: (error: Error) => void) => void;
|
|
19
|
+
end: () => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type NodeHttpClient = {
|
|
23
|
+
request: (
|
|
24
|
+
url: URL,
|
|
25
|
+
options: Record<string, unknown>,
|
|
26
|
+
onResponse: (response: NodeResponse) => void
|
|
27
|
+
) => NodeRequest;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const isNodeRuntime = typeof process !== 'undefined' && !!process.versions?.node;
|
|
31
|
+
|
|
32
|
+
const toResponseHeaders = (headers: Record<string, string | string[] | undefined>): Headers => {
|
|
33
|
+
const result = new Headers();
|
|
34
|
+
|
|
35
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
36
|
+
if (Array.isArray(value)) value.forEach(entry => result.append(name, entry));
|
|
37
|
+
else if (value !== undefined) result.append(name, value);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Fetches through an already-validated IP address in Node without changing the URL
|
|
45
|
+
* hostname. Keeping the original URL lets Node perform SNI and certificate hostname
|
|
46
|
+
* verification against the refresh endpoint while the custom lookup prevents a
|
|
47
|
+
* second, potentially rebound DNS answer from selecting the socket destination.
|
|
48
|
+
* Browsers do not expose connection-level DNS controls and therefore use native fetch.
|
|
49
|
+
*/
|
|
50
|
+
export const fetchWithPinnedAddress = async (
|
|
51
|
+
url: URL,
|
|
52
|
+
init: RequestInit,
|
|
53
|
+
pinnedAddress?: PinnedAddress
|
|
54
|
+
): Promise<Response> => {
|
|
55
|
+
if (!isNodeRuntime || !pinnedAddress) return globalThis.fetch(url.href, init);
|
|
56
|
+
|
|
57
|
+
const specifier = url.protocol === 'https:' ? 'node:https' : 'node:http';
|
|
58
|
+
const client = (await import(/* @vite-ignore */ specifier)) as unknown as NodeHttpClient;
|
|
59
|
+
|
|
60
|
+
return new Promise<Response>((resolve, reject) => {
|
|
61
|
+
const lookup = (
|
|
62
|
+
_hostname: string,
|
|
63
|
+
options: { all?: boolean },
|
|
64
|
+
callback: NodeLookupCallback
|
|
65
|
+
): void => {
|
|
66
|
+
if (options?.all) callback(null, [pinnedAddress]);
|
|
67
|
+
else callback(null, pinnedAddress.address, pinnedAddress.family);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
let request: NodeRequest;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const headers = Object.fromEntries(new Headers(init.headers).entries());
|
|
74
|
+
|
|
75
|
+
headers['accept-encoding'] = 'identity';
|
|
76
|
+
|
|
77
|
+
request = client.request(
|
|
78
|
+
url,
|
|
79
|
+
{
|
|
80
|
+
method: init.method,
|
|
81
|
+
headers,
|
|
82
|
+
signal: init.signal,
|
|
83
|
+
lookup,
|
|
84
|
+
// A shared agent could reuse a socket created for a different DNS
|
|
85
|
+
// answer and bypass this request's validated pin.
|
|
86
|
+
agent: false,
|
|
87
|
+
...(url.protocol === 'https:' ? { servername: url.hostname } : {}),
|
|
88
|
+
},
|
|
89
|
+
nodeResponse => {
|
|
90
|
+
try {
|
|
91
|
+
const status = nodeResponse.statusCode;
|
|
92
|
+
if (
|
|
93
|
+
status === undefined ||
|
|
94
|
+
!Number.isInteger(status) ||
|
|
95
|
+
status < 200 ||
|
|
96
|
+
status > 599
|
|
97
|
+
) {
|
|
98
|
+
reject(new RangeError('Invalid refresh response status'));
|
|
99
|
+
nodeResponse.destroy();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const hasBody = status !== 204 && status !== 205 && status !== 304;
|
|
103
|
+
const body = hasBody
|
|
104
|
+
? new ReadableStream<Uint8Array>({
|
|
105
|
+
start(controller) {
|
|
106
|
+
nodeResponse.on('data', chunk => {
|
|
107
|
+
if (typeof chunk === 'string') {
|
|
108
|
+
controller.enqueue(new TextEncoder().encode(chunk));
|
|
109
|
+
} else if (chunk instanceof Uint8Array) {
|
|
110
|
+
controller.enqueue(chunk);
|
|
111
|
+
} else {
|
|
112
|
+
controller.error(
|
|
113
|
+
new TypeError('Unsupported response chunk')
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
nodeResponse.on('end', () => controller.close());
|
|
118
|
+
nodeResponse.on('error', error => controller.error(error));
|
|
119
|
+
nodeResponse.on('aborted', () =>
|
|
120
|
+
controller.error(
|
|
121
|
+
new DOMException(
|
|
122
|
+
'The operation was aborted',
|
|
123
|
+
'AbortError'
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
);
|
|
127
|
+
},
|
|
128
|
+
cancel(reason) {
|
|
129
|
+
nodeResponse.destroy(
|
|
130
|
+
reason instanceof Error
|
|
131
|
+
? reason
|
|
132
|
+
: new Error(String(reason))
|
|
133
|
+
);
|
|
134
|
+
},
|
|
135
|
+
})
|
|
136
|
+
: null;
|
|
137
|
+
|
|
138
|
+
resolve(
|
|
139
|
+
new Response(body, {
|
|
140
|
+
status,
|
|
141
|
+
statusText: nodeResponse.statusMessage,
|
|
142
|
+
headers: toResponseHeaders(nodeResponse.headers),
|
|
143
|
+
})
|
|
144
|
+
);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
reject(error);
|
|
147
|
+
nodeResponse.destroy();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
reject(error);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
request.on('error', reject);
|
|
157
|
+
request.end();
|
|
158
|
+
});
|
|
159
|
+
};
|