@@ -82,6 +82,18 @@ inputs:
8282 description : Verify the public Cloudflare Tunnel health endpoint before continuing.
8383 required : false
8484 default : " false"
85+ ci_proxy_url :
86+ description : Optional SimDeck CI proxy Worker URL, such as https://ci.simdeck.sh.
87+ required : false
88+ default : https://simdeck-ci-proxy.djdeveloperr.workers.dev
89+ proxy_links :
90+ description : Post SimDeck CI proxy links instead of raw Cloudflare Tunnel links.
91+ required : false
92+ default : " true"
93+ session_password :
94+ description : Optional password required by the SimDeck CI proxy before it opens the tunnel.
95+ required : false
96+ default : " "
8597
8698runs :
8799 using : composite
@@ -106,6 +118,9 @@ runs:
106118 INPUT_DEVICE_STRATEGY_VALUE : ${{ inputs.device_strategy }}
107119 INPUT_SIMULATOR_CACHE_VALUE : ${{ inputs.simulator_cache }}
108120 INPUT_PUBLIC_HEALTH_CHECK_VALUE : ${{ inputs.public_health_check }}
121+ INPUT_CI_PROXY_URL_VALUE : ${{ inputs.ci_proxy_url }}
122+ INPUT_PROXY_LINKS_VALUE : ${{ inputs.proxy_links }}
123+ INPUT_SESSION_PASSWORD_VALUE : ${{ inputs.session_password }}
109124 KEEPALIVE_SECONDS_VALUE : ${{ inputs.keepalive_seconds }}
110125 BUILD_WORKFLOW_VALUE : ${{ inputs.build_workflow }}
111126 ARTIFACT_PREFIX_VALUE : ${{ inputs.artifact_prefix }}
@@ -138,6 +153,12 @@ runs:
138153 write_env "SIMDECK_VERSION" "${SIMDECK_VERSION_VALUE}"
139154 write_env "INPUT_STREAM_PROFILE" "${INPUT_STREAM_PROFILE_VALUE}"
140155 write_env "INPUT_SIMULATOR_NAME" "${INPUT_SIMULATOR_NAME_VALUE}"
156+ write_env "SIMDECK_CI_PROXY_URL" "${INPUT_CI_PROXY_URL_VALUE}"
157+ write_env "SIMDECK_PROXY_LINKS" "${INPUT_PROXY_LINKS_VALUE}"
158+ write_env "SIMDECK_SESSION_PASSWORD" "${INPUT_SESSION_PASSWORD_VALUE}"
159+ if [[ -n "${INPUT_SESSION_PASSWORD_VALUE}" ]]; then
160+ echo "::add-mask::${INPUT_SESSION_PASSWORD_VALUE}"
161+ fi
141162 write_env "INPUT_DEVICE_STRATEGY" "${INPUT_DEVICE_STRATEGY_VALUE}"
142163 write_env "INPUT_SIMULATOR_CACHE" "${INPUT_SIMULATOR_CACHE_VALUE}"
143164 write_env "INPUT_PUBLIC_HEALTH_CHECK" "${INPUT_PUBLIC_HEALTH_CHECK_VALUE}"
@@ -198,6 +219,12 @@ runs:
198219 echo "SIMDECK_DEVICE_STRATEGY=${device_strategy}" >> "${GITHUB_ENV}"
199220 echo "SIMDECK_STREAM_PROFILE=${stream_profile}" >> "${GITHUB_ENV}"
200221 echo "SIMDECK_PUBLIC_HEALTH_CHECK=${public_health_check}" >> "${GITHUB_ENV}"
222+ if [[ -n "${SIMDECK_SESSION_PASSWORD:-}" ]]; then
223+ if [[ "${SIMDECK_PROXY_LINKS}" != "true" || -z "${SIMDECK_CI_PROXY_URL:-}" ]]; then
224+ echo "session_password requires proxy_links: true and a non-empty ci_proxy_url." >&2
225+ exit 1
226+ fi
227+ fi
201228 echo "Simulator cache: ${simulator_cache}"
202229 echo "Preferred simulator: ${simulator_name:-<none>}"
203230 echo "Device strategy: ${device_strategy}"
@@ -354,6 +381,72 @@ runs:
354381
355382 echo "url=${tunnel_url}" >> "${GITHUB_OUTPUT}"
356383 echo "access_token=${access_token}" >> "${GITHUB_OUTPUT}"
384+ public_url="${tunnel_url}?simdeckToken=${access_token}"
385+ ci_proxy_url="${SIMDECK_CI_PROXY_URL%/}"
386+ if [[ "${SIMDECK_PROXY_LINKS}" == "true" && -n "${ci_proxy_url}" ]]; then
387+ encoded_redirect="$(SIMDECK_TUNNEL_URL="${tunnel_url}" \
388+ SIMDECK_ACCESS_TOKEN="${access_token}" \
389+ SIMDECK_SESSION_PASSWORD="${SIMDECK_SESSION_PASSWORD:-}" \
390+ SIMDECK_PLATFORM="ios" \
391+ SIMDECK_REPO="${REPO}" \
392+ SIMDECK_PR_NUMBER="${PR_NUMBER}" \
393+ SIMDECK_RUN_ID="${GITHUB_RUN_ID}" \
394+ SIMDECK_KEEPALIVE_SECONDS="${KEEPALIVE_SECONDS}" \
395+ node --input-type=module <<'NODE'
396+ import { webcrypto } from "node:crypto";
397+
398+ const env = process.env;
399+ const encoder = new TextEncoder();
400+ const base64url = (bytes) =>
401+ Buffer.from(bytes).toString("base64url");
402+ const keepalive = Number(env.SIMDECK_KEEPALIVE_SECONDS || "1800");
403+ const payload = {
404+ v: 1,
405+ upstream: env.SIMDECK_TUNNEL_URL,
406+ platform: env.SIMDECK_PLATFORM,
407+ repo: env.SIMDECK_REPO,
408+ pr: env.SIMDECK_PR_NUMBER,
409+ runId: env.SIMDECK_RUN_ID,
410+ expiresAt: new Date(Date.now() + (keepalive + 600) * 1000).toISOString(),
411+ };
412+
413+ const password = env.SIMDECK_SESSION_PASSWORD || "";
414+ const token = env.SIMDECK_ACCESS_TOKEN || "";
415+ if (password) {
416+ const salt = webcrypto.getRandomValues(new Uint8Array(16));
417+ const iv = webcrypto.getRandomValues(new Uint8Array(12));
418+ const passwordBytes = encoder.encode(password);
419+ const material = new Uint8Array(passwordBytes.length + salt.length + 1);
420+ material.set(passwordBytes);
421+ material[passwordBytes.length] = 0;
422+ material.set(salt, passwordBytes.length + 1);
423+ const digest = await webcrypto.subtle.digest("SHA-256", material);
424+ const key = await webcrypto.subtle.importKey(
425+ "raw",
426+ digest,
427+ { name: "AES-GCM", length: 256 },
428+ false,
429+ ["encrypt"],
430+ );
431+ const ciphertext = new Uint8Array(
432+ await webcrypto.subtle.encrypt({ name: "AES-GCM", iv }, key, encoder.encode(token)),
433+ );
434+ payload.tokenCipher = {
435+ algorithm: "SHA256-SALTED+A256GCM",
436+ ciphertext: base64url(ciphertext),
437+ iv: base64url(iv),
438+ salt: base64url(salt),
439+ };
440+ } else {
441+ payload.token = token;
442+ }
443+
444+ process.stdout.write(base64url(Buffer.from(JSON.stringify(payload))));
445+ NODE
446+ )"
447+ public_url="${ci_proxy_url}?redirect=${encoded_redirect}"
448+ fi
449+ echo "public_url=${public_url}" >> "${GITHUB_OUTPUT}"
357450
358451 public_health_url="${tunnel_url}/api/health?simdeckToken=${access_token}"
359452 tunnel_host="${tunnel_url#https://}"
@@ -676,7 +769,7 @@ runs:
676769 fi
677770
678771 cat > comment.md <<'EOF'
679- __MENTION__SimDeck iOS session is ready: [Open SimDeck](${{ steps.stream.outputs.url }}?simdeckToken=${{ steps.stream.outputs.access_token }}&device=${{ env.SIMULATOR_UDID }})
772+ __MENTION__SimDeck iOS session is ready: [Open SimDeck](${{ steps.stream.outputs.public_url }}&device=${{ env.SIMULATOR_UDID }})
680773
681774 The selected simulator is booted and the PR app will launch here once its build artifact is installed.
682775
@@ -837,7 +930,7 @@ runs:
837930 shell : bash
838931 run : |
839932 cat > comment.md <<'EOF'
840- SimDeck iOS session is ready: [Open SimDeck](${{ steps.stream.outputs.url }}?simdeckToken=${{ steps.stream.outputs.access_token }}&device=${{ steps.simulator.outputs.udid }})
933+ SimDeck iOS session is ready: [Open SimDeck](${{ steps.stream.outputs.public_url }}&device=${{ steps.simulator.outputs.udid }})
841934
842935 App: `${{ steps.simulator.outputs.bundle_id }}`
843936 Commit: `${{ steps.pr.outputs.sha }}`
0 commit comments