Skip to content

Commit 602d41e

Browse files
Merge pull request #11 from doyoonkim12345/feature/code-signing
Feature/code signing
2 parents d8bd4f5 + 67357bf commit 602d41e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+955
-389
lines changed

apps/client/app.config.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
1111
policy: "appVersion",
1212
},
1313
orientation: "portrait",
14-
icon: "./assets/icon.png",
14+
icon: "./assets/cloud-push-logo.png",
1515
userInterfaceStyle: "light",
1616
newArchEnabled: true,
1717
splash: {
@@ -21,12 +21,18 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
2121
},
2222
ios: {
2323
supportsTablet: true,
24+
bundleIdentifier: "com.durun-onout.client",
2425
},
2526
updates: {
2627
url: sharedConfig.updateBundleUrl,
2728
requestHeaders: {
2829
"expo-channel-name": sharedConfig.channel,
2930
},
31+
codeSigningMetadata: {
32+
alg: "rsa-v1_5-sha256",
33+
keyid: "main",
34+
},
35+
codeSigningCertificate: sharedConfig.certificatePath,
3036
},
3137
android: {
3238
adaptiveIcon: {

apps/client/cloud-push.config.ts

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,26 @@
1-
import { SupabaseStorageClient, SupabaseDbClient } from "@cloud-push/cloud";
21
import { defineConfig } from "@cloud-push/cli";
3-
import sharedConfig from "./sharedConfig";
2+
import { SupabaseStorageClient, SupabaseDbClient } from "@cloud-push/cloud";
43

54
export default defineConfig(() => ({
65
loadClients: () => {
7-
const storageClient = new SupabaseStorageClient({
8-
bucketName: process.env.SUPABASE_BUCKET_NAME,
9-
supabaseUrl: process.env.SUPABASE_URL,
10-
supabaseKey: process.env.SUPABASE_KEY,
11-
});
12-
13-
const dbClient = new SupabaseDbClient({
14-
tableName: process.env.SUPABASE_TABLE_NAME!,
15-
supabaseUrl: process.env.SUPABASE_URL!,
16-
supabaseKey: process.env.SUPABASE_KEY!,
17-
});
6+
7+
const storageClient = new SupabaseStorageClient({
8+
bucketName: process.env.SUPABASE_BUCKET_NAME,
9+
supabaseUrl: process.env.SUPABASE_URL,
10+
supabaseKey: process.env.SUPABASE_KEY,
11+
});
12+
13+
14+
const dbClient = new SupabaseDbClient({
15+
tableName: process.env.SUPABASE_TABLE_NAME!,
16+
supabaseUrl: process.env.SUPABASE_URL!,
17+
supabaseKey: process.env.SUPABASE_KEY!,
18+
});
19+
1820

1921
return {
2022
storage: storageClient,
2123
db: dbClient,
2224
};
2325
},
24-
envSource: "file",
25-
runtimeVersion: sharedConfig.runtimeVersion,
26-
channel: sharedConfig.channel,
27-
environment: "development",
2826
}));

apps/client/metro.config.js

Lines changed: 0 additions & 42 deletions
This file was deleted.

apps/client/screens/HomeScreen.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from "react";
22
import * as Updates from "expo-updates";
33
import { getUpdateStatus } from "@cloud-push/expo";
4-
import { Alert, View, Button, Text } from "react-native";
4+
import { Alert, View, Button, Text, Image } from "react-native";
55

66
export default function HomeScreen() {
77
const handlePress = async () => {
@@ -24,9 +24,10 @@ export default function HomeScreen() {
2424
return (
2525
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
2626
<Text>{Updates.updateId}</Text>
27-
<Text style={{ fontSize: 40, fontWeight: "bold", color: "#ff0000" }}>
28-
Cloud Push
29-
</Text>
27+
<Image
28+
style={{ width: 100, height: 100 }}
29+
source={require("../assets/cloud-push-logo.png")}
30+
/>
3031
<Button title="test force update" onPress={handlePress} />
3132
<Button title="fetch & reload" onPress={handleFetchAndReloadClick} />
3233
</View>

apps/client/sharedConfig.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module.exports = {
22
runtimeVersion: "1.0.0",
33
channel: "development",
4-
updateBundleUrl: "http://192.168.0.10:3000/api/updates/manifest",
5-
currentBundleUrl: "http://192.168.0.10:3000/api/updates/status",
4+
updateBundleUrl: "http://192.168.0.190:3000/api/updates/manifest",
5+
currentBundleUrl: "http://192.168.0.190:3000/api/updates/status",
6+
certificatePath: "./certs/certificate.pem",
67
};

apps/keys/private-key.pem

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIIEowIBAAKCAQEA0i7/YxTrEpch4KH28ouXt/870OUK9rKi2/aaSujSkwd66Oyo
3+
EU6ljLhi5gHKp8sxqAnFNTX7r5Rr0WcPWBwwImNXev2UpSrPSECwoUH9Km8L1v9D
4+
oIYUfC3ue5Yk123UTAM9SzBpKk+KJECv07i7idf9ViaaJ4n3w1QzjSTI8NDU0tjL
5+
RZ9VH2CfuTfUk9evLtR4R+4PxbKu0rnSSMaKmLnHlZ/2n4JKlmNpPM9l56Msjc0Y
6+
Vjk5iQ50zrtrZeN4y/Hp2qKPCfQ7yhMYGMQwii5T7I8Kf4Zp6nOie1Bcb48FRVE8
7+
zGOH8gh+eKSfMl2+Of8buB/OVxy/8sHwUqGmUwIDAQABAoIBAA0QAK0+upFQNTnJ
8+
txhB1q8HvMbxxSmp1ndHLzWinJuoplndg2B3+8/wEa1rxRWCilaALPJupXK3DbBY
9+
8FowfklU1TX+loNhUrqR0qhi+oHtntXAzYonaxSAokaqASYmXEBRHzkSDCcmBFbW
10+
clb9LyKU+tik04XLjEtma0HFXHui0gDnVsBzvNbnIKweBbv0pfLUhqChDu176oT6
11+
UfanMMlX8X2XvIfqdt/fbUaE2pIELK3fQksybT99XFNNoAVnoI1w+KHaepwDvfGo
12+
Zsh9QeAkI9osUtEEIoaqZVn/YKYN5obznxlu46nTfNCigyQVBW4PR/2Po+MEOyUi
13+
7WVG46ECgYEA+FebTMQbxeRD4Zn76Ow/tQh9BoI3sTED+Gm1gBtXOG1IXHAozs8u
14+
HlQqpt1oM3wIkZsJuYVvne13SpOwNF+zpvnGxIjG5+Gki41njkg/TpGsIcTCkT9y
15+
H24t833j7e95LNwK96gThHSn6WTIYEhtqvfvT3dtgwt63dlNfhYpOlECgYEA2Kor
16+
eh6NeNCkN6oIU92/tMr8h8LXRPcuzn1UFccJqP+24ac+E8kZ28kl5OHgr6GsaWrQ
17+
76m16Cr7jX8d3KPH/T9/7gUroroLjvZxDrFHU6WKrnV5UeIN6KLOUyMW6TmXuc+n
18+
D3ZGA5IL4Q8gNDEDpzVySQaUqYiXoMWq1+tfSWMCgYBxLxRjp1l1FrTtZE3QeaQL
19+
cSPyTHTveAR0OlFzYoKdAAmjv6aJoxlEz11lEbHFDTmmiv+iozMcyja9MZR0Ok3Z
20+
wysZNbFZy5g/1iGlUj4wI+pMta1rn3v24TNmLzErpyIWFO+Wse67RqOklr9QSpJZ
21+
Aoj6Mdcii5/i7oAIADeoEQKBgHAhGWEZAMRezi4UrDyjDXzGdIzaNEh32Fx7FhIz
22+
MPeqZ34+7GyW7fAGGLtsfrjGZhEke78cyIy/+fHPsKeoh26z90Q6nsuXlzXEqtwc
23+
uTo7+RqypzfOUr5ry3XROiE3ciNyqRXicveUFNvD1TnNiAN4MI5EfpmCnF5TDkiy
24+
glzbAoGBAMflKsc0ddclJiRQBXy9qsq31zQx/GRcgOx5X6pMZWSPqMnyB4SjKCOn
25+
LkGQRl7pyRSImTpHWm8L4F4IMIouVriX5WrcR5q6N3JVFsqxCSJ0erx/rjlFFt6A
26+
VtaRmKwKDjuomf7Z4Lex9gi5I41p4ivuFUVQ8LaMaxTsVvADGeS5
27+
-----END RSA PRIVATE KEY-----

apps/keys/public-key.pem

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-----BEGIN PUBLIC KEY-----
2+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0i7/YxTrEpch4KH28ouX
3+
t/870OUK9rKi2/aaSujSkwd66OyoEU6ljLhi5gHKp8sxqAnFNTX7r5Rr0WcPWBww
4+
ImNXev2UpSrPSECwoUH9Km8L1v9DoIYUfC3ue5Yk123UTAM9SzBpKk+KJECv07i7
5+
idf9ViaaJ4n3w1QzjSTI8NDU0tjLRZ9VH2CfuTfUk9evLtR4R+4PxbKu0rnSSMaK
6+
mLnHlZ/2n4JKlmNpPM9l56Msjc0YVjk5iQ50zrtrZeN4y/Hp2qKPCfQ7yhMYGMQw
7+
ii5T7I8Kf4Zp6nOie1Bcb48FRVE8zGOH8gh+eKSfMl2+Of8buB/OVxy/8sHwUqGm
8+
UwIDAQAB
9+
-----END PUBLIC KEY-----

apps/server/app/api/updates/manifest/route.ts

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import type { NextRequest } from "next/server";
22
import type { Bundle } from "@cloud-push/cloud";
3-
import { dbNodeClient, storageNodeClient } from "@/cloud-push.server";
4-
import { createManifest } from "@cloud-push/next/node";
3+
import cloudPushConfig, {
4+
dbNodeClient,
5+
storageNodeClient,
6+
} from "@/cloud-push.server";
7+
import { createManifest, createSignature } from "@cloud-push/next/node";
58
import {
69
type Directive,
710
ErrorResponse,
@@ -21,6 +24,7 @@ export async function GET(request: NextRequest) {
2124
platform,
2225
protocolVersion,
2326
runtimeVersion,
27+
expectSignature,
2428
} = parseHeaders({
2529
headers: request.headers,
2630
url: new URL(request.url),
@@ -76,14 +80,41 @@ export async function GET(request: NextRequest) {
7680
}
7781
}
7882

79-
if (!nextBundle) {
83+
if (!currentBundle && !nextBundle) {
8084
const directive: Directive = {
8185
type: "rollBackToEmbedded",
86+
parameters: {
87+
commitTime: new Date().toISOString(),
88+
},
8289
};
83-
return UpdateResponse({ bundleId: embeddedUpdateId, directive });
90+
console.log("rollBackToEmbedded");
91+
92+
const sig =
93+
cloudPushConfig.codeSigningPrivateKey && expectSignature
94+
? createSignature(
95+
expectSignature.alg,
96+
JSON.stringify(directive),
97+
cloudPushConfig.codeSigningPrivateKey,
98+
)
99+
: undefined;
100+
101+
return UpdateResponse({
102+
bundleId: embeddedUpdateId,
103+
directive,
104+
signature:
105+
sig && expectSignature?.keyid
106+
? { sig, keyid: expectSignature.keyid }
107+
: undefined,
108+
});
109+
}
110+
111+
if (!nextBundle) {
112+
console.log("NoUpdateResponse");
113+
return NoUpdateResponse();
84114
}
85115

86116
if (nextBundle.bundleId === currentBundle?.bundleId) {
117+
console.log("NoUpdateResponse");
87118
return NoUpdateResponse();
88119
}
89120

@@ -94,9 +125,27 @@ export async function GET(request: NextRequest) {
94125
storageClient: storageNodeClient,
95126
channel,
96127
});
128+
console.log("UpdateResponse");
129+
130+
const sig =
131+
cloudPushConfig.codeSigningPrivateKey && expectSignature
132+
? createSignature(
133+
expectSignature.alg,
134+
JSON.stringify(manifest),
135+
cloudPushConfig.codeSigningPrivateKey,
136+
)
137+
: undefined;
97138

98-
return UpdateResponse({ manifest, bundleId: nextBundle.bundleId });
139+
return UpdateResponse({
140+
manifest,
141+
bundleId: nextBundle.bundleId,
142+
signature:
143+
sig && expectSignature?.keyid
144+
? { sig, keyid: expectSignature.keyid }
145+
: undefined,
146+
});
99147
} catch (error) {
148+
console.error(error);
100149
return ErrorResponse(error as Error);
101150
}
102151
}

apps/server/cloud-push.server.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { CloudPushConfig } from "@cloud-push/next";
12

23
import { SupabaseStorageClient, SupabaseDbClient } from "@cloud-push/cloud";
34

@@ -14,3 +15,10 @@ export const dbNodeClient = new SupabaseDbClient({
1415
supabaseUrl: process.env.SUPABASE_URL!,
1516
supabaseKey: process.env.SUPABASE_KEY!,
1617
});
18+
19+
20+
const cloudPushConfig: CloudPushConfig = {
21+
codeSigningPrivateKey: process.env.CLOUD_PUSH_PRIVATE_KEY,
22+
};
23+
24+
export default cloudPushConfig;

apps/server/components/BundleCard.tsx

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,19 @@ export function BundleCard({
2121
const isIosLatestBundle = iosLatestBundle?.bundleId === bundle.bundleId;
2222

2323
return (
24-
<div className="bg-white rounded-xl shadow-md p-6 border border-gray-200 hover:shadow-lg transition">
25-
<div className="flex justify-between items-start mb-4">
26-
<div>
27-
<h3 className="font-semibold text-gray-800 flex items-center gap-4">
24+
<div className="bg-white rounded-xl shadow-md p-4 sm:p-6 border border-gray-200 hover:shadow-lg transition w-full max-w-full">
25+
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-4 mb-4">
26+
<div className="flex-1">
27+
<h3 className="font-semibold text-gray-800 flex flex-wrap items-center gap-2 text-base sm:text-lg">
2828
<span className="text-blue-600">#{index + 1}</span> Bundle ID
29-
<div className="flex gap-2">
29+
<div className="flex flex-wrap gap-1">
3030
{isAndroidLatestBundle && (
31-
<span className="px-2 py-1 text-xs font-medium text-white bg-green-500 rounded">
31+
<span className="px-2 py-0.5 text-xs font-medium text-white bg-green-500 rounded">
3232
Android Latest
3333
</span>
3434
)}
3535
{isIosLatestBundle && (
36-
<span className="px-2 py-1 text-xs font-medium text-white bg-green-500 rounded">
36+
<span className="px-2 py-0.5 text-xs font-medium text-white bg-green-500 rounded">
3737
iOS Latest
3838
</span>
3939
)}
@@ -44,15 +44,15 @@ export function BundleCard({
4444
</p>
4545
</div>
4646

47-
<div className="space-y-2">
47+
<div className="flex flex-wrap gap-2">
4848
{(
4949
["FORCE_UPDATE", "NORMAL_UPDATE", "ROLLBACK"] as UpdatePolicy[]
5050
).map((policy) => (
5151
<button
5252
type="button"
5353
key={policy}
5454
onClick={() => onUpdatePolicyChange(bundle, policy)}
55-
className={`px-4 py-1 rounded-md text-xs font-medium transition ${
55+
className={`px-3 py-1 rounded-md text-xs font-medium transition ${
5656
bundle.updatePolicy === policy
5757
? "bg-blue-600 text-white"
5858
: "bg-gray-200 text-gray-700 hover:bg-gray-300"
@@ -90,14 +90,13 @@ export function BundleCard({
9090
rel="noreferrer"
9191
>
9292
<strong>Commit:</strong>{" "}
93-
<span className="underline">{bundle.gitHash}</span>
93+
<span className="underline break-all">{bundle.gitHash}</span>
9494
</a>
95-
{/* ✅ 수정된 부분: <p> 태그 밖에 <ul> 위치 */}
9695
<div>
9796
<p className="mb-1">
9897
<strong>Policy Target:</strong>
9998
</p>
100-
<ul className="ml-4 list-disc text-xs">
99+
<ul className="ml-4 list-disc text-xs sm:text-sm">
101100
{bundle.updatePolicy === "ROLLBACK" ? (
102101
<>
103102
{bundle.supportAndroid && (

0 commit comments

Comments
 (0)