8000 Merge pull request #526 from coderoad/fallback-to-file · dcmade01/coderoad-vscode@312ac1a · GitHub
[go: up one dir, main page]

Skip to content

Commit 312ac1a

Browse files
authored
Merge pull request coderoad#526 from coderoad/fallback-to-file
fallback session state to file
2 parents d01b0d2 + ba4e1ac commit 312ac1a

File tree

7 files changed

+77
-8
lines changed
  • services
  • 7 files changed

    +77
    -8
    lines changed

    docs/docs/env-vars.md

    Lines changed: 2 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -20,6 +20,8 @@ CodeRoad has a number of configurations:
    2020

    2121
    - `CODEROAD_WEBHOOK_TOKEN` - an optional token for authenticating/authorizing webhook endpoints. Passed to the webhook endpoint in a `CodeRoad-User-Token` header.
    2222

    23+
    - `CODEROAD_SESSION_STORAGE_PATH` - the path to a directory for writing session storage to files. Helps preserves state across containers. Example: `../tmp`.
    24+
    2325
    ## How to Use Variables
    2426

    2527
    ### Local

    src/actions/onStartup.ts

    Lines changed: 2 additions & 2 deletions
    Original file line numberDiff line numberDiff line change
    @@ -35,8 +35,8 @@ const onStartup = async (context: Context): Promise<void> => {
    3535

    3636
    // NEW: no stored tutorial, must start new tutorial
    3737
    if (!tutorial || !tutorial.id) {
    38-
    if (!!TUTORIAL_URL) {
    39-
    // NEW_FROM_URL
    38+
    if (TUTORIAL_URL) {
    39+
    // if a tutorial URL is added, launch on startup
    4040
    try {
    4141
    const tutorialRes = await fetch(TUTORIAL_URL)
    4242
    const tutorial = await tutorialRes.json()

    src/environment.ts

    Lines changed: 3 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -46,3 +46,6 @@ export const CONTENT_SECURITY_POLICY_EXEMPTIONS: string | null =
    4646

    4747
    // optional token for authorization/authentication of webhook calls
    4848
    export const WEBHOOK_TOKEN = process.env.CODEROAD_WEBHOOK_TOKEN || null
    49+
    50+
    // a path to write session state to a file. Useful for maintaining session across containers
    51+
    export const SESSION_STORAGE_PATH = process.env.CODEROAD_SESSION_STORAGE_PATH || null

    src/services/context/state/Position.ts

    Lines changed: 1 addition & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -19,6 +19,7 @@ class Position {
    1919
    setTutorial(workspaceState: vscode.Memento, tutorial: TT.Tutorial): void {
    2020
    this.storage = new Storage<T.Position>({
    2121
    key: `coderoad:position:${tutorial.id}:${tutorial.version}`,
    22+
    filePath: 'coderoad_position',
    2223
    storage: workspaceState,
    2324
    defaultValue,
    2425
    })

    src/services/context/state/Tutorial.ts

    Lines changed: 1 addition & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -9,6 +9,7 @@ class Tutorial {
    99
    constructor(workspaceState: vscode.Memento) {
    1010
    this.storage = new Storage<TT.Tutorial | null>({
    1111
    key: 'coderoad:currentTutorial',
    12+
    filePath: 'coderoad_tutorial',
    1213
    storage: workspaceState,
    1314
    defaultValue: null,
    1415
    })

    src/services/node/index.ts

    Lines changed: 20 additions & 4 deletions
    Original file line numberDiff line numberDiff line change
    @@ -7,25 +7,41 @@ import { WORKSPACE_ROOT } from '../../environment'
    77
    const asyncExec = promisify(cpExec)
    88
    const asyncRemoveFile = promisify(fs.unlink)
    99
    const asyncReadFile = promisify(fs.readFile)
    10+
    const asyncWriteFile = promisify(fs.writeFile)
    1011

    1112
    interface ExecParams {
    1213
    command: string
    1314
    dir?: string
    1415
    }
    1516

    17+
    // correct paths to be from workspace root rather than extension folder
    18+
    const getWorkspacePath = (...paths: string[]) => {
    19+
    return join(WORKSPACE_ROOT, ...paths)
    20+
    }
    21+
    1622
    export const exec = (params: ExecParams): Promise<{ stdout: string; stderr: string }> | never => {
    1723
    const cwd = join(WORKSPACE_ROOT, params.dir || '')
    1824
    return asyncExec(params.command, { cwd })
    1925
    }
    2026

    2127
    export const exists = (...paths: string[]): boolean | never => {
    22-
    return fs.existsSync(join(WORKSPACE_ROOT, ...paths))
    28+
    return fs.existsSync(getWorkspacePath(...paths))
    2329
    }
    2430

    2531
    export const removeFile = (...paths: string[]) => {
    26-
    return asyncRemoveFile(join(WORKSPACE_ROOT, ...paths))
    32+
    return asyncRemoveFile(getWorkspacePath(...paths))
    33+
    }
    34+
    35+
    export const readFile = (...paths: string[]): Promise<string | void> => {
    36+
    const filePath = getWorkspacePath(...paths)
    37+
    return asyncReadFile(getWorkspacePath(...paths), 'utf8').catch((err) => {
    38+
    console.warn(`Failed to read from ${filePath}: ${err.message}`)
    39+
    })
    2740
    }
    2841

    29-
    export const readFile = (...paths: string[]) => {
    30-
    return asyncReadFile(join(...paths))
    42+
    export const writeFile = (data: any, ...paths: string[]): Promise<void> => {
    43+
    const filePath = getWorkspacePath(...paths)
    44+
    return asyncWriteFile(filePath, data).catch((err) => {
    45+
    console.warn(`Failed to write to ${filePath}: ${err.message}`)
    46+
    })
    3147
    }

    src/services/storage/index.ts

    Lines changed: 48 additions & 2 deletions
    Original file line numberDiff line numberDiff line change
    @@ -1,4 +1,6 @@
    11
    import * as vscode from 'vscode'
    2+
    import { readFile, writeFile } from '../node'
    3+
    import { SESSION_STORAGE_PATH } from '../../environment'
    24

    35
    // NOTE: localStorage is not available on client
    46
    // and must be stored in editor
    @@ -8,31 +10,75 @@ import * as vscode from 'vscode'
    810
    // forcing it to be passed in through activation and down to other tools
    911
    class Storage<T> {
    1012
    private key: string
    13+
    private filePath: string
    1114
    private storage: vscode.Memento
    1215
    private defaultValue: T
    13-
    constructor({ key, storage, defaultValue }: { key: string; storage: vscode.Memento; defaultValue: T }) {
    16+
    constructor({
    17+
    key,
    18+
    filePath,
    19+
    storage,
    20+
    defaultValue,
    21+
    }: {
    22+
    key: string
    23+
    filePath: string
    24+
    storage: vscode.Memento
    25+
    defaultValue: T
    26+
    }) {
    1427
    this.storage = storage
    1528
    this.key = key
    29+
    this.filePath = filePath
    1630
    this.defaultValue = defaultValue
    1731
    }
    1832
    public get = async (): Promise<T> => {
    1933
    const value: string | undefined = await this.storage.get(this.key)
    2034
    if (value) {
    2135
    return JSON.parse(value)
    36+
    } else if (SESSION_STORAGE_PATH) {
    37+
    try {
    38+
    // optionally read from file as a fallback to local storage
    39+
    const sessionFile = await readFile(SESSION_STORAGE_PATH, `${this.filePath}.json`)
    40+
    if (!sessionFile) {
    41+
    throw new Error('No session file found')
    42+
    }
    43+
    const data: T = JSON.parse(sessionFile)
    44+
    45+
    if (data) {
    46+
    // validate session
    47+
    const keys = Object.keys(data)
    48+
    if (keys.length) {
    49+
    return data
    50+
    }
    51+
    }
    52+
    } catch (err) {
    53+
    console.warn(`Failed to read or parse session file: ${SESSION_STORAGE_PATH}/${this.filePath}.json`)
    54+
    }
    2255
    }
    2356
    return this.defaultValue
    2457
    }
    2558
    public set = (value: T): void => {
    2659
    const stringValue = JSON.stringify(value)
    2760
    this.storage.update(this.key, stringValue)
    61+
    this.writeToSessionFile(stringValue)
    2862
    }
    2963
    public update = async (value: T): Promise<void> => {
    3064
    const current = await this.get()
    3165
    const next = JSON.stringify({
    3266
    ...current,
    3367
    ...value,
    3468
    })
    35-
    this.storage.update(this.key, next)
    69+
    await this.storage.update(this.key, next)
    70+
    71+
    this.writeToSessionFile(next)
    72+
    }
    73+
    public writeToSessionFile(data: string) {
    74+
    // optionally write state to file, useful when state cannot be controlled across containers
    75+
    if (SESSION_STORAGE_PATH) {
    76+
    try {
    77+
    writeFile(data, SESSION_STORAGE_PATH, `${this.filePath}.json`)
    78+
    } catch (err: any) {
    79+
    console.warn(`Failed to write coderoad session to path: ${SESSION_STORAGE_PATH}/${this.filePath}.json`)
    80+
    }
    81+
    }
    3682
    }
    3783
    public reset = () => {
    3884
    this.set(this.defaultValue)

    0 commit comments

    Comments
     (0)
    0