WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 1112b1c

Browse files
authored
Add downloads endpoint (#34)
1 parent 53a8387 commit 1112b1c

File tree

5 files changed

+181
-0
lines changed

5 files changed

+181
-0
lines changed

client.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,11 @@ func (c *Client) EndpointAuthPoll(ctx context.Context, pollCode string) (*messag
673673
return d, err
674674
}
675675

676+
func (c *Client) Downloads(ctx context.Context) (*message.DownloadsData, error) {
677+
_, d, err := callAPI[message.DownloadsData](ctx, c, "GET", message.DownloadsEndpoint, nil)
678+
return d, err
679+
}
680+
676681
func urlPath(base, path string) (string, error) {
677682
baseURL, err := url.Parse(base)
678683
if err != nil {

client_fixtures_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package dnapi
2+
3+
const downloadsResponse = `{
4+
"data": {
5+
"dnclient": {
6+
"0.8.4": {
7+
"freebsd-amd64": "https://dl.defined.net/290ff4b6/v0.8.4/freebsd/amd64/dnclient",
8+
"freebsd-arm64": "https://dl.defined.net/290ff4b6/v0.8.4/freebsd/arm64/dnclient",
9+
"linux-386": "https://dl.defined.net/290ff4b6/v0.8.4/linux/386/dnclient",
10+
"linux-amd64": "https://dl.defined.net/290ff4b6/v0.8.4/linux/amd64/dnclient",
11+
"linux-arm64": "https://dl.defined.net/290ff4b6/v0.8.4/linux/arm64/dnclient",
12+
"linux-armv5": "https://dl.defined.net/290ff4b6/v0.8.4/linux/arm-5/dnclient",
13+
"linux-armv6": "https://dl.defined.net/290ff4b6/v0.8.4/linux/arm-6/dnclient",
14+
"linux-armv7": "https://dl.defined.net/290ff4b6/v0.8.4/linux/arm-7/dnclient",
15+
"linux-mips": "https://dl.defined.net/290ff4b6/v0.8.4/linux/mips/dnclient",
16+
"linux-mips-softfloat": "https://dl.defined.net/290ff4b6/v0.8.4/linux/mips-softfloat/dnclient",
17+
"linux-mips64": "https://dl.defined.net/290ff4b6/v0.8.4/linux/mips64/dnclient",
18+
"linux-mips64le": "https://dl.defined.net/290ff4b6/v0.8.4/linux/mips64le/dnclient",
19+
"linux-mipsle": "https://dl.defined.net/290ff4b6/v0.8.4/linux/mipsle/dnclient",
20+
"linux-ppc64le": "https://dl.defined.net/290ff4b6/v0.8.4/linux/ppc64le/dnclient",
21+
"linux-riscv64": "https://dl.defined.net/290ff4b6/v0.8.4/linux/riscv64/dnclient",
22+
"macos-universal-desktop": "https://dl.defined.net/290ff4b6/v0.8.4/macos/DNClient-Desktop.dmg",
23+
"macos-universal-server": "https://dl.defined.net/290ff4b6/v0.8.4/macos/dnclient",
24+
"macos-universal-server-dmg": "https://dl.defined.net/290ff4b6/v0.8.4/macos/DNClient-Server.dmg",
25+
"windows-amd64-desktop": "https://dl.defined.net/290ff4b6/v0.8.4/windows/amd64/DNClient-Desktop.msi",
26+
"windows-amd64-server": "https://dl.defined.net/290ff4b6/v0.8.4/windows/amd64/DNClient-Server.msi",
27+
"windows-arm64-desktop": "https://dl.defined.net/290ff4b6/v0.8.4/windows/arm64/DNClient-Desktop.msi",
28+
"windows-arm64-server": "https://dl.defined.net/290ff4b6/v0.8.4/windows/arm64/DNClient-Server.msi"
29+
},
30+
"latest": {
31+
"freebsd-amd64": "https://dl.defined.net/290ff4b6/v0.8.4/freebsd/amd64/dnclient",
32+
"freebsd-arm64": "https://dl.defined.net/290ff4b6/v0.8.4/freebsd/arm64/dnclient",
33+
"linux-386": "https://dl.defined.net/290ff4b6/v0.8.4/linux/386/dnclient",
34+
"linux-amd64": "https://dl.defined.net/290ff4b6/v0.8.4/linux/amd64/dnclient",
35+
"linux-arm64": "https://dl.defined.net/290ff4b6/v0.8.4/linux/arm64/dnclient",
36+
"linux-armv5": "https://dl.defined.net/290ff4b6/v0.8.4/linux/arm-5/dnclient",
37+
"linux-armv6": "https://dl.defined.net/290ff4b6/v0.8.4/linux/arm-6/dnclient",
38+
"linux-armv7": "https://dl.defined.net/290ff4b6/v0.8.4/linux/arm-7/dnclient",
39+
"linux-mips": "https://dl.defined.net/290ff4b6/v0.8.4/linux/mips/dnclient",
40+
"linux-mips-softfloat": "https://dl.defined.net/290ff4b6/v0.8.4/linux/mips-softfloat/dnclient",
41+
"linux-mips64": "https://dl.defined.net/290ff4b6/v0.8.4/linux/mips64/dnclient",
42+
"linux-mips64le": "https://dl.defined.net/290ff4b6/v0.8.4/linux/mips64le/dnclient",
43+
"linux-mipsle": "https://dl.defined.net/290ff4b6/v0.8.4/linux/mipsle/dnclient",
44+
"linux-ppc64le": "https://dl.defined.net/290ff4b6/v0.8.4/linux/ppc64le/dnclient",
45+
"linux-riscv64": "https://dl.defined.net/290ff4b6/v0.8.4/linux/riscv64/dnclient",
46+
"macos-universal-desktop": "https://dl.defined.net/290ff4b6/v0.8.4/macos/DNClient-Desktop.dmg",
47+
"macos-universal-server": "https://dl.defined.net/290ff4b6/v0.8.4/macos/dnclient",
48+
"macos-universal-server-dmg": "https://dl.defined.net/290ff4b6/v0.8.4/macos/DNClient-Server.dmg",
49+
"windows-amd64-desktop": "https://dl.defined.net/290ff4b6/v0.8.4/windows/amd64/DNClient-Desktop.msi",
50+
"windows-amd64-server": "https://dl.defined.net/290ff4b6/v0.8.4/windows/amd64/DNClient-Server.msi",
51+
"windows-arm64-desktop": "https://dl.defined.net/290ff4b6/v0.8.4/windows/arm64/DNClient-Desktop.msi",
52+
"windows-arm64-server": "https://dl.defined.net/290ff4b6/v0.8.4/windows/arm64/DNClient-Server.msi"
53+
}
54+
},
55+
"mobile": {
56+
"android": "https://play.google.com/store/apps/details?id=net.defined.mobile_nebula",
57+
"ios": "https://apps.apple.com/us/app/mobile-nebula/id1509587936"
58+
},
59+
"container": {
60+
"docker": "https://hub.docker.com/r/definednet/dnclient/"
61+
},
62+
"versionInfo": {
63+
"dnclient": {
64+
"0.8.4": {
65+
"latest": true,
66+
"releaseDate": "2025-10-10"
67+
}
68+
},
69+
"latest": {
70+
"dnclient": "0.8.4",
71+
"mobile": "0.5.1"
72+
}
73+
}
74+
}
75+
}`

client_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1156,3 +1156,59 @@ func TestDoOidcPoll(t *testing.T) {
11561156
assert.Empty(t, ts.Errors())
11571157
assert.Equal(t, 0, ts.RequestsRemaining())
11581158
}
1159+
1160+
func TestDownloads(t *testing.T) {
1161+
t.Parallel()
1162+
1163+
useragent := "dnclientUnitTests/1.0.0 (not a real client)"
1164+
ts := dnapitest.NewServer(useragent)
1165+
client := NewClient(useragent, ts.URL)
1166+
t.Cleanup(func() { ts.Close() })
1167+
1168+
// Happy path - successful downloads response
1169+
ts.ExpectAPIRequest(http.StatusOK, func(r any) []byte {
1170+
return []byte(downloadsResponse)
1171+
})
1172+
1173+
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
1174+
defer cancel()
1175+
resp, err := client.Downloads(ctx)
1176+
require.NoError(t, err)
1177+
assert.Empty(t, ts.Errors())
1178+
assert.Equal(t, 0, ts.RequestsRemaining())
1179+
1180+
// Verify DNClient downloads - this list is not exhaustive but tests some of the more common platforms.
1181+
require.NotNil(t, resp)
1182+
require.Contains(t, resp.DNClient, "0.8.4")
1183+
assert.Equal(t, "https://dl.defined.net/290ff4b6/v0.8.4/freebsd/amd64/dnclient", resp.DNClient["0.8.4"]["freebsd-amd64"])
1184+
assert.Equal(t, "https://dl.defined.net/290ff4b6/v0.8.4/freebsd/arm64/dnclient", resp.DNClient["0.8.4"]["freebsd-arm64"])
1185+
assert.Equal(t, "https://dl.defined.net/290ff4b6/v0.8.4/linux/amd64/dnclient", resp.DNClient["0.8.4"]["linux-amd64"])
1186+
assert.Equal(t, "https://dl.defined.net/290ff4b6/v0.8.4/linux/arm64/dnclient", resp.DNClient["0.8.4"]["linux-arm64"])
1187+
assert.Equal(t, "https://dl.defined.net/290ff4b6/v0.8.4/windows/amd64/DNClient-Desktop.msi", resp.DNClient["0.8.4"]["windows-amd64-desktop"])
1188+
assert.Equal(t, "https://dl.defined.net/290ff4b6/v0.8.4/windows/amd64/DNClient-Server.msi", resp.DNClient["0.8.4"]["windows-amd64-server"])
1189+
assert.Equal(t, "https://dl.defined.net/290ff4b6/v0.8.4/windows/arm64/DNClient-Desktop.msi", resp.DNClient["0.8.4"]["windows-arm64-desktop"])
1190+
assert.Equal(t, "https://dl.defined.net/290ff4b6/v0.8.4/windows/arm64/DNClient-Server.msi", resp.DNClient["0.8.4"]["windows-arm64-server"])
1191+
assert.Equal(t, "https://dl.defined.net/290ff4b6/v0.8.4/macos/dnclient", resp.DNClient["0.8.4"]["macos-universal-server"])
1192+
assert.Equal(t, "https://dl.defined.net/290ff4b6/v0.8.4/macos/DNClient-Server.dmg", resp.DNClient["0.8.4"]["macos-universal-server-dmg"])
1193+
assert.Equal(t, "https://dl.defined.net/290ff4b6/v0.8.4/macos/DNClient-Desktop.dmg", resp.DNClient["0.8.4"]["macos-universal-desktop"])
1194+
1195+
// Verify the latest release info
1196+
require.Contains(t, resp.DNClient, "latest")
1197+
require.Equal(t, resp.DNClient["latest"], resp.DNClient["0.8.4"])
1198+
1199+
// Verify mobile downloads
1200+
assert.Equal(t, "https://play.google.com/store/apps/details?id=net.defined.mobile_nebula", resp.Mobile.Android)
1201+
assert.Equal(t, "https://apps.apple.com/us/app/mobile-nebula/id1509587936", resp.Mobile.IOS)
1202+
1203+
// Verify container downloads
1204+
assert.Equal(t, "https://hub.docker.com/r/definednet/dnclient/", resp.Container.Docker)
1205+
1206+
// Verify version info
1207+
require.Contains(t, resp.VersionInfo.DNClient, "0.8.4")
1208+
assert.True(t, resp.VersionInfo.DNClient["0.8.4"].Latest)
1209+
assert.Equal(t, "2025-10-10", resp.VersionInfo.DNClient["0.8.4"].ReleaseDate)
1210+
1211+
// Verify latest versions
1212+
assert.Equal(t, "0.8.4", resp.VersionInfo.Latest.DNClient)
1213+
assert.Equal(t, "0.5.1", resp.VersionInfo.Latest.Mobile)
1214+
}

dnapitest/dnapitest.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ func (s *Server) handler(w http.ResponseWriter, r *http.Request) {
7474
_, _ = w.Write(expected.Respond(nil))
7575
case message.AuthPollEndpoint:
7676
s.handlerDoOidcPoll(w, r)
77+
case message.DownloadsEndpoint:
78+
expected := s.expectedRequests[0]
79+
s.expectedRequests = s.expectedRequests[1:]
80+
w.WriteHeader(expected.StatusCode())
81+
_, _ = w.Write(expected.Respond(nil))
7782
default:
7883
s.errors = append(s.errors, fmt.Errorf("invalid request path %s", r.URL.Path))
7984
http.NotFound(w, r)

message/message.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,46 @@ type EndpointAuthPollData struct {
246246
EnrollmentCode string `json:"enrollmentCode"`
247247
}
248248

249+
const DownloadsEndpoint = "/v1/downloads"
250+
251+
type DownloadsData struct {
252+
// DNClient maps versions to a map of platforms' download links.
253+
DNClient map[string]map[string]string `json:"dnclient"`
254+
// Mobile maps platforms to their download links (i.e. App Store / Play Store.)
255+
Mobile DownloadsMobile `json:"mobile"`
256+
// Links to container repositories like Docker
257+
Container DownloadsContainers `json:"container"`
258+
259+
// VersionInfo contains information about past versions.
260+
VersionInfo DownloadsVersionInfo `json:"versionInfo"`
261+
}
262+
263+
type DownloadsVersionInfo struct {
264+
// DNClient maps versions to their version info.
265+
DNClient map[string]DNClientVersionInfo `json:"dnclient"`
266+
// Latest returns the latest versions for each platform.
267+
Latest DownloadsLatest `json:"latest"`
268+
}
269+
270+
type DownloadsMobile struct {
271+
Android string `json:"android"`
272+
IOS string `json:"ios"`
273+
}
274+
275+
type DownloadsContainers struct {
276+
Docker string `json:"docker"`
277+
}
278+
279+
type DownloadsLatest struct {
280+
DNClient string `json:"dnclient"`
281+
Mobile string `json:"mobile"`
282+
}
283+
284+
type DNClientVersionInfo struct {
285+
Latest bool `json:"latest"`
286+
ReleaseDate string `json:"releaseDate"`
287+
}
288+
249289
// NetworkCurve represents the network curve specified by the API.
250290
type NetworkCurve string
251291

0 commit comments

Comments
 (0)