|
| 1 | +import { |
| 2 | + call, |
| 3 | + createContext, |
| 4 | + Operation, |
| 5 | + useAbortSignal, |
| 6 | +} from "effection"; |
| 7 | + |
| 8 | +const PodcastContext = createContext<Episode[]>( |
| 9 | + "podcast", |
| 10 | +); |
| 11 | + |
| 12 | +export interface SimplecastClient { |
| 13 | + getEpisodes(name: string): Operation<Episode[]>; |
| 14 | +} |
| 15 | + |
| 16 | +export interface Podcast { |
| 17 | + readonly title: string; |
| 18 | + readonly id: string; |
| 19 | +} |
| 20 | + |
| 21 | +export interface Episode { |
| 22 | + readonly id: string; |
| 23 | + readonly title: string; |
| 24 | + readonly description: string; |
| 25 | + readonly image_url: string; |
| 26 | + readonly href: string; |
| 27 | + readonly duration: number; |
| 28 | + readonly linkname: string; |
| 29 | +} |
| 30 | +export function* initSimpleCast(apiKey?: string) { |
| 31 | + if (!apiKey) { |
| 32 | + console.log(`simplecast: disabled`); |
| 33 | + yield* PodcastContext.set([]); |
| 34 | + } else { |
| 35 | + let client = new HTTPClient({ apiKey }); |
| 36 | + let episodes = yield* client.getEpisodes("The Frontside Podcast"); |
| 37 | + console.log(`simplecast: loaded ${episodes.length} episodes`); |
| 38 | + yield* PodcastContext.set(episodes); |
| 39 | + } |
| 40 | +} |
| 41 | + |
| 42 | +export function* usePodcastEpisodes(): Operation<Episode[]> { |
| 43 | + return yield* PodcastContext; |
| 44 | +} |
| 45 | + |
| 46 | +interface HTTPCLientOptions { |
| 47 | + apiKey: string; |
| 48 | +} |
| 49 | + |
| 50 | +class HTTPClient implements SimplecastClient { |
| 51 | + constructor(public readonly options: HTTPCLientOptions) {} |
| 52 | + |
| 53 | + *getEpisodes(title: string): Operation<Episode[]> { |
| 54 | + let podcasts = yield* this.getPodcasts(); |
| 55 | + let podcast = podcasts.find((p) => p.title === title); |
| 56 | + if (!podcast) { |
| 57 | + throw new Error( |
| 58 | + `unable to find podcast: ${title} in [${ |
| 59 | + podcasts.map((p) => p.title).join(", ") |
| 60 | + }]`, |
| 61 | + ); |
| 62 | + } |
| 63 | + let response = yield* this.request(`/podcasts/${podcast.id}/episodes`, { |
| 64 | + limit: "200", |
| 65 | + }); |
| 66 | + let json = yield* call(() => response.json()); |
| 67 | + return json.collection.map((episode: Episode) => ({ |
| 68 | + ...episode, |
| 69 | + linkname: episode.title.toLowerCase().replaceAll(/\s/g, "-"), |
| 70 | + })); |
| 71 | + } |
| 72 | + |
| 73 | + *getPodcasts(): Operation<Podcast[]> { |
| 74 | + let response = yield* this.request("/podcasts"); |
| 75 | + let json = yield* call(() => response.json()); |
| 76 | + return json.collection; |
| 77 | + } |
| 78 | + |
| 79 | + private *request( |
| 80 | + pathname: string, |
| 81 | + params: Record<string, string> = {}, |
| 82 | + ): Operation<Response> { |
| 83 | + let url = new URL(`https://api.simplecast.com`); |
| 84 | + url.pathname = pathname; |
| 85 | + url.search = new URLSearchParams(params).toString(); |
| 86 | + let signal = yield* useAbortSignal(); |
| 87 | + let response = yield* call(() => |
| 88 | + fetch(url, { |
| 89 | + signal, |
| 90 | + headers: { |
| 91 | + "Authorization": `Bearer ${this.options.apiKey}`, |
| 92 | + }, |
| 93 | + }) |
| 94 | + ); |
| 95 | + if (!response.ok) { |
| 96 | + throw new Error(`${response.status}: ${response.statusText}`, { |
| 97 | + cause: pathname, |
| 98 | + }); |
| 99 | + } else { |
| 100 | + return response; |
| 101 | + } |
| 102 | + } |
| 103 | +} |
| 104 | + |
0 commit comments