heimdall-tide 0.1.0

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.
Files changed (42) hide show
  1. package/LICENSE +334 -0
  2. package/README.md +3 -0
  3. package/dist/cjs/enclaves/ApprovalEnclave.d.ts +12 -0
  4. package/dist/cjs/enclaves/ApprovalEnclave.js +57 -0
  5. package/dist/cjs/enclaves/ApprovalEnclave.js.map +1 -0
  6. package/dist/cjs/enclaves/RequestEnclave.d.ts +38 -0
  7. package/dist/cjs/enclaves/RequestEnclave.js +204 -0
  8. package/dist/cjs/enclaves/RequestEnclave.js.map +1 -0
  9. package/dist/cjs/heimdall.d.ts +46 -0
  10. package/dist/cjs/heimdall.js +168 -0
  11. package/dist/cjs/heimdall.js.map +1 -0
  12. package/dist/cjs/index.d.ts +4 -0
  13. package/dist/cjs/index.js +8 -0
  14. package/dist/cjs/index.js.map +1 -0
  15. package/dist/cjs/wrapper.d.ts +16 -0
  16. package/dist/cjs/wrapper.js +232 -0
  17. package/dist/cjs/wrapper.js.map +1 -0
  18. package/dist/esm/enclaves/ApprovalEnclave.d.ts +12 -0
  19. package/dist/esm/enclaves/ApprovalEnclave.js +53 -0
  20. package/dist/esm/enclaves/ApprovalEnclave.js.map +1 -0
  21. package/dist/esm/enclaves/RequestEnclave.d.ts +38 -0
  22. package/dist/esm/enclaves/RequestEnclave.js +200 -0
  23. package/dist/esm/enclaves/RequestEnclave.js.map +1 -0
  24. package/dist/esm/heimdall.d.ts +46 -0
  25. package/dist/esm/heimdall.js +164 -0
  26. package/dist/esm/heimdall.js.map +1 -0
  27. package/dist/esm/index.d.ts +4 -0
  28. package/dist/esm/index.js +5 -0
  29. package/dist/esm/index.js.map +1 -0
  30. package/dist/esm/wrapper.d.ts +16 -0
  31. package/dist/esm/wrapper.js +225 -0
  32. package/dist/esm/wrapper.js.map +1 -0
  33. package/heimdall-tide-0.1.0.tgz +0 -0
  34. package/package.json +44 -0
  35. package/src/enclaves/ApprovalEnclave.ts +63 -0
  36. package/src/enclaves/RequestEnclave.ts +237 -0
  37. package/src/heimdall.ts +204 -0
  38. package/src/index.ts +4 -0
  39. package/src/wrapper.ts +258 -0
  40. package/tsconfig.cjs.json +9 -0
  41. package/tsconfig.esm.json +10 -0
  42. package/tsconfig.json +9 -0
@@ -0,0 +1,204 @@
1
+ //
2
+ // Tide Protocol - Infrastructure for a TRUE Zero-Trust paradigm
3
+ // Copyright (C) 2022 Tide Foundation Ltd
4
+ //
5
+ // This program is free software and is subject to the terms of
6
+ // the Tide Community Open Code License as published by the
7
+ // Tide Foundation Limited. You may modify it and redistribute
8
+ // it in accordance with and subject to the terms of that License.
9
+ // This program is distributed WITHOUT WARRANTY of any kind,
10
+ // including without any implied warranty of MERCHANTABILITY or
11
+ // FITNESS FOR A PARTICULAR PURPOSE.
12
+ // See the Tide Community Open Code License for more details.
13
+ // You should have received a copy of the Tide Community Open
14
+ // Code License along with this program.
15
+ // If not, see https://tide.org/licenses_tcoc2-0-0-en
16
+
17
+
18
+ //
19
+ export interface HeimdallConstructor{
20
+ vendorId: string;
21
+ homeOrkOrigin: string,
22
+ voucherURL: string,
23
+ signed_client_origin: string;
24
+ }
25
+ export abstract class Heimdall<T> implements EnclaveFlow<T> {
26
+ name: string;
27
+ _windowType: windowType;
28
+ enclaveOrigin: string;
29
+ voucherURL: string;
30
+ signed_client_origin: string;
31
+ vendorId: string;
32
+
33
+ private enclaveWindow: WindowProxy;
34
+
35
+ constructor(init: HeimdallConstructor){
36
+ this.enclaveOrigin = init.homeOrkOrigin;
37
+ this.voucherURL = init.voucherURL;
38
+ this.signed_client_origin = init.signed_client_origin;
39
+ this.vendorId = init.vendorId;
40
+ }
41
+
42
+ enclaveClosed(){
43
+ return this.enclaveWindow.closed;
44
+ }
45
+
46
+ getOrkUrl(): URL {
47
+ throw new Error("Method not implemented.");
48
+ }
49
+
50
+ public async open(): Promise<boolean> {
51
+ switch(this._windowType){
52
+ case windowType.Popup:
53
+ return this.openPopUp();
54
+ case windowType.Redirect:
55
+ throw new Error("Method not implemented.");
56
+ case windowType.Hidden:
57
+ return this.openHiddenIframe();
58
+ }
59
+ }
60
+ public send(data: any): void {
61
+ switch(this._windowType){
62
+ case windowType.Popup:
63
+ this.sendPostWindowMessage(data);
64
+ break;
65
+ case windowType.Redirect:
66
+ throw new Error("Method not implemented.");
67
+ case windowType.Hidden:
68
+ this.sendPostWindowMessage(data);
69
+ break;
70
+ }
71
+ }
72
+ public async recieve(type: string): Promise<any> {
73
+ switch(this._windowType){
74
+ case windowType.Popup:
75
+ return this.waitForWindowPostMessage(type);
76
+ case windowType.Redirect:
77
+ throw new Error("Method not implemented.");
78
+ case windowType.Hidden:
79
+ return this.waitForWindowPostMessage(type);
80
+ }
81
+ }
82
+ public close() {
83
+ switch(this._windowType){
84
+ case windowType.Popup:
85
+ this.closePopupEnclave();
86
+ break;
87
+ case windowType.Redirect:
88
+ throw new Error("Method not implemented.");
89
+ case windowType.Hidden:
90
+ this.closeHiddenIframe();
91
+ break;
92
+ default:
93
+ throw "Unknown window type";
94
+ }
95
+ }
96
+
97
+ onerror(data: any): void {
98
+ throw new Error("Method not implemented.");
99
+ }
100
+
101
+ private async openPopUp(): Promise<boolean> {
102
+ const left_pos = (window.length / 2) - 400;
103
+ const w = window.open(this.getOrkUrl(), "_blank", `width=800,height=800,left=${left_pos}`);
104
+ if(!w) return false;
105
+ this.enclaveWindow = w;
106
+ await this.waitForWindowPostMessage("pageLoaded"); // we need to wait for the page to load before we send sensitive data
107
+ return true;
108
+ }
109
+
110
+ private async closeHiddenIframe(){
111
+ window.document
112
+ .querySelectorAll<HTMLIFrameElement>('iframe#heimdall')
113
+ .forEach(iframe => iframe.remove());
114
+ }
115
+
116
+ private async openHiddenIframe() {
117
+ // Remove any existing iframes with heimdall id
118
+ this.closeHiddenIframe();
119
+
120
+ // 1. Create the iframe
121
+ const iframe = document.createElement('iframe');
122
+ iframe.src = this.getOrkUrl().toString();
123
+ iframe.style.display = 'none'; // hide it visually
124
+ iframe.id = "heimdall"; // in case multiple frames get popped up - we only want one
125
+ iframe.setAttribute('aria-hidden', 'true'); // accessibility hint
126
+
127
+ // 2. Add it to the document
128
+ document.body.appendChild(iframe);
129
+
130
+ // 3. Keep a reference to its window for postMessage
131
+ this.enclaveWindow = iframe.contentWindow;
132
+ if (!this.enclaveWindow) return false;
133
+
134
+ // 4. Wait for the iframe to signal it’s ready
135
+ await this.waitForWindowPostMessage("pageLoaded");
136
+
137
+ return true;
138
+ }
139
+
140
+ private closePopupEnclave() {
141
+ this.enclaveWindow.close();
142
+ }
143
+
144
+ private async waitForWindowPostMessage(responseTypeToAwait: string) {
145
+ return new Promise((resolve) => {
146
+ const handler = (event) => {
147
+ const response = this.processEvent(event.data, event.origin, responseTypeToAwait);
148
+ if (response.ok) {
149
+ resolve(response.message);
150
+ window.removeEventListener("message", handler);
151
+ } else {
152
+ if(response.print) console.error("[HEIMDALL] Recieved enclave error: " + response.error);
153
+ }
154
+ };
155
+ window.addEventListener("message", handler, false);
156
+ });
157
+ }
158
+
159
+ private sendPostWindowMessage(message: any) {
160
+ this.enclaveWindow.postMessage(message, this.enclaveOrigin);
161
+ }
162
+
163
+ private processEvent(data: any, origin: string, expectedType: string){
164
+ if (origin !== this.enclaveOrigin) {
165
+ // Something's not right... The message has come from an unknown domain...
166
+ return {ok: false, print: false, error: "WRONG WINDOW SENT MESSAGE"};
167
+ }
168
+
169
+ switch (data.type) {
170
+ case "newORKUrl":
171
+ this.enclaveOrigin = new URL(data.url).origin;
172
+ break;
173
+ case "error":
174
+ this.onerror(data);
175
+ return {ok: false, print: false, error: "handled error"}
176
+ }
177
+
178
+ if(expectedType !== data.type) {
179
+ console.log("[HEIMDALL] Received type{" + data.type + "} but waiting for type{" + expectedType + "}");
180
+ return {ok: false, print: false, error: "handled error"}
181
+ }else{
182
+ console.log("[HEIMDALL] Correctly received type{" + data.type + "}");
183
+ return {ok: true, message: data.message}
184
+ }
185
+ }
186
+ }
187
+ export enum windowType{
188
+ Popup,
189
+ Redirect,
190
+ Hidden
191
+ };
192
+ interface EnclaveFlow<T>{
193
+ name: string;
194
+ _windowType: windowType;
195
+
196
+ open(): Promise<boolean>;
197
+ send(data: any): void;
198
+ recieve(type: string): Promise<any>;
199
+ close(): void;
200
+
201
+ onerror(data: any): void;
202
+
203
+ getOrkUrl(): URL;
204
+ };
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { ApprovalEnclave } from "./enclaves/ApprovalEnclave"
2
+ import { RequestEnclave } from "./enclaves/RequestEnclave"
3
+ export { ApprovalEnclave }
4
+ export { RequestEnclave }
package/src/wrapper.ts ADDED
@@ -0,0 +1,258 @@
1
+ export const version = "1";
2
+
3
+ export function wrapper(arr: NestedEntry): TideMemory {
4
+ // If array is only Uint8Arrays - create a TideMemory out of it
5
+ // If there is any entry in an array that is another array
6
+ // -> Go inside that array and repeat the process
7
+ if(arr.every(a => a instanceof Uint8Array)) return TideMemory.CreateFromArray(arr);
8
+ else {
9
+ // Go through each entry
10
+ arr.forEach((a) => {
11
+ // If the entry is an array, apply the wappa on it
12
+ if(Array.isArray(a)){
13
+ // Reassign the value of the entry -> to the serialized wrapper
14
+ a = wrapper(a);
15
+ }else if(a["value"]){
16
+ // Let's check if is a number, boolean or Uint8Array. If none of those, it'll be null
17
+ const res = encode(a["value"]);
18
+ if(res){
19
+ // serialized correctly
20
+ a = res;
21
+ }else{
22
+ if(typeof a["value"] == "string"){
23
+ // Serialize it into Uint8Array
24
+ if(!a["encoding"]){
25
+ // No encoding provided
26
+ // Let's default to UTF-8
27
+ a = encodeStr(a["value"], "UTF-8");
28
+ }else{
29
+ a = encodeStr(a["value"], a["encoding"]);
30
+ }
31
+ }
32
+ else throw 'Unsupported type';
33
+ }
34
+ }
35
+ else throw 'Unexpected format';
36
+ })
37
+ if(arr.every(a => a instanceof Uint8Array)) return TideMemory.CreateFromArray(arr); // Check to make sure everything was serialized correctly from the wappa
38
+ else throw 'There was an error encoding all your values';
39
+ }
40
+ }
41
+
42
+ export function encodeStr(str: string, enc: string): Uint8Array {
43
+ switch(enc){
44
+ case "UTF-8":
45
+ return new TextEncoder().encode(str);
46
+ case "HEX":
47
+ // 1) Strip 0x prefix
48
+ let normalized = str.replace(/^0x/i, "");
49
+
50
+ // treat empty as invalid
51
+ if (normalized.length === 0) {
52
+ throw new Error("Empty hex string");
53
+ }
54
+
55
+ // 2) Pad odd length
56
+ if (normalized.length % 2 !== 0) {
57
+ normalized = "0" + normalized;
58
+ }
59
+
60
+ // 3) Validate
61
+ if (!/^[0-9A-Fa-f]+$/.test(normalized)) {
62
+ throw new Error("Invalid hex string");
63
+ }
64
+
65
+ // 4) Parse into bytes
66
+ const byteCount = normalized.length / 2;
67
+ const out = new Uint8Array(byteCount);
68
+ for (let i = 0; i < byteCount; i++) {
69
+ out[i] = Number.parseInt(normalized.slice(i * 2, i * 2 + 2), 16);
70
+ }
71
+
72
+ return out;
73
+ case "B64":
74
+ const binaryString = atob(str);
75
+ const len = binaryString.length;
76
+ const bytes = new Uint8Array(len);
77
+ for (let i = 0; i < len; i++) {
78
+ bytes[i] = binaryString.charCodeAt(i);
79
+ }
80
+ return bytes;
81
+ case "B64URL":
82
+ // 1) Replace URL-safe chars with standard Base64 chars
83
+ let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
84
+
85
+ // 2) Pad with '=' so length is a multiple of 4
86
+ const pad = base64.length % 4;
87
+ if (pad === 2) {
88
+ base64 += '==';
89
+ } else if (pad === 3) {
90
+ base64 += '=';
91
+ } else if (pad === 1) {
92
+ // This shouldn’t happen for valid Base64-URL, but just in case…
93
+ base64 += '===';
94
+ }
95
+
96
+ // 3) Decode to binary string
97
+ const binary = atob(base64);
98
+
99
+ // 4) Convert to Uint8Array
100
+ const ulen = binary.length;
101
+ const ubytes = new Uint8Array(ulen);
102
+ for (let i = 0; i < ulen; i++) {
103
+ ubytes[i] = binary.charCodeAt(i);
104
+ }
105
+ return ubytes;
106
+ default:
107
+ // catches anything else (should never happen)
108
+ throw new TypeError(`Unsupported encoding: ${enc}`);
109
+ }
110
+ }
111
+
112
+ export function encode(data: number | boolean | Uint8Array): Uint8Array | undefined {
113
+ switch (typeof data) {
114
+ case 'number':
115
+ const buffer = new ArrayBuffer(4);
116
+ const view = new DataView(buffer);
117
+ view.setUint32(0, data, true);
118
+ return new Uint8Array(buffer);
119
+
120
+ case 'boolean':
121
+ return new Uint8Array([data ? 1 : 0]);
122
+
123
+ case 'object':
124
+ // since a Uint8Array is an object at runtime, we need to check it here
125
+ if (data instanceof Uint8Array) {
126
+ return new Uint8Array(data.slice(0));
127
+ }
128
+ // if we fall through, it wasn't one of our allowed types
129
+ throw new TypeError(`Unsupported object type: ${data}`);
130
+
131
+ default:
132
+ // catches anything else (should never happen)
133
+ return undefined;
134
+ }
135
+ }
136
+
137
+ interface entry{
138
+ value: any;
139
+ encoding?: string;
140
+ }
141
+ type NestedEntry = (entry | Uint8Array | NestedEntry)[]; // added Uint8Array as an optional type so we can serialize it without deep copy
142
+
143
+
144
+ // Tide Memory Object helper functions from tide-js
145
+ export class TideMemory extends Uint8Array{
146
+ static CreateFromArray(datas: Uint8Array[]): TideMemory {
147
+ const length = datas.reduce((sum, next) => sum + next.length, 0);
148
+ const mem = this.Create(datas[0], length);
149
+ for(let i = 1; i < datas.length; i++){
150
+ mem.WriteValue(i, datas[i]);
151
+ }
152
+ return mem;
153
+ }
154
+ static Create(initialValue: Uint8Array, totalLength: number, version: number = 1): TideMemory {
155
+ if (totalLength < initialValue.length + 4) {
156
+ throw new Error("Not enough space to allocate requested data. Make sure to request more space in totalLength than length of InitialValue plus 4 bytes for length.");
157
+ }
158
+
159
+ // Total buffer length is 4 (version) + totalLength
160
+ const bufferLength = 4 + totalLength;
161
+ const buffer = new TideMemory(bufferLength);
162
+ const dataView = new DataView(buffer.buffer);
163
+
164
+ // Write version at position 0 (4 bytes)
165
+ dataView.setInt32(0, version, true); // true for little-endian
166
+
167
+ let dataLocationIndex = 4;
168
+
169
+ // Write data length of initialValue at position 4 (4 bytes)
170
+ dataView.setInt32(dataLocationIndex, initialValue.length, true);
171
+ dataLocationIndex += 4;
172
+
173
+ // Write initialValue starting from position 8
174
+ buffer.set(initialValue, dataLocationIndex);
175
+
176
+ return buffer;
177
+ }
178
+
179
+ WriteValue(index: number, value: Uint8Array): void {
180
+ if (index < 0) throw new Error("Index cannot be less than 0");
181
+ if (index === 0) throw new Error("Use CreateTideMemory to set value at index 0");
182
+ if (this.length < 4 + value.length) throw new Error("Could not write to memory. Memory too small for this value");
183
+
184
+ const dataView = new DataView(this.buffer);
185
+ let dataLocationIndex = 4; // Start after the version number
186
+
187
+ // Navigate through existing data segments
188
+ for (let i = 0; i < index; i++) {
189
+ if (dataLocationIndex + 4 > this.length) {
190
+ throw new RangeError("Index out of range.");
191
+ }
192
+
193
+ // Read data length at current position
194
+ const nextDataLength = dataView.getInt32(dataLocationIndex, true);
195
+ dataLocationIndex += 4;
196
+
197
+ dataLocationIndex += nextDataLength;
198
+ }
199
+
200
+ // Check if there's enough space to write the value
201
+ if (dataLocationIndex + 4 + value.length > this.length) {
202
+ throw new RangeError("Not enough space to write value");
203
+ }
204
+
205
+ // Check if data has already been written to this index
206
+ const existingLength = dataView.getInt32(dataLocationIndex, true);
207
+ if (existingLength !== 0) {
208
+ throw new Error("Data has already been written to this index");
209
+ }
210
+
211
+ // Write data length of value at current position
212
+ dataView.setInt32(dataLocationIndex, value.length, true);
213
+ dataLocationIndex += 4;
214
+
215
+ // Write value starting from current position
216
+ this.set(value, dataLocationIndex);
217
+ }
218
+
219
+ GetValue<T extends Uint8Array>(index: number): T{
220
+ // 'a' should be an ArrayBuffer or Uint8Array
221
+ if (this.length < 4) {
222
+ throw new Error("Insufficient data to read.");
223
+ }
224
+
225
+ // Create a DataView for reading integers in little-endian format
226
+ const dataView = new DataView(this.buffer, this.byteOffset, this.byteLength);
227
+
228
+ // Optional: Read the version if needed
229
+ // const version = dataView.getInt32(0, true);
230
+
231
+ let dataLocationIndex = 4;
232
+
233
+ for (let i = 0; i < index; i++) {
234
+ // Check if there's enough data to read the length of the next segment
235
+ if (dataLocationIndex + 4 > this.length) {
236
+ throw new RangeError("Index out of range.");
237
+ }
238
+
239
+ const nextDataLength = dataView.getInt32(dataLocationIndex, true);
240
+ dataLocationIndex += 4 + nextDataLength;
241
+ }
242
+
243
+ // Check if there's enough data to read the length of the final segment
244
+ if (dataLocationIndex + 4 > this.length) {
245
+ throw new RangeError("Index out of range.");
246
+ }
247
+
248
+ const finalDataLength = dataView.getInt32(dataLocationIndex, true);
249
+ dataLocationIndex += 4;
250
+
251
+ // Check if the final data segment is within bounds
252
+ if (dataLocationIndex + finalDataLength > this.length) {
253
+ throw new RangeError("Index out of range.");
254
+ }
255
+
256
+ return this.subarray(dataLocationIndex, dataLocationIndex + finalDataLength) as T;
257
+ }
258
+ }
@@ -0,0 +1,9 @@
1
+ // tsconfig.cjs.json
2
+ {
3
+ "extends": "./tsconfig.json",
4
+ "compilerOptions": {
5
+ "module": "CommonJS",
6
+ "outDir": "dist/cjs"
7
+ },
8
+ "include": ["src"]
9
+ }
@@ -0,0 +1,10 @@
1
+ // tsconfig.esm.json
2
+ {
3
+ "extends": "./tsconfig.json",
4
+ "compilerOptions": {
5
+ "module": "ESNext",
6
+ "moduleResolution": "node10",
7
+ "outDir": "dist/esm"
8
+ },
9
+ "include": ["src"]
10
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "compilerOptions": {
3
+ "esModuleInterop": true,
4
+ "declaration": true,
5
+ "target": "es2017",
6
+ "sourceMap": true,
7
+ },
8
+ "include": ["src"]
9
+ }