firefly-compiler 0.4.36 → 0.4.46

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 (116) hide show
  1. package/.hintrc +4 -4
  2. package/.vscode/settings.json +4 -4
  3. package/bin/Release.ff +99 -49
  4. package/bin/firefly.mjs +1 -1
  5. package/compiler/Builder.ff +257 -257
  6. package/compiler/Compiler.ff +227 -227
  7. package/compiler/Dependencies.ff +186 -186
  8. package/compiler/DependencyLock.ff +17 -17
  9. package/compiler/JsEmitter.ff +946 -946
  10. package/compiler/LspHook.ff +202 -202
  11. package/compiler/ModuleCache.ff +178 -178
  12. package/compiler/Workspace.ff +88 -88
  13. package/core/.firefly/include/package-lock.json +394 -394
  14. package/core/.firefly/include/package.json +5 -5
  15. package/core/.firefly/include/prepare.sh +1 -1
  16. package/core/.firefly/package.ff +2 -2
  17. package/core/Array.ff +265 -265
  18. package/core/Atomic.ff +64 -64
  19. package/core/Box.ff +7 -7
  20. package/core/BrowserSystem.ff +40 -37
  21. package/core/Buffer.ff +3 -3
  22. package/core/BuildSystem.ff +148 -145
  23. package/core/Crypto.ff +96 -95
  24. package/core/Equal.ff +36 -36
  25. package/core/HttpClient.ff +87 -87
  26. package/core/Instant.ff +17 -0
  27. package/core/JsSystem.ff +69 -69
  28. package/core/Json.ff +434 -434
  29. package/core/List.ff +415 -415
  30. package/core/Lock.ff +144 -144
  31. package/core/NodeSystem.ff +189 -189
  32. package/core/Ordering.ff +161 -161
  33. package/core/Path.ff +401 -401
  34. package/core/Random.ff +134 -134
  35. package/core/RbMap.ff +216 -216
  36. package/core/Show.ff +43 -43
  37. package/core/SourceLocation.ff +68 -68
  38. package/core/Stream.ff +1 -1
  39. package/core/Task.ff +141 -141
  40. package/experimental/benchmarks/ListGrab.ff +23 -23
  41. package/experimental/benchmarks/ListGrab.java +55 -55
  42. package/experimental/benchmarks/Pyrotek45.ff +30 -30
  43. package/experimental/benchmarks/Pyrotek45.java +64 -64
  44. package/experimental/bidirectional/Bidi.ff +88 -88
  45. package/experimental/random/Index.ff +53 -53
  46. package/experimental/random/Process.ff +120 -120
  47. package/experimental/random/RunLength.ff +3 -3
  48. package/experimental/random/Scrape.ff +51 -51
  49. package/experimental/random/Symbols.ff +73 -73
  50. package/experimental/random/Tensor.ff +52 -52
  51. package/experimental/random/Units.ff +36 -36
  52. package/experimental/s3/S3TestAuthorizationHeader.ff +38 -38
  53. package/experimental/s3/S3TestPut.ff +15 -15
  54. package/experimental/tests/TestJson.ff +26 -26
  55. package/firefly.sh +0 -0
  56. package/fireflysite/Main.ff +13 -13
  57. package/lsp/.firefly/package.ff +1 -1
  58. package/lsp/CompletionHandler.ff +811 -811
  59. package/lsp/Handler.ff +714 -714
  60. package/lsp/HoverHandler.ff +79 -79
  61. package/lsp/LanguageServer.ff +272 -272
  62. package/lsp/SignatureHelpHandler.ff +55 -55
  63. package/lsp/SymbolHandler.ff +181 -181
  64. package/lsp/TestReferences.ff +16 -16
  65. package/lsp/TestReferencesCase.ff +7 -7
  66. package/lsp/stderr.txt +1 -1
  67. package/lsp/stdout.txt +34 -34
  68. package/lux/.firefly/package.ff +1 -1
  69. package/lux/Css.ff +648 -648
  70. package/lux/CssTest.ff +48 -48
  71. package/lux/Lux.ff +487 -487
  72. package/lux/LuxEvent.ff +116 -116
  73. package/lux/Main.ff +128 -128
  74. package/lux/Main2.ff +144 -144
  75. package/output/js/ff/compiler/Builder.mjs +43 -43
  76. package/output/js/ff/compiler/Dependencies.mjs +3 -3
  77. package/output/js/ff/core/Array.mjs +59 -59
  78. package/output/js/ff/core/Atomic.mjs +36 -36
  79. package/output/js/ff/core/BrowserSystem.mjs +19 -11
  80. package/output/js/ff/core/Buffer.mjs +7 -7
  81. package/output/js/ff/core/BuildSystem.mjs +38 -30
  82. package/output/js/ff/core/Crypto.mjs +67 -68
  83. package/output/js/ff/core/HttpClient.mjs +24 -24
  84. package/output/js/ff/core/Instant.mjs +38 -0
  85. package/output/js/ff/core/Json.mjs +147 -147
  86. package/output/js/ff/core/List.mjs +50 -50
  87. package/output/js/ff/core/Lock.mjs +97 -97
  88. package/output/js/ff/core/NodeSystem.mjs +77 -77
  89. package/output/js/ff/core/Ordering.mjs +8 -8
  90. package/output/js/ff/core/Path.mjs +231 -231
  91. package/output/js/ff/core/Random.mjs +56 -56
  92. package/output/js/ff/core/Stream.mjs +2 -2
  93. package/output/js/ff/core/Task.mjs +31 -31
  94. package/package.json +29 -29
  95. package/rpc/.firefly/package.ff +1 -1
  96. package/rpc/Rpc.ff +69 -69
  97. package/s3/.firefly/package.ff +1 -0
  98. package/{experimental/s3 → s3}/S3.ff +92 -92
  99. package/unsafejs/UnsafeJs.ff +19 -19
  100. package/vscode/LICENSE.txt +21 -21
  101. package/vscode/Prepublish.ff +15 -15
  102. package/vscode/README.md +16 -16
  103. package/vscode/client/package.json +22 -22
  104. package/vscode/client/src/extension.ts +104 -104
  105. package/vscode/icons/firefly-icon.svg +10 -10
  106. package/vscode/language-configuration.json +61 -61
  107. package/vscode/package-lock.json +3623 -3623
  108. package/vscode/package.json +160 -160
  109. package/vscode/snippets.json +241 -241
  110. package/webserver/.firefly/include/package-lock.json +16 -16
  111. package/webserver/.firefly/include/package.json +5 -5
  112. package/webserver/.firefly/package.ff +2 -2
  113. package/webserver/WebServer.ff +685 -685
  114. package/websocket/.firefly/package.ff +1 -1
  115. package/websocket/WebSocket.ff +131 -131
  116. package/crypto/SubtleCrypto.ff +0 -149
package/core/Box.ff CHANGED
@@ -1,7 +1,7 @@
1
- class Box[T](mutable value: T)
2
-
3
- extend self[T]: Box[T] {
4
- modify(body: T => T): Unit {
5
- self.value = body(self.value)
6
- }
7
- }
1
+ class Box[T](mutable value: T)
2
+
3
+ extend self[T]: Box[T] {
4
+ modify(body: T => T): Unit {
5
+ self.value = body(self.value)
6
+ }
7
+ }
@@ -1,37 +1,40 @@
1
- capability BrowserSystem {}
2
-
3
- extend self: BrowserSystem {
4
-
5
- httpClient(): HttpClient
6
- target js async "return null"
7
-
8
- mainTask(): Task
9
- target js async "return self_.task_"
10
-
11
- js(): JsSystem
12
- target js async "return typeof globalThis !== 'undefined' ? globalThis : window"
13
-
14
- url(): String
15
- target js async """
16
- return location.href;
17
- """
18
-
19
- urlPath(): String
20
- target js async """
21
- return location.pathname;
22
- """
23
-
24
- urlQuery(name: String): Option[String]
25
- target js async """
26
- const param = new URLSearchParams(location.search).get(name_)
27
- if(param == null) return ff_core_Option.None();
28
- return ff_core_Option.Some(param);
29
- """
30
-
31
- urlFragment(): Option[String]
32
- target js async """
33
- if(!location.hash.startsWith('#')) return ff_core_Option.None();
34
- return ff_core_Option.Some(location.hash.slice(1));
35
- """
36
-
37
- }
1
+ capability BrowserSystem {}
2
+
3
+ extend self: BrowserSystem {
4
+
5
+ httpClient(): HttpClient
6
+ target js async "return null"
7
+
8
+ mainTask(): Task
9
+ target js async "return self_.task_"
10
+
11
+ crypto(): Crypto
12
+ target js async "return (typeof globalThis !== 'undefined' ? globalThis : window).crypto"
13
+
14
+ js(): JsSystem
15
+ target js async "return typeof globalThis !== 'undefined' ? globalThis : window"
16
+
17
+ url(): String
18
+ target js async """
19
+ return location.href;
20
+ """
21
+
22
+ urlPath(): String
23
+ target js async """
24
+ return location.pathname;
25
+ """
26
+
27
+ urlQuery(name: String): Option[String]
28
+ target js async """
29
+ const param = new URLSearchParams(location.search).get(name_)
30
+ if(param == null) return ff_core_Option.None();
31
+ return ff_core_Option.Some(param);
32
+ """
33
+
34
+ urlFragment(): Option[String]
35
+ target js async """
36
+ if(!location.hash.startsWith('#')) return ff_core_Option.None();
37
+ return ff_core_Option.Some(location.hash.slice(1));
38
+ """
39
+
40
+ }
package/core/Buffer.ff CHANGED
@@ -143,10 +143,10 @@ extend self: Buffer {
143
143
 
144
144
  }
145
145
 
146
- fromByteArray(array: List[Int]): Buffer
146
+ fromByteList(array: List[Int]): Buffer
147
147
  target js sync "return new DataView(new Uint8Array(array_).buffer)"
148
148
 
149
- fromBufferArray(array: List[Buffer]): Buffer
149
+ fromBufferList(array: List[Buffer]): Buffer
150
150
  target js sync """
151
151
  let length = 0
152
152
  for(let b of array_) length += b.byteLength
@@ -162,7 +162,7 @@ fromBufferArray(array: List[Buffer]): Buffer
162
162
 
163
163
  fromHex(hex: String): Buffer
164
164
  target js sync """
165
- const hexValues = hexString.match(/.{1,2}/g) || []
165
+ const hexValues = hex_.match(/.{1,2}/g) || []
166
166
  const numbers = hexValues.map(value => parseInt(value, 16))
167
167
  return new DataView(new Uint8Array(numbers).buffer)
168
168
  """
@@ -1,145 +1,148 @@
1
- capability BuildSystem {}
2
- capability BrowserCode(packageGroup: String, packageName: String, mainFile: Path, assetSystem: AssetSystem)
3
- capability BrowserBundle(assetSystem: AssetSystem)
4
-
5
- extend self: BuildSystem {
6
-
7
- compileForBrowser(mainFile: String): BrowserCode {
8
- // TODO: Check that the mainFile is in the current package directory
9
- internalCompile(self, internalPath(self, mainFile), "browser")
10
- let streams = internalListDirectory(internalPath(self, ".firefly/output/browser"))
11
- let mainPackagePair = internalMainPackagePair(self)
12
- BrowserCode(
13
- packageGroup = mainPackagePair.first
14
- packageName = mainPackagePair.second
15
- mainFile = internalPath(self, mainFile)
16
- assetSystem = AssetSystem(streams.toMap())
17
- )
18
- }
19
-
20
- buildMode(): Bool
21
- target node async "return !!self_.buildMode_"
22
-
23
- setAssets(assetSystem: AssetSystem): Unit
24
- target node async "self_.assets_ = assetSystem_"
25
-
26
- packageAssets(): AssetSystem {
27
- let streams = internalListDirectory(internalPath(self, "."))
28
- AssetSystem(streams.toMap())
29
- }
30
-
31
- dependencyAssets(user: String, package: String): AssetSystem {
32
- panic("dependencyAssets not yet implemented")
33
- }
34
-
35
- arguments(): List[String]
36
- target node async "return self_.array_"
37
-
38
- mainTask(): Task
39
- target js async "return self_.task_"
40
-
41
- }
42
-
43
- extend self: BrowserCode {
44
-
45
- assets(): AssetSystem {
46
- self.assetSystem
47
- }
48
-
49
- bundle(minify: Bool = True, sourceMap: Bool = False): BrowserBundle {
50
- let prefix = ".firefly/output/browser"
51
- let mainJsBaseFile = self.mainFile.base().removeLast(".ff").grab() + ".mjs"
52
- let mainJsFile = prefix + "/" + self.packageGroup + "/" + self.packageName + "/" + mainJsBaseFile
53
- let mainDirectory = self.mainFile.parent().grab()
54
- let file = prefix + "/Main.bundle.js"
55
- internalCallEsBuild(self, mainJsFile = mainJsFile, outputPath = file, minify = minify, sourceMap = sourceMap)
56
- let assets = AssetSystem([
57
- Pair(file.dropFirst(prefix.size()), {mainDirectory.path(file).readStream()})
58
- ...if(sourceMap) {[
59
- Pair(file.dropFirst(prefix.size()) + ".map", {mainDirectory.path(file + ".map").readStream()})
60
- ]} else {[]}
61
- ].toMap())
62
- BrowserBundle(assets)
63
- }
64
-
65
- }
66
-
67
- extend self: BrowserBundle {
68
-
69
- assets(): AssetSystem {
70
- self.assetSystem
71
- }
72
-
73
- }
74
-
75
-
76
- internalCallEsBuild(
77
- self: BrowserCode
78
- mainJsFile: String
79
- outputPath: String
80
- minify: Bool
81
- sourceMap: Bool
82
- ): Unit
83
- target node async """
84
- import * as esbuild from 'esbuild'
85
- return await esbuild.build({
86
- entryPoints: [mainJsFile_],
87
- bundle: true,
88
- minify: minify_,
89
- sourcemap: sourceMap_,
90
- platform: 'browser',
91
- target: 'es6',
92
- external: ['../../../node_modules/*'], // TODO
93
- outfile: outputPath_
94
- })
95
- """
96
-
97
- internalNodeCallEsBuild(
98
- self: NodeSystem
99
- mainJsFile: String
100
- outputPath: String
101
- minify: Bool
102
- ): Unit
103
- target node async """
104
- import * as esbuild from 'esbuild'
105
- return await esbuild.build({
106
- entryPoints: [mainJsFile_],
107
- bundle: true,
108
- minify: minify_,
109
- sourcemap: true,
110
- platform: 'node',
111
- target: 'es6',
112
- external: ['../../../node_modules/*'], // TODO
113
- outfile: outputPath_
114
- })
115
- """
116
-
117
- internalListDirectory(path: Path): List[Pair[String, () => Stream[Buffer]]] {
118
- function go(currentPath: Path): Stream[Path] {
119
- currentPath.entries().flatMap {file =>
120
- if(file.isDirectory()) {
121
- go(file.path())
122
- } else {
123
- [file.path()].toStream()
124
- }
125
- }
126
- }
127
- go(path).map {file =>
128
- Pair("/" + file.relativeTo(path).replace("\\", "/"), {file.readStream()})
129
- }.toList()
130
- }
131
-
132
- internalPath(buildSystem: BuildSystem, absoluteOrRelative: String): Path
133
- target node async """
134
- return absoluteOrRelative_
135
- """
136
-
137
- internalCompile(buildSystem: BuildSystem, mainFile: Path, target: String): Unit
138
- target node async """
139
- return await $firefly_compiler.buildViaBuildSystem_$(buildSystem_, buildSystem_.fireflyPath_, mainFile_, target_, $task)
140
- """
141
-
142
- internalMainPackagePair(buildSystem: BuildSystem): Pair[String, String]
143
- target node async """
144
- return {first_: buildSystem_.mainPackagePair_.group_, second_: buildSystem_.mainPackagePair_.name_}
145
- """
1
+ capability BuildSystem {}
2
+ capability BrowserCode(packageGroup: String, packageName: String, mainFile: Path, assetSystem: AssetSystem)
3
+ capability BrowserBundle(assetSystem: AssetSystem)
4
+
5
+ extend self: BuildSystem {
6
+
7
+ compileForBrowser(mainFile: String): BrowserCode {
8
+ // TODO: Check that the mainFile is in the current package directory
9
+ internalCompile(self, internalPath(self, mainFile), "browser")
10
+ let streams = internalListDirectory(internalPath(self, ".firefly/output/browser"))
11
+ let mainPackagePair = internalMainPackagePair(self)
12
+ BrowserCode(
13
+ packageGroup = mainPackagePair.first
14
+ packageName = mainPackagePair.second
15
+ mainFile = internalPath(self, mainFile)
16
+ assetSystem = AssetSystem(streams.toMap())
17
+ )
18
+ }
19
+
20
+ buildMode(): Bool
21
+ target node async "return !!self_.buildMode_"
22
+
23
+ setAssets(assetSystem: AssetSystem): Unit
24
+ target node async "self_.assets_ = assetSystem_"
25
+
26
+ packageAssets(): AssetSystem {
27
+ let streams = internalListDirectory(internalPath(self, "."))
28
+ AssetSystem(streams.toMap())
29
+ }
30
+
31
+ dependencyAssets(user: String, package: String): AssetSystem {
32
+ panic("dependencyAssets not yet implemented")
33
+ }
34
+
35
+ arguments(): List[String]
36
+ target node async "return self_.array_"
37
+
38
+ mainTask(): Task
39
+ target js async "return self_.task_"
40
+
41
+ crypto(): Crypto
42
+ target js async "return (typeof globalThis !== 'undefined' ? globalThis : window).crypto"
43
+
44
+ }
45
+
46
+ extend self: BrowserCode {
47
+
48
+ assets(): AssetSystem {
49
+ self.assetSystem
50
+ }
51
+
52
+ bundle(minify: Bool = True, sourceMap: Bool = False): BrowserBundle {
53
+ let prefix = ".firefly/output/browser"
54
+ let mainJsBaseFile = self.mainFile.base().removeLast(".ff").grab() + ".mjs"
55
+ let mainJsFile = prefix + "/" + self.packageGroup + "/" + self.packageName + "/" + mainJsBaseFile
56
+ let mainDirectory = self.mainFile.parent().grab()
57
+ let file = prefix + "/Main.bundle.js"
58
+ internalCallEsBuild(self, mainJsFile = mainJsFile, outputPath = file, minify = minify, sourceMap = sourceMap)
59
+ let assets = AssetSystem([
60
+ Pair(file.dropFirst(prefix.size()), {mainDirectory.path(file).readStream()})
61
+ ...if(sourceMap) {[
62
+ Pair(file.dropFirst(prefix.size()) + ".map", {mainDirectory.path(file + ".map").readStream()})
63
+ ]} else {[]}
64
+ ].toMap())
65
+ BrowserBundle(assets)
66
+ }
67
+
68
+ }
69
+
70
+ extend self: BrowserBundle {
71
+
72
+ assets(): AssetSystem {
73
+ self.assetSystem
74
+ }
75
+
76
+ }
77
+
78
+
79
+ internalCallEsBuild(
80
+ self: BrowserCode
81
+ mainJsFile: String
82
+ outputPath: String
83
+ minify: Bool
84
+ sourceMap: Bool
85
+ ): Unit
86
+ target node async """
87
+ import * as esbuild from 'esbuild'
88
+ return await esbuild.build({
89
+ entryPoints: [mainJsFile_],
90
+ bundle: true,
91
+ minify: minify_,
92
+ sourcemap: sourceMap_,
93
+ platform: 'browser',
94
+ target: 'es6',
95
+ external: ['../../../node_modules/*'], // TODO
96
+ outfile: outputPath_
97
+ })
98
+ """
99
+
100
+ internalNodeCallEsBuild(
101
+ self: NodeSystem
102
+ mainJsFile: String
103
+ outputPath: String
104
+ minify: Bool
105
+ ): Unit
106
+ target node async """
107
+ import * as esbuild from 'esbuild'
108
+ return await esbuild.build({
109
+ entryPoints: [mainJsFile_],
110
+ bundle: true,
111
+ minify: minify_,
112
+ sourcemap: true,
113
+ platform: 'node',
114
+ target: 'es6',
115
+ external: ['../../../node_modules/*'], // TODO
116
+ outfile: outputPath_
117
+ })
118
+ """
119
+
120
+ internalListDirectory(path: Path): List[Pair[String, () => Stream[Buffer]]] {
121
+ function go(currentPath: Path): Stream[Path] {
122
+ currentPath.entries().flatMap {file =>
123
+ if(file.isDirectory()) {
124
+ go(file.path())
125
+ } else {
126
+ [file.path()].toStream()
127
+ }
128
+ }
129
+ }
130
+ go(path).map {file =>
131
+ Pair("/" + file.relativeTo(path).replace("\\", "/"), {file.readStream()})
132
+ }.toList()
133
+ }
134
+
135
+ internalPath(buildSystem: BuildSystem, absoluteOrRelative: String): Path
136
+ target node async """
137
+ return absoluteOrRelative_
138
+ """
139
+
140
+ internalCompile(buildSystem: BuildSystem, mainFile: Path, target: String): Unit
141
+ target node async """
142
+ return await $firefly_compiler.buildViaBuildSystem_$(buildSystem_, buildSystem_.fireflyPath_, mainFile_, target_, $task)
143
+ """
144
+
145
+ internalMainPackagePair(buildSystem: BuildSystem): Pair[String, String]
146
+ target node async """
147
+ return {first_: buildSystem_.mainPackagePair_.group_, second_: buildSystem_.mainPackagePair_.name_}
148
+ """
package/core/Crypto.ff CHANGED
@@ -1,95 +1,96 @@
1
- capability Crypto {}
2
-
3
- extend self: Crypto {
4
-
5
- randomUuid(): String
6
- target js async """
7
- return self_.randomUUID();
8
- """
9
-
10
- randomBuffer(size: Int): Buffer {
11
- let buffer = Buffer.new(size)
12
- self.randomizeBuffer(buffer)
13
- buffer
14
- }
15
-
16
- randomizeBuffer(buffer: Buffer): Unit
17
- target js async """
18
- self_.getRandomValues(new Uint8Array(buffer_.buffer, buffer_.byteOffset, buffer_.byteLength));
19
- """
20
-
21
- hmacSha256(key: Buffer, buffer: Buffer): Buffer
22
- target js async """
23
- const cryptoKey = await self_.subtle.importKey(
24
- 'raw',
25
- key_,
26
- {name: 'HMAC', hash: {name: 'SHA-256'}},
27
- false,
28
- ['sign']
29
- );
30
- const signature = await self_.subtle.sign(
31
- 'HMAC',
32
- cryptoKey,
33
- buffer_
34
- );
35
- return new DataView(signature);
36
- """
37
-
38
- sha256(buffer: Buffer): Buffer
39
- target js async """
40
- let hash = await self_.subtle.digest('SHA-256', buffer_);
41
- return new DataView(hash);
42
- """
43
-
44
- makePasswordHash(password: String, iterations: Int = 100000): String {
45
- let salt = self.randomBuffer(16)
46
- let hash = internalMakePasswordHash(salt, password.toBuffer(), iterations)
47
- "PSG_" + iterations + "_" + salt.toHex() + "_" + hash.toHex()
48
- }
49
-
50
- checkPasswordHash(password: String, passwordHash: String): Bool {
51
- passwordHash.split('_').{
52
- | ["PSG", iterationsText, saltText, hashText] {iterationsText.getInt() | Some(iterations)} =>
53
- let computedHash = internalMakePasswordHash(Buffer.fromHex(saltText), password.toBuffer(), iterations)
54
- let hash = Buffer.fromHex(hashText)
55
- self.constantTimeEquals(computedHash, hash)
56
- | _ => False
57
- }
58
- }
59
-
60
- constantTimeEquals(buffer1: Buffer, buffer2: Buffer): Bool {
61
- if(buffer1.size() != buffer2.size()) {False} else:
62
- mutable v = 0
63
- mutable i = 0
64
- while {i < buffer1.size()} {
65
- v = v.bitOr(buffer1.grabUint8(i).bitXor(buffer2.grabUint8(i)))
66
- }
67
- v == 0
68
- }
69
-
70
- }
71
-
72
- internalMakePasswordHash(salt: Buffer, password: Buffer, iterations: Int): Buffer
73
- target js async """
74
- const keyMaterial = await crypto.subtle.importKey(
75
- 'raw',
76
- password_,
77
- {name: 'PBKDF2'},
78
- false,
79
- ['deriveKey']
80
- );
81
- const derivedKey = await crypto.subtle.deriveKey(
82
- {
83
- name: 'PBKDF2',
84
- salt: salt_,
85
- iterations: iterations_,
86
- hash: 'SHA-256'
87
- },
88
- keyMaterial,
89
- {name: 'AES-GCM', length: 256},
90
- true,
91
- ['encrypt', 'decrypt']
92
- );
93
- const hashBuffer = await crypto.subtle.exportKey('raw', derivedKey);
94
- return new DataView(hashBuffer);
95
- """
1
+ capability Crypto {}
2
+
3
+ extend self: Crypto {
4
+
5
+ randomUuid(): String
6
+ target js async """
7
+ return self_.randomUUID();
8
+ """
9
+
10
+ randomBuffer(size: Int): Buffer {
11
+ let buffer = Buffer.new(size)
12
+ self.randomizeBuffer(buffer)
13
+ buffer
14
+ }
15
+
16
+ randomizeBuffer(buffer: Buffer): Unit
17
+ target js async """
18
+ self_.getRandomValues(new Uint8Array(buffer_.buffer, buffer_.byteOffset, buffer_.byteLength));
19
+ """
20
+
21
+ hmacSha256(key: Buffer, buffer: Buffer): Buffer
22
+ target js async """
23
+ const cryptoKey = await self_.subtle.importKey(
24
+ 'raw',
25
+ key_,
26
+ {name: 'HMAC', hash: {name: 'SHA-256'}},
27
+ false,
28
+ ['sign']
29
+ );
30
+ const signature = await self_.subtle.sign(
31
+ 'HMAC',
32
+ cryptoKey,
33
+ buffer_
34
+ );
35
+ return new DataView(signature);
36
+ """
37
+
38
+ sha256(buffer: Buffer): Buffer
39
+ target js async """
40
+ let hash = await self_.subtle.digest('SHA-256', buffer_);
41
+ return new DataView(hash);
42
+ """
43
+
44
+ hashPassword(password: String, iterations: Int = 600000): String {
45
+ let salt = self.randomBuffer(16)
46
+ let hash = internalHashPassword(self, salt, password.toBuffer(), iterations)
47
+ "$pbkdf2-sha256-hex$" + iterations + "$" + salt.toHex() + "$" + hash.toHex()
48
+ }
49
+
50
+ checkPassword(password: String, passwordHash: String): Bool {
51
+ passwordHash.split('$').{
52
+ | ["", "pbkdf2-sha256-hex", iterationsText, saltText, hashText] {
53
+ iterationsText.getInt() | Some(iterations)
54
+ } =>
55
+ let computedHash =
56
+ internalHashPassword(self, Buffer.fromHex(saltText), password.toBuffer(), iterations)
57
+ let hash = Buffer.fromHex(hashText)
58
+ self.constantTimeEquals(computedHash, hash)
59
+ | _ => False
60
+ }
61
+ }
62
+
63
+ constantTimeEquals(buffer1: Buffer, buffer2: Buffer): Bool {
64
+ if(buffer1.size() != buffer2.size()) {False} else:
65
+ mutable v = 0
66
+ mutable i = 0
67
+ while {i < buffer1.size()} {
68
+ v = v.bitOr(buffer1.grabUint8(i).bitXor(buffer2.grabUint8(i)))
69
+ i += 1
70
+ }
71
+ v == 0
72
+ }
73
+
74
+ }
75
+
76
+ internalHashPassword(system: Crypto, salt: Buffer, password: Buffer, iterations: Int): Buffer
77
+ target js async """
78
+ const keyMaterial = await crypto.subtle.importKey(
79
+ 'raw',
80
+ password_,
81
+ {name: 'PBKDF2'},
82
+ false,
83
+ ['deriveBits']
84
+ );
85
+ const hashBuffer = await crypto.subtle.deriveBits(
86
+ {
87
+ name: 'PBKDF2',
88
+ salt: salt_,
89
+ iterations: iterations_,
90
+ hash: 'SHA-256'
91
+ },
92
+ keyMaterial,
93
+ 256
94
+ );
95
+ return new DataView(hashBuffer);
96
+ """