Skip to content

Redesign window.kolibri as a content API pass-through and move it into html5_viewer #15200

Description

@rtibbles

This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.

Overview

window.kolibri is installed for every sandboxed viewer but only answered for custom-presentation channels, so from an H5P or plain HTML5 node a call never settles. Where it does work the surface is broken: getContext() always resolves to {}, failures reject with undefined, and pagination leaks the backend's query vocabulary into an otherwise camelCase API. Replace it with a thin pass-through to the content API, and move both halves of the shim into the html5_viewer plugin.

Complexity: High
Target branch: develop

Context

  • Kolibri is one of SandboxHandler.baseShims (packages/kolibri-sandbox/src/SandboxHandler.js:43). Its main side is kolibri/plugins/learn/frontend/views/ChannelRenderer/CustomContentRenderer.vue: 11 sandbox.on registrations, a local createReturnMsg, and sandbox.mediator.sendMessage replies.
  • getContext() replies data: {} unconditionally (CustomContentRenderer.vue:306), and its fallback — the context computed at :83-86 — is JSON.stringify(decodeURI(this.$route.query)), which yields "[object Object]".
  • createReturnMsg (:45-59) infers success from data truthiness, so a falsy-but-valid payload becomes FAILURE with err: null, and mediator.js:77 then calls bare reject().
  • more is documented as {cursor} but pagination.py:181-192 returns the whole query-param dict, so an app receives kind_in/descendant_of/max_results while calling with kinds/descendantOf/maxResults.
  • A SandboxedContentViewerHook builds two bundles (buildConfig.js): the plugin's main bundle, and a sandbox_handler bundle built with no Kolibri externals, served from the alt origin and script-loaded into the iframe. Main-side code needing ContentNodeResource cannot ship in the latter.
  • useSandbox returns the client as a shallowRef, re-exposed by SandboxedContentViewer, so a host can bind handlers to it.

The Change

window.kolibri becomes a thin pass-through to the content API, installed only for HTML5 zip content:

window.kolibri = {
  version,                    // string, from the init payload
  contentNode: {
    list(params),             // GET /api/content/contentnode/
    retrieve(id, params),     // GET /api/content/contentnode/<id>/
    tree(id, params),         // GET /api/content/contentnode_tree/<id>/
  },
  navigateTo(nodeId),
}
  • Params and responses should be the endpoint's own, untranslated in either direction. list resolves to { more, results, labels } and the next page is list(more). Queries are not confined to the calling node's channel.
  • self should be accepted anywhere a node id is — the id argument, or a param value such as parent or descendant_of — resolving to the calling node.
  • Rejections should be Error instances carrying the response status: propagated from the endpoint where there is one, 400 where the shim rejects a call before it leaves, 501 where the host binds no handler, 504 on a 30 second deadline. Destroying the shim rejects everything still outstanding.
  • The shim should move onto Html5ZipHandler beside SCORMShim, with its main half in the html5_viewer plugin's main bundle. The halves should talk on the shim's own kolibri namespace, which needs sendMessageAwaitReply on SandboxShim and a MainClient surface for attaching a main half, so neither the plugin nor the host reaches into the mediator.
  • CustomContentRenderer should become a thin wrapper that renders the viewer and binds navigateTo.

How to Get There

  • A custom channel is the only content exercising the API today: TopicsPage renders CustomContentRenderer when plugin_data.enableCustomChannelNav is set and the topic has options.modality === 'CUSTOM_NAVIGATION' (TopicsPage/index.vue:709-718).
  • The channel's root topic ships an HTML5 zip that calls window.kolibri from inside the sandbox iframe.

Acceptance Criteria

  • window.kolibri exposes version, contentNode.list, contentNode.retrieve, contentNode.tree and navigateTo, and nothing else.
  • Params and responses pass through untranslated in both directions, and list(more) returns the next page.
  • self resolves to the calling node both as an id argument and as a param value such as parent or descendant_of.
  • Failed calls reject with an Error carrying status: propagated from the endpoint, 400 for a call rejected before it leaves, 501 where the host binds no handler, 504 after a 30 second deadline. Destroying the shim rejects outstanding calls.
  • window.kolibri is installed for HTML5 zip content only — not for H5P or Bloom.
  • Requests go through SandboxShim.sendMessageAwaitReply and a MainClient attachment surface; neither CustomContentRenderer nor the html5_viewer main half touches a mediator directly.
  • getRandomNodes, getChannelFilterOptions, themeRenderer, updateContext and getContext are gone, together with fetchRandomCollection/_v2, fetchFilterOptions/_v2, the backend random and channel filter_options actions, validateChannelTheme, createReturnMsg, and the kolibri-only events in base.js.
  • Unit tests cover self substitution, status mapping, deadline expiry and teardown rejection.

Testing

  • pnpm test-jest --testPathPatterns kolibri-sandbox, plus the html5_viewer and learn plugin suites.
  • pytest kolibri/core/content/test/ for the endpoint removals.
  • Manually: an H5P and a Bloom resource still load with the shim no longer installed for them.

References

AI usage

Drafted with Claude Code. Every claim about current behaviour was checked against the code rather than taken on trust — line references verified, and the orphaned call chains (random, filter_options, validateChannelTheme) confirmed by searching for callers rather than assumed. The API shape came out of a design conversation and the decisions are mine; Claude drafted the write-up, which I trimmed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DEV: backendPython, databases, networking, filesystem...DEV: frontendDEV: renderersHTML5 apps, videos, exercises, etc.

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions