Skip to content

Commit

Permalink
Load a fresh api key from the backend on each search session (#151)
Browse files Browse the repository at this point in the history
* Upgrade @fern-fern/registry-browser

* Update useSearchService to load api keys on demand

* Make sure algolia client reference doesn't change between rerenders

* Import existing registry client instead of creating a new instance

* Implement createSearchApiKeyLoader()
  • Loading branch information
kafkas authored Sep 23, 2023
1 parent 9dde2fd commit 0d3652a
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 60 deletions.
12 changes: 6 additions & 6 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
2 changes: 1 addition & 1 deletion packages/commons/app-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"depcheck": "depcheck"
},
"dependencies": {
"@fern-fern/registry-browser": "0.20.0-2-g764a1b3",
"@fern-fern/registry-browser": "0.21.0-2-g96ea287",
"@fern-ui/core-utils": "workspace:*",
"lodash-es": "^4.17.21",
"next-mdx-remote": "^4.4.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"@blueprintjs/icons": "^4.4.0",
"@blueprintjs/popover2": "^1.8.0",
"@blueprintjs/select": "^4.4.2",
"@fern-fern/registry-browser": "0.20.0-2-g764a1b3",
"@fern-fern/registry-browser": "0.21.0-2-g96ea287",
"@fern-ui/app-utils": "workspace:*",
"@fern-ui/core-utils": "workspace:*",
"@fern-ui/react-commons": "workspace:*",
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/app/src/env.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
type EnvironmentConfig = {
export interface EnvironmentConfig {
algoliaAppId: string;
/**
* This is used by the legacy search system.
*/
algoliaApiKey: string;

algoliaSearchIndex?: string;
};
}

export function getEnvConfig(): EnvironmentConfig {
if (process.env.NEXT_PUBLIC_ALGOLIA_APP_ID == null) {
Expand Down
83 changes: 50 additions & 33 deletions packages/ui/app/src/search/SearchDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Icon } from "@blueprintjs/core";
import { Dialog } from "@headlessui/react";
import algolia from "algoliasearch/lite";
import classNames from "classnames";
import { useEffect, useMemo, useState } from "react";
import { InstantSearch, SearchBox } from "react-instantsearch-hooks-web";
import { useSearchService } from "../services/useSearchService";
import { useSearchService, type SearchCredentials } from "../services/useSearchService";
import styles from "./SearchDialog.module.scss";
import { SearchHits } from "./SearchHits";

Expand All @@ -17,43 +19,58 @@ export declare namespace SearchDialog {
export const SearchDialog: React.FC<SearchDialog.Props> = (providedProps) => {
const { isOpen, onClose, activeVersion } = providedProps;
const searchService = useSearchService();
const [credentials, setSearchCredentials] = useState<SearchCredentials | undefined>(undefined);

if (!searchService.isAvailable) {
return null;
}
useEffect(() => {
if (isOpen && searchService.isAvailable) {
void (async () => {
const credentials = await searchService.loadCredentials();
setSearchCredentials(credentials);
})();
}
}, [isOpen, searchService]);

const searchClient = useMemo(() => {
if (credentials?.appId == null) {
return undefined;
}
return algolia(credentials.appId, credentials.searchApiKey);
}, [credentials?.appId, credentials?.searchApiKey]);

return (
<Dialog as="div" className="fixed inset-0 z-30" open={isOpen} onClose={onClose}>
<InstantSearch searchClient={searchService.client} indexName={searchService.index}>
<div className="flex min-h-screen items-start justify-center p-4">
<Dialog.Overlay className="bg-background-tertiary-light/40 dark:bg-background-tertiary-dark/40 fixed inset-0 backdrop-blur-sm" />
<div className="bg-background-primary-light dark:bg-background-primary-dark z-10 mx-3 mb-8 mt-10 flex w-full max-w-2xl flex-col overflow-hidden rounded-md text-left align-middle shadow-2xl">
<div className={classNames(styles.searchBox, "flex items-center space-x-3 px-5")}>
<Icon className="t-muted" icon="search" size={14} />
<SearchBox
inputMode="text"
autoFocus
placeholder={
activeVersion != null
? `Search across version ${activeVersion}`
: "Find something..."
}
classNames={{
root: "w-full",
loadingIcon: "hidden",
loadingIndicator: "hidden",
reset: "hidden",
resetIcon: "hidden",
submit: "hidden",
submitIcon: "hidden",
input: "w-full text-base t-muted placeholder:text-text-muted-light placeholder:dark:text-text-muted-dark bg-transparent py-5",
}}
/>
<div className="flex min-h-screen items-start justify-center p-4">
<Dialog.Overlay className="bg-background-tertiary-light/40 dark:bg-background-tertiary-dark/40 fixed inset-0 backdrop-blur-sm" />
{searchService.isAvailable && searchClient != null && (
<InstantSearch searchClient={searchClient} indexName={searchService.index}>
<div className="bg-background-primary-light dark:bg-background-primary-dark z-10 mx-3 mb-8 mt-10 flex w-full max-w-2xl flex-col overflow-hidden rounded-md text-left align-middle shadow-2xl">
<div className={classNames(styles.searchBox, "flex items-center space-x-3 px-5")}>
<Icon className="t-muted" icon="search" size={14} />
<SearchBox
inputMode="text"
autoFocus
placeholder={
activeVersion != null
? `Search across version ${activeVersion}`
: "Find something..."
}
classNames={{
root: "w-full",
loadingIcon: "hidden",
loadingIndicator: "hidden",
reset: "hidden",
resetIcon: "hidden",
submit: "hidden",
submitIcon: "hidden",
input: "w-full text-base t-muted placeholder:text-text-muted-light placeholder:dark:text-text-muted-dark bg-transparent py-5",
}}
/>
</div>
<SearchHits />
</div>
<SearchHits />
</div>
</div>
</InstantSearch>
</InstantSearch>
)}
</div>
</Dialog>
);
};
50 changes: 41 additions & 9 deletions packages/ui/app/src/services/useSearchService.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { FernRegistry } from "@fern-fern/registry-browser";
import algolia, { type SearchClient } from "algoliasearch/lite";
import { useMemo } from "react";

import { useCallback, useMemo } from "react";
import { useDocsContext } from "../docs-context/useDocsContext";
import { getEnvConfig } from "../env";
import { getEnvConfig, type EnvironmentConfig } from "../env";
import { REGISTRY_SERVICE } from "./registry";

export type SearchCredentials = {
appId: string;
searchApiKey: string;
};

export type SearchService =
| {
isAvailable: true;
client: SearchClient;
loadCredentials: () => Promise<SearchCredentials | undefined>;
index: string;
}
| {
Expand All @@ -18,13 +24,35 @@ export function useSearchService(): SearchService {
const { docsDefinition, docsInfo } = useDocsContext();
const { search: searchInfo } = docsDefinition;

const createSearchApiKeyLoader = useCallback(
(envConfig: EnvironmentConfig, indexSegmentId: string) => async () => {
const resp = await REGISTRY_SERVICE.docs.v2.read.getSearchApiKeyForIndexSegment({
indexSegmentId,
});
if (!resp.ok) {
// eslint-disable-next-line no-console
console.error("Failed to fetch index segment api key", resp.error);
return undefined;
}
const { searchApiKey } = resp.body;
return {
appId: envConfig.algoliaAppId,
searchApiKey,
};
},
[]
);

return useMemo<SearchService>(() => {
const envConfig = getEnvConfig();
if (typeof searchInfo !== "object") {
return docsDefinition.algoliaSearchIndex != null
? {
isAvailable: true,
client: algolia(envConfig.algoliaAppId, envConfig.algoliaApiKey),
loadCredentials: async () => ({
appId: envConfig.algoliaAppId,
searchApiKey: envConfig.algoliaApiKey,
}),
index: docsDefinition.algoliaSearchIndex,
}
: { isAvailable: false };
Expand All @@ -33,7 +61,10 @@ export function useSearchService(): SearchService {
return algoliaIndex != null
? {
isAvailable: true,
client: algolia(envConfig.algoliaAppId, envConfig.algoliaApiKey),
loadCredentials: async () => ({
appId: envConfig.algoliaAppId,
searchApiKey: envConfig.algoliaApiKey,
}),
index: algoliaIndex,
}
: { isAvailable: false };
Expand All @@ -45,9 +76,10 @@ export function useSearchService(): SearchService {
throw new Error('Missing environment variable "NEXT_PUBLIC_ALGOLIA_SEARCH_INDEX"');
}
const { indexSegment } = searchInfo.value;

return {
isAvailable: true,
client: algolia(envConfig.algoliaAppId, indexSegment.searchApiKey),
loadCredentials: createSearchApiKeyLoader(envConfig, indexSegment.id),
index: envConfig.algoliaSearchIndex,
};
} else {
Expand All @@ -67,9 +99,9 @@ export function useSearchService(): SearchService {
}
return {
isAvailable: true,
client: algolia(envConfig.algoliaAppId, indexSegment.searchApiKey),
loadCredentials: createSearchApiKeyLoader(envConfig, indexSegment.id),
index: envConfig.algoliaSearchIndex,
};
}
}, [docsDefinition.algoliaSearchIndex, docsInfo, searchInfo]);
}, [docsDefinition.algoliaSearchIndex, createSearchApiKeyLoader, docsInfo, searchInfo]);
}
2 changes: 1 addition & 1 deletion packages/ui/fe-bundle/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"lint:fern-prod": "yarn compile && yarn env:fern-prod next lint"
},
"dependencies": {
"@fern-fern/registry-browser": "0.20.0-2-g764a1b3",
"@fern-fern/registry-browser": "0.21.0-2-g96ea287",
"@fern-ui/app-utils": "workspace:*",
"@fern-ui/core-utils": "workspace:*",
"@fern-ui/theme": "workspace:*",
Expand Down
14 changes: 7 additions & 7 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1851,24 +1851,24 @@ __metadata:
languageName: node
linkType: hard

"@fern-fern/registry-browser@npm:0.20.0-2-g764a1b3":
version: 0.20.0-2-g764a1b3
resolution: "@fern-fern/registry-browser@npm:0.20.0-2-g764a1b3"
"@fern-fern/registry-browser@npm:0.21.0-2-g96ea287":
version: 0.21.0-2-g96ea287
resolution: "@fern-fern/registry-browser@npm:0.21.0-2-g96ea287"
dependencies:
"@types/url-join": 4.0.1
"@ungap/url-search-params": 0.2.2
axios: 1.4.0
js-base64: 3.7.2
url-join: 4.0.1
checksum: 544089d6cc1e52c4578a871e40f572213b34d171263e9e9d440f21a0e82f3897872db7adb3e547b25b33385eb8db8472c938c5750c01c259ed48a4fb540c22b2
checksum: 9267ab1b5e4dd11b64cda792956b9f1f123f0dd27c5ef43427130e6d0c69a0f40190db654da1dac6f083c8427c1115804066580a33efb5092437182bf52a7ee2
languageName: node
linkType: hard

"@fern-ui/app-utils@workspace:*, @fern-ui/app-utils@workspace:packages/commons/app-utils":
version: 0.0.0-use.local
resolution: "@fern-ui/app-utils@workspace:packages/commons/app-utils"
dependencies:
"@fern-fern/registry-browser": 0.20.0-2-g764a1b3
"@fern-fern/registry-browser": 0.21.0-2-g96ea287
"@fern-ui/core-utils": "workspace:*"
"@types/jest": ^29.0.3
"@types/lodash-es": 4.17.9
Expand Down Expand Up @@ -1961,7 +1961,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@fern-ui/fe-bundle@workspace:packages/ui/fe-bundle"
dependencies:
"@fern-fern/registry-browser": 0.20.0-2-g764a1b3
"@fern-fern/registry-browser": 0.21.0-2-g96ea287
"@fern-ui/app-utils": "workspace:*"
"@fern-ui/core-utils": "workspace:*"
"@fern-ui/theme": "workspace:*"
Expand Down Expand Up @@ -2200,7 +2200,7 @@ __metadata:
"@blueprintjs/icons": ^4.4.0
"@blueprintjs/popover2": ^1.8.0
"@blueprintjs/select": ^4.4.2
"@fern-fern/registry-browser": 0.20.0-2-g764a1b3
"@fern-fern/registry-browser": 0.21.0-2-g96ea287
"@fern-ui/app-utils": "workspace:*"
"@fern-ui/core-utils": "workspace:*"
"@fern-ui/react-commons": "workspace:*"
Expand Down

1 comment on commit 0d3652a

@vercel
Copy link

@vercel vercel bot commented on 0d3652a Sep 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

fern-dev – ./packages/ui/fe-bundle

fern-dev-git-main-buildwithfern.vercel.app
devtest.buildwithfern.com
fern-dev-buildwithfern.vercel.app
app-dev.buildwithfern.com

Please sign in to comment.