mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-18 19:30:27 +08:00
Refactor api calls
Signed-off-by: Sebastian Martinez <sebastinez@me.com>
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
export let items: { key: string; value: string; badge: string | null }[];
|
||||
export let selected: string | null;
|
||||
export let selected: string | null = null;
|
||||
export let visible = false;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<script lang="ts">
|
||||
import { navigate } from "svelte-routing";
|
||||
import { Seed } from "@app/base/seeds/Seed";
|
||||
import Dropdown from "@app/Dropdown.svelte";
|
||||
import type { Config } from "@app/config";
|
||||
import type { SeedSession } from "@app/siwe";
|
||||
|
||||
export let seeds: { [key: string]: SeedSession };
|
||||
export let seedDropdown = false;
|
||||
export let toggleDropdown: () => void;
|
||||
export let config: Config;
|
||||
|
||||
// When a user signs into a new seed, we want to update the seed listing
|
||||
const formatSeeds = async () => {
|
||||
return await Promise.all(Object.values(seeds).map(async session => {
|
||||
let seed = await Seed.lookup(session.domain, config);
|
||||
let key = `${seed.emoji} ${seed.host}`;
|
||||
|
||||
return { key, value: seed.host, badge: null };
|
||||
}));
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.selector {
|
||||
margin-left: 2rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="selector">
|
||||
<span>
|
||||
<button class="small" on:click={toggleDropdown}>
|
||||
Seeds
|
||||
</button>
|
||||
{#await formatSeeds() then items}
|
||||
<Dropdown
|
||||
{items}
|
||||
visible={seedDropdown}
|
||||
on:select={({ detail }) => {
|
||||
seedDropdown = false;
|
||||
navigate(`/seeds/${detail}`);
|
||||
}}
|
||||
/>
|
||||
{/await}
|
||||
</span>
|
||||
</div>
|
||||
+84
-36
@@ -3,50 +3,98 @@ export interface Host {
|
||||
port: number | null;
|
||||
}
|
||||
|
||||
export async function get(
|
||||
path: string,
|
||||
params: Record<string, any>,
|
||||
api: Host
|
||||
): Promise<any> {
|
||||
const query: Record<string, string> = {};
|
||||
for (const [key, val] of Object.entries(params)) {
|
||||
if (val !== undefined && val !== null) {
|
||||
query[key] = val.toString();
|
||||
}
|
||||
export class Request {
|
||||
path: string;
|
||||
base: string;
|
||||
protocol: string;
|
||||
port: number;
|
||||
|
||||
constructor(path: string, api: Host) {
|
||||
this.port = api.port || 8777;
|
||||
this.base = api.host;
|
||||
this.path = path.startsWith("/") ? path.slice(1) : path;
|
||||
this.protocol = api.host === "0.0.0.0" ? "http://" : "https://";
|
||||
}
|
||||
|
||||
const base = api.host;
|
||||
const port = api.port;
|
||||
const search = new URLSearchParams(query).toString();
|
||||
// Allow using the functionality with local runned http-api
|
||||
const isLocalhost = /^0.0.0.0$/.test(base);
|
||||
const protocol = isLocalhost ? "http://" : "https://";
|
||||
async get(
|
||||
params: Record<string, any> = {},
|
||||
headers: Record<string, string> = {},
|
||||
): Promise<any> {
|
||||
const query = this.formatParams(params);
|
||||
const search = new URLSearchParams(query).toString();
|
||||
const urlString = this.createUrl(search);
|
||||
|
||||
path = path.startsWith("/") ? path.slice(1) : path;
|
||||
|
||||
const baseUrl = path
|
||||
? `${protocol}${base}/v1/${path}`
|
||||
: `${protocol}${base}`;
|
||||
const url = new URL(search ? `${baseUrl}?${search}` : baseUrl);
|
||||
url.port = String(port);
|
||||
|
||||
const urlString = String(url);
|
||||
let response = null;
|
||||
try {
|
||||
response = await fetch(urlString, {
|
||||
return await Request.exec(urlString, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
headers: { ...headers, 'Accept': 'application/json' }
|
||||
});
|
||||
} catch (err) {
|
||||
throw new ApiError("API request failed", urlString);
|
||||
}
|
||||
|
||||
if (! response.ok) {
|
||||
throw new ApiError("Not found", urlString);
|
||||
async post(
|
||||
params: Record<string, any> = {},
|
||||
headers: Record<string, string> = {},
|
||||
): Promise<any> {
|
||||
const body = this.formatParams(params);
|
||||
const urlString = this.createUrl();
|
||||
|
||||
return await Request.exec(urlString, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
headers: { ...headers, 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
async put(
|
||||
params: Record<string, any> = {},
|
||||
headers: Record<string, string> = {},
|
||||
): Promise<any> {
|
||||
const body = this.formatParams(params);
|
||||
const urlString = this.createUrl();
|
||||
|
||||
return await Request.exec(urlString, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(body),
|
||||
headers: { ...headers, 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
// Executes a request and returns the response.
|
||||
static async exec(urlString: string, props: Record<string, any>): Promise<any> {
|
||||
let response = null;
|
||||
try {
|
||||
response = await fetch(urlString, props);
|
||||
} catch (err) {
|
||||
throw new ApiError("API request failed", urlString);
|
||||
}
|
||||
|
||||
if (! response.ok) {
|
||||
throw new ApiError("Not found", urlString);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
// Filters out undefined and null values.
|
||||
private formatParams(params: Record<string, any>): Record<string, string> {
|
||||
const query: Record<string, string> = {};
|
||||
for (const [key, val] of Object.entries(params)) {
|
||||
if (val !== undefined && val !== null) {
|
||||
query[key] = val.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
// Creates a URL with an eventual query string and port.
|
||||
private createUrl(search?: string): string {
|
||||
const baseUrl = this.path
|
||||
? `${this.protocol}${this.base}/v1/${this.path}`
|
||||
: `${this.protocol}${this.base}`;
|
||||
|
||||
const url = new URL(search ? `${baseUrl}?${search}` : baseUrl);
|
||||
url.port = String(this.port);
|
||||
return String(url);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export class ApiError extends Error {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as api from '@app/api';
|
||||
import { Request, type Host } from '@app/api';
|
||||
import type { Config } from '@app/config';
|
||||
import * as proj from "@app/project";
|
||||
import { isDomain } from '@app/utils';
|
||||
@@ -108,12 +108,12 @@ export class Seed {
|
||||
return result.map((project: proj.ProjectInfo) => ({ ...project, id: project.urn }));
|
||||
}
|
||||
|
||||
static async getPeer({ host, port }: api.Host): Promise<{ id: string }> {
|
||||
return api.get("/peer", {}, { host, port });
|
||||
static async getPeer(host: Host): Promise<{ id: string }> {
|
||||
return new Request("/peer", host).get();
|
||||
}
|
||||
|
||||
static async getInfo({ host, port }: api.Host): Promise<{ version: string }> {
|
||||
return api.get("/", {}, { host, port });
|
||||
static async getInfo(host: Host): Promise<{ version: string }> {
|
||||
return new Request("/", host).get();
|
||||
}
|
||||
|
||||
static async lookup(hostname: string, cfg: Config): Promise<Seed> {
|
||||
|
||||
+17
-17
@@ -1,6 +1,6 @@
|
||||
import { navigate } from 'svelte-routing';
|
||||
import { get, writable } from 'svelte/store';
|
||||
import * as api from '@app/api';
|
||||
import { type Host, Request } from '@app/api';
|
||||
import type { Commit, CommitHeader, CommitsHistory } from '@app/commit';
|
||||
import { isOid, isRadicleId } from '@app/utils';
|
||||
import { Profile, ProfileType } from '@app/profile';
|
||||
@@ -249,8 +249,8 @@ export class Project implements ProjectInfo {
|
||||
return { tree, commit };
|
||||
}
|
||||
|
||||
static async getInfo(nameOrUrn: string, host: api.Host): Promise<ProjectInfo> {
|
||||
const info = await api.get(`projects/${nameOrUrn}`, {}, host);
|
||||
static async getInfo(nameOrUrn: string, host: Host): Promise<ProjectInfo> {
|
||||
const info = await new Request(`projects/${nameOrUrn}`, host).get();
|
||||
|
||||
return {
|
||||
...info,
|
||||
@@ -258,25 +258,25 @@ export class Project implements ProjectInfo {
|
||||
};
|
||||
}
|
||||
|
||||
static async getProjects(host: api.Host): Promise<ProjectInfo[]> {
|
||||
return api.get("projects", {}, host);
|
||||
static async getProjects(host: Host): Promise<ProjectInfo[]> {
|
||||
return new Request("projects", host).get();
|
||||
}
|
||||
|
||||
static async getDelegateProjects(delegate: string, host: api.Host): Promise<ProjectInfo[]> {
|
||||
return api.get(`delegates/${delegate}/projects`, {}, host);
|
||||
static async getDelegateProjects(delegate: string, host: Host): Promise<ProjectInfo[]> {
|
||||
return new Request(`delegates/${delegate}/projects`, host).get();
|
||||
}
|
||||
|
||||
static async getRemote(urn: string, peer: string, host: api.Host): Promise<Remote> {
|
||||
return api.get(`projects/${urn}/remotes/${peer}`, {}, host);
|
||||
static async getRemote(urn: string, peer: string, host: Host): Promise<Remote> {
|
||||
return new Request(`projects/${urn}/remotes/${peer}`, host).get();
|
||||
}
|
||||
|
||||
static async getRemotes(urn: string, host: api.Host): Promise<Peer[]> {
|
||||
return api.get(`projects/${urn}/remotes`, {}, host);
|
||||
static async getRemotes(urn: string, host: Host): Promise<Peer[]> {
|
||||
return new Request(`projects/${urn}/remotes`, host).get();
|
||||
}
|
||||
|
||||
static async getCommits(
|
||||
urn: string,
|
||||
host: api.Host,
|
||||
host: Host,
|
||||
opts?: {
|
||||
parent?: string | null;
|
||||
since?: string;
|
||||
@@ -294,11 +294,11 @@ export class Project implements ProjectInfo {
|
||||
"page": opts?.page,
|
||||
"verified": opts?.verified
|
||||
};
|
||||
return api.get(`projects/${urn}/commits`, params, host);
|
||||
return new Request(`projects/${urn}/commits`, host).get(params);
|
||||
}
|
||||
|
||||
async getCommit(commit: string): Promise<Commit> {
|
||||
return api.get(`projects/${this.urn}/commits/${commit}`, {}, this.seed.api);
|
||||
return new Request(`projects/${this.urn}/commits/${commit}`, this.seed.api).get();
|
||||
}
|
||||
|
||||
async getTree(
|
||||
@@ -306,7 +306,7 @@ export class Project implements ProjectInfo {
|
||||
path: string,
|
||||
): Promise<Tree> {
|
||||
if (path === "/") path = "";
|
||||
return api.get(`projects/${this.urn}/tree/${commit}/${path}`, {}, this.seed.api);
|
||||
return new Request(`projects/${this.urn}/tree/${commit}/${path}`, this.seed.api).get();
|
||||
}
|
||||
|
||||
async getBlob(
|
||||
@@ -314,13 +314,13 @@ export class Project implements ProjectInfo {
|
||||
path: string,
|
||||
options: { highlight: boolean },
|
||||
): Promise<Blob> {
|
||||
return api.get(`projects/${this.urn}/blob/${commit}/${path}`, options, this.seed.api);
|
||||
return new Request(`projects/${this.urn}/blob/${commit}/${path}`, this.seed.api).get(options);
|
||||
}
|
||||
|
||||
async getReadme(
|
||||
commit: string,
|
||||
): Promise<Blob> {
|
||||
return api.get(`projects/${this.urn}/readme/${commit}`, {}, this.seed.api);
|
||||
return new Request(`projects/${this.urn}/readme/${commit}`, this.seed.api).get();
|
||||
}
|
||||
|
||||
navigateTo(browse: BrowseTo): void {
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import { SiweMessage } from "siwe";
|
||||
import { Request, type Host } from '@app/api';
|
||||
import type { Config } from "@app/config";
|
||||
import { removePrefix } from "@app/utils";
|
||||
import { connectSeed } from "./session";
|
||||
import type { Seed } from "./base/seeds/Seed";
|
||||
|
||||
export interface SeedSession {
|
||||
domain: string;
|
||||
address: string;
|
||||
statement: string;
|
||||
uri: string;
|
||||
version: string;
|
||||
chain_id: string;
|
||||
nonce: string;
|
||||
issued_at: number;
|
||||
expiration_time: number;
|
||||
resources: string[];
|
||||
}
|
||||
|
||||
export function createSiweMessage(seed: Seed, address: string, nonce: string, config: Config): string {
|
||||
const date = new Date();
|
||||
const expirationTime = new Date(date.getFullYear(), date.getMonth(), date.getDate() + 7).toISOString();
|
||||
|
||||
const message = new SiweMessage({
|
||||
domain: seed.api.host,
|
||||
address,
|
||||
statement: `${seed.api.host} wants you to sign in with Ethereum`,
|
||||
uri: window.location.origin,
|
||||
nonce,
|
||||
version: '1',
|
||||
resources: [`rad:git:${seed.id}`],
|
||||
expirationTime,
|
||||
chainId: config.network.chainId
|
||||
});
|
||||
|
||||
return message.prepareMessage();
|
||||
}
|
||||
|
||||
export async function createUnauthorizedSession(host: Host): Promise<{ nonce: string; id: string }> {
|
||||
return await new Request(`sessions`, host).post();
|
||||
}
|
||||
|
||||
/// Signs the user into given seed and returns when succesfull a session id
|
||||
export async function signInWithEthereum(seed: Seed, config: Config): Promise<{ id: string } | null> {
|
||||
if (! config.signer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = await createUnauthorizedSession(seed.api);
|
||||
const address = await config.signer.getAddress();
|
||||
const message = createSiweMessage(seed, address, result.nonce, config);
|
||||
const signature = await config.signer.signMessage(message);
|
||||
|
||||
const auth: { id: string; session: SeedSession } = await new Request(`sessions/${result.id}`, seed.api).put({ message, signature: removePrefix(signature) });
|
||||
|
||||
await new Request(`sessions`, seed.api).get({}, { Authorization: result.id });
|
||||
connectSeed({ id: result.id, session: auth.session });
|
||||
|
||||
return { id: result.id };
|
||||
}
|
||||
Reference in New Issue
Block a user