Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/next/lib/share/proxy/api.ts
Views: 687
/*1api.github.com is very nice to use to get info, but23"Unauthenticated clients can make **60 requests per hour**."4https://docs.github.com/en/rest/guides/getting-started-with-the-rest-api56So it's completely useless for our purposes without authentication.78"When authenticating, you should see your rate limit bumped to 5,000 requests an hour,"910which is also hopefully sufficient, but worrisome.1112Thoughts:1314- Since all rendering could be done client side, I could actually have15the client browser grab content instead of the server, then render there to16massively reduce api load, although even users could quickly hit "60 requests17per hour", so the api still wouldn't help.1819- If we do hit the 5K/hour limit, maybe we can use more than one api key?2021- Upgrading to enterprise doesn't increase this much.2223- We could switch to mirroring and cloning files locally, and that might24work around this problem in practice, but be a lot of work. We'll see.252627Get at https://github.com/settings/tokens28*/2930// these are provided by nextjs: https://nextjs.org/blog/next-9-4#improved-built-in-fetch-support31declare var fetch, Headers;3233import { encode } from "base-64";34import { join } from "path";35import getPool from "@cocalc/database/pool";3637// We don't allow just fetching content that is arbitrarily large, since that could cause38// the server to just run out of memory. However, we want this to reasonably big.39export const RAW_MAX_SIZE_BYTES = 10000000; // 10MB4041// TODO: we will also have a raw blob or stream or something for serving up images, etc.,42export async function rawText(43githubOrg: string,44githubRepo: string,45segments: string[]46): Promise<string> {47const url = rawURL(githubOrg, githubRepo, segments);48//console.log("raw:", { url });49return await (await fetch(url, { size: RAW_MAX_SIZE_BYTES })).text();50}5152function rawURL(53githubOrg: string,54githubRepo: string,55segments: string[]56): string {57return `https://raw.githubusercontent.com/${githubOrg}/${githubRepo}/${join(58...segments.slice(1)59)}`;60}6162interface GithubFile {63name: string;64path: string;65sha: string;66size: number;67url: string;68html_url: string;69git_url: string;70download_url: string;71type: "file" | "dir";72content: string;73encoding: string;74}7576async function credentials(): Promise<{77github_username?: string;78github_token?: string;79}> {80const pool = getPool("long");81const { rows } = await pool.query(82"SELECT name, value FROM server_settings WHERE name='github_username' OR name='github_token'"83);84let result: { github_username?: string; github_token?: string } = {};85for (const row of rows) {86result[row.name] = row.value;87}88return result;89}9091export async function api(path: string): Promise<any> {92const url = `https://api.github.com/${path}`;93const options: any = {};94const { github_username, github_token } = await credentials();95if (github_username && github_token) {96options.headers = new Headers({97Authorization: "Basic " + encode(`${github_username}:${github_token}`),98"Content-Type": "application/json",99});100}101//console.log(options);102const response = await fetch(url, options);103//console.log(response.headers);104const data: any = await response.json();105//console.log({ url, response });106if (data.message) {107throw Error(`${data.message} (see ${data.documentation_url})`);108}109return data;110}111112// Use the github api to get the contents of a path on github.113// We are planning to use this just to get directory listings,114// since individual files have their content base64 encoded, etc.,115// and that has to be much slower than just grabbing the116// file form raw (and also only works up to 1MB according to117// github docs).118// How to do auth + fetch with node: https://stackoverflow.com/questions/43842793/basic-authentication-with-fetch119export async function contents(120githubOrg: string,121githubRepo: string,122segments: string[]123): Promise<GithubFile[]> {124let ref, path;125if (segments.length == 0) {126ref = ""; // the default;127path = ""; // root128} else {129// tree/[ref]/[path ...]130ref = segments[1];131path = join(...segments.slice(2));132}133const result = await api(134`repos/${githubOrg}/${githubRepo}/contents/${path}${135ref ? "?ref=" + ref : ""136}`137);138if (result.name != null) {139throw Error(140"only use contents to get directory listing, not to get file contents"141);142}143return result;144}145146export async function defaultBranch(147githubOrg: string,148githubRepo: string149): Promise<string> {150return (await api(`repos/${githubOrg}/${githubRepo}`)).default_branch;151}152153// Return all the repositories in a GitHub organization or user:154export async function repos(githubOrg: string): Promise<{ name: string }[]> {155let result;156try {157result = await api(`orgs/${githubOrg}/repos`);158} catch (err) {159result = await api(`users/${githubOrg}/repos`);160}161return result162.filter((repo) => !repo.private)163.map((repo) => {164return {165isdir: true,166name: repo.name,167mtime: new Date(repo.updated_at).valueOf(),168url: `/github/${githubOrg}/${repo.name}`,169};170});171}172173export async function fileInGist(gistId: string): Promise<string> {174const info = await api(`gists/${gistId}`);175for (const filename in info.files) {176return filename;177}178throw Error("no files in the gist");179}180181182