I'd like people with a good Cloudflare and Kotlin/...
# javascript
s
I'd like people with a good Cloudflare and Kotlin/JS understanding to review (or contribute to) my new project: https://github.com/StefanOltmann/cloudflare-steam-name-update-service I think a lot can be improved here. Especially how I pass in the environment - didn’t find a better way. And I don't like all the
js()
interop, too. I like this to be Kotlin/WASM, but the sample (a fork from the original hello world) I found was outdated and didn't work for me. So I appeciate if you drop me hints how to improve or helpful ressources (like sample code) how to properly work with Kotlin/JS (or even better Kotlin/WASM) on Cloudflare Workers. 🙂
👀 2
K 1
t
Yes, all
js
calls can be replaced with help of Kotlin Wrappers declarations
Reflect.deleteProperty
- for property removing
globalThis as EventTarget
for
addEventListener
Also I suppose, that you can have cancellation support here
FetchEvent - is it what you receive in
main
function as event?
🤷 1
s
Thanks for the hints. Will need to look if it’s the same FetchEvent, I’m not sure.
t
It can be something CloudFlare specific
s
Yeah, that’s my issue. Even the Cloudflare guys on their discord don’t really now I think. My main.kt feels like a total hack and I don’t really know how to make it nice.
t
Is there TS typings for this env?
s
In Cloudflare you can assign binding and name them. These are the variables available on that object
t
You use deprecated service worker API
👀 1
s
Is there a deprecation note on the page I missed?
s
Oh, that’s why one of the PRs to their Kotlin sample has this migration to ES modules 💡
Ok, so I may need to build upon that once I get it working. 👀
Or I need to use the more expensive AWS lambdas, can write Kotlin/Native and keep my sanity. I’ll think about that.
I need to rework the whole thing anyway as it takes 35ms and goes therefore constantly over the 10 ms limit for a worker
t
CoroutineScope
factories with cancellation support 😉
Request
implements
AbortableLike
in your case
s
That’s nice. 🙂 I try to rework it without Kotlinx-coroutines. ChatGPT said I can use something lightweight like this: ˋˋˋ /* Minimal async/util.await for Kotlin/JS */ import kotlin.coroutines.* import kotlin.js.Promise suspend fun <T> Promise<T>.await(): T = suspendCoroutine { continuation -> then( onFulfilled = { value -> continuation.resume(value) }, onRejected = { error -> continuation.resumeWithException(Throwable(error.toString())) } ) } fun <T> runSuspend(block: suspend () -> T): Promise<T> = Promise { resolve, reject -> block.startCoroutine( completion = object : Continuation<T> { override val context: CoroutineContext = EmptyCoroutineContext override fun resumeWith(result: Result<T>) { result.fold(resolve, reject) } } ) } ˋˋˋ
I figured out that coroutines core alone cause 25k of extra lines in the JS file and makes the worker slow
This solution weights way less
The worker is still too slow; I guess I need to replace cryptography-Kotlin with your JWT sample code to see how that works out
I have no idea if the worker will go under 10 millis execution time or not
Maybe that’s all doomed and Cloudflare workers are just good for being a reverse proxy if you don’t want to do it in JavaScript
For the Kotlin native AWS lambda I shared in feed I have a Cloudflare worker just as a rate limiting reverse proxy in front so no bad actor will cause a big bill for me by calling it a million times. Maybe that’s what Cloudflare workers are meant for.
t
I guess I need to replace cryptography-Kotlin with your JWT sample code to see how that works out
“Native” calls should be faster 😉
Do you use
es2015
target?
Do you use Kotlin distribution task output in worker?
s
Do you use
es2015
target?
Not in the published code, but I added it in a dev version. Doesn't change execution times much.
Do you use Kotlin distribution task output in worker?'
I use the output of
compileProductionExecutableKotlinJs
I wondered how I can use the output of webpack, but my results didn't work on the Cloudflare worker. I guess treeshaking removes necessary stuff. https://github.com/StefanOltmann/cloudflare-steam-name-update-service/blob/b1a9f3d0befbe387565badcf1be06af81acc52f1/wrangler.toml#L8
t
I use the output of
compileProductionExecutableKotlinJs
It goes without optimization/minification :(
s
I know, but I wasn't successful to change that.
Do you have a hint for me here?
t
Which module type do you need for Cloudflare worker?
ESM or CommonJS?
s
Cloudflare workers run V8 Isolates (like in Chrome), but don't have a "window"
I migrated to ESM
*I think
t
And now you have
@JsExport
- right?
s
Yes
I'll share my branch
This was ChatGPT's insight that to run suspend functions you don't necessarily need the big kotlinx-coroutines https://github.com/StefanOltmann/cloudflare-steam-name-update-service/blob/main/src/jsMain/kotlin/util/PromiseUtil.kt
t
And distribution doesn't work?
s
What gradle command exactly?
I seem not to have any task including "dist" in my task list
t
jsBrowserProductionDistribution
?
s
Task 'jsBrowserProductionDistribution' not found in root project 'worker'.
t
build
includes it
s
I have these target from the template. No idea if that's correct. Cloudflare works don't run nodeJS https://github.com/StefanOltmann/cloudflare-steam-name-update-service/blob/5c29e84aa15161893a25e7530c0de4d790f133a2/build.gradle.kts#L17-L20
t
With
nodejs
you won't have distribution tasks 😞
s
ChatGPT gave me some wild changes trying to utilize webpack, but that only broke the project.
Ok, I dropped the nodeJS() line... that didn't do anything it looks like. Still works without
t
browser()
will add distribution tasks
s
The Cloudflare Kotlin template is so wild; I don't think they knew what they were even doing
t
Also you can use granularity
whole-program
It will be less bundle
s
That's also BS from the template. Thanks for noting. What is the correct value?
t
It's fine right now 😉
s
t
Probably webpack
globalThis
override required
s
How to?
Or the entry point is no longer injected... that's possible, too
t
This tool will be better instead - https://github.com/vercel/ncc
You can back
nodejs()
call and build distribution with
ncc
I can share similar logic for
vite
bundler
s
I've an idea why webpack might not work... The expected entry point will not be there with obfuscated name.. https://github.com/StefanOltmann/cloudflare-steam-name-update-service/blob/70ccfa853bab5883e842f8e52db1f38b164ffdd0/build.gradle.kts#L52-L59
Can webpack be instructed to not change that part?
t
ncc
configuration should be easier
👀 1
s
Just that it doesn't come with a Gradle plugin. Another tool to learn. I put that onto my list. I hoped that the tools we already have would work somehow.
For the common JS environment that I seem to have, how do I implement these now?
Copy code
fun utf8Decode(uint8arr: Uint8Array<*>): String {
    return TODO()
}

fun base64urlToUint8Array(base64url: String): Uint8Array<ArrayBuffer> {
    return TODO()
}

fun base64ToUint8Array(base64: String): Uint8Array<ArrayBuffer> {
    return TODO()
}
Like this? I'm just guessing.
Copy code
fun utf8Decode(uint8arr: Uint8Array<*>): String =
    uint8arr.toByteArray().decodeToString()

fun base64urlToUint8Array(base64url: String): Uint8Array<ArrayBuffer> =
    Base64.UrlSafe.decode(base64url).toUint8Array()

fun base64ToUint8Array(base64: String): Uint8Array<ArrayBuffer> =
    Base64.decode(base64).toUint8Array()
t
Buffer.from(str)
should be faster
s
Copy code
private fun base64ToUint8Array(base64: String): Uint8Array<ArrayBuffer> =
    Buffer.from(base64).toUint8Array()
?
This is the first time I do Kotlin/JS, I'm learning all this right now
t
Buffer extends Uint8Array
No additional conventions required
s
And how does it deal with base64 default & base 64 urlencode?
Which Buffer do you mean? IDEA wants to import kotlinx.io.Buffer, but I guess that's wrong
Ok... for the others, stumbling over this:
Copy code
private fun base64urlToUint8Array(base64url: String): Uint8Array<ArrayBuffer> =
    Buffer.from(base64url, BufferEncoding.base64url)

private fun base64ToUint8Array(base64: String): Uint8Array<ArrayBuffer> =
    Buffer.from(base64, BufferEncoding.base64)
And you need to import
org.jetbrains.kotlin-wrappers:kotlin-node:2025.9.2-24.3.0
Note that it won't work on Cloudflare Workers, because they are not a NodeJS environment
t
I see
Buffer
in list
And base64 example
s
Ah, I see. nodejs polyfills need to be activated to make them available 💡
t
Without
Buffer
:
Copy code
TextEncoder().encode(atob(base64_string))
TextDecoder
- in opposite direction
s
Not sure if that's correct. It fails on the worker. The AI generated me this original code / slop:
Copy code
function base64ToUint8Array(base64: string): Uint8Array {
    const binaryString = atob(base64);
    const length = binaryString.length;
    const bytes = new Uint8Array(length);
    for (let index = 0; index < length; index++)
        bytes[index] = binaryString.charCodeAt(index);

    return bytes;
}
t
It fails on the worker.
With some error?
s
Invalid SPKI input.
Copy code
private fun utf8Decode(uint8arr: Uint8Array<*>): String =
    textDecoder.decode(uint8arr)

private fun base64urlToUint8Array(base64url: String): Uint8Array<ArrayBuffer> =
    textEncoder.encode(atob(base64url.replace('-', '+').replace('_', '/'))).toUint8Array()

private fun base64ToUint8Array(base64: String): Uint8Array<ArrayBuffer> =
    textEncoder.encode(atob(base64))
The method seems anyway to complete under one ms; so maybe I don't need to optimize there that much. The R2 operation takes way longer.
t
Was it
web.encoding.TextEncoder
? According calls looks like other type.
s
Yes. It was that one.