Web extensions API services

Using injected API services for developing web extensions.

The web extension has access to services that are injected into the page to facilitate communication with Skedulo’s API.

These services have their appropriate type definitions (for Typescript) defined in the src/Services/Services.ts boilerplate file.

You can use this file to get direct references to everything injected in the web extension.

This page describes the variables and services that are injected in to your web extensions and how to use them.

Variables

Skedulo updates the injected services in your environment using environment variables.

As we update the injected services we will also periodically update the boilerplate files.

The following is a list of all variables that are injected into web extensions, and an example of the result.

Name Type Description Example
context string or string[] The reference UID of the context under which the page is loaded referenceUID: "000195c2-1cad-409e-aa22-74181732b2c7"
params [paramName: string]: any Any parameters that have been passed to the web component foo: "bar"
profile Profile Contains information about the user running the web extension roles: ["administrator"]
tenantId: "sk_b30d8a5952d94b47a000d254f2c9256d"
userId:"000195c2-1cad-409e-aa22-74181732b2c7"
username: "mwheeler@skedulo.com"
credentials Credentials Credentials of the current user and details of API server apiAccessToken: "token"
apiServer: "https://api.skedulo.com"
vendor: {
type: "skedulo",
url: 'https://api.skedulo.com',
token: null
}
navigation Navigation desc ex

These variables can be used in your Web Extension by importing them and then referencing them (see below example)

import * as React from 'react'
import { Services, profile } from './Services/Services' //import profile variable

interface AppState {
  profile: any
}

export class App extends React.PureComponent<{}, AppState> {

  constructor(props: {}) {
    super(props)
    this.state = {
      profile: profile //add it to the state
    }
  }

  render() {
    return (
      <div className="App">
       Current User Id: { this.state.profile.userId } //show profile.userId
      </div>
    )
  }
}

Services

The below services are exposed to make it easier to call Skedulo’s APIs.

There are some example implementations of these services (for Typescript) defined in the src/Services/DataServices.ts boilerplate file.

You can use these to retrieve and manipulate data and metadata within the Skedulo platform.

Name Description
Services.graphQL This service allows you to send queries to Skedulo’s GraphQL API. This is the primary method of retrieving, consuming and updating data from a web extension. Refer to src/Services/DataServices.ts for usage examples. This also exposes a method for retrieving GraphQL introspection data for any given model if required.
Services.metadata This service allows you to fetch data from our metadata API.
Services.errorClasses This is a map that lists all possible errors that can be thrown while interacting with the available services. For example, a GraphQLExecutionError can occur if there is an error in the defined query. You can use these classes (if needed) to pattern-match (instanceof checks) in Promise catch blocks for easy and contextual error handling.

These services can be used in your Web Extension by importing them and then referencing them (see below example)

import { Services } from './Services' //import the service

export class DataServices {

  constructor(private services: Services) { }

  fetchJobs() {
    return this.services.graphQL //perform a fetch using it
      .fetch<{ jobs: Job[] }>({
        query: JobsQuery
      })
      .then(({ jobs }) => jobs)
  }
}

//example job interface
export interface Job { 
  UID: string
  Name: string
  Description: string
}

//example job fetch query
const JobsQuery = `
{
  jobs {
    edges {
      node {
        UID
        Name
        Description
      }
    }
  }
}
`

Example Services.ts file:

interface GraphQLRequest {
  query: string
  variables?: Record<string, any>
  operationName?: string
  context?: Record<string, any>
  extensions?: Record<string, any>
}

export interface GraphQLMutationResult {
  data: null | { schema: { [operationName: string]: string } }
  errors: null | {
    message: string
    path?: string[]
    locations?: { line: number, column: number }[]
  }[]
}

// tslint:disable:no-misused-new
interface GraphQLError {
  getErrors: () => string[]
  new(): GraphQLError
}

type Model = string

interface IntrospectionField {
  name: string
  type: {
    name: null | 'Instant' | 'Boolean' | 'BigDecimal' | 'String' | Model
    kind: 'SCALAR' | 'NON_NULL' | 'OBJECT' | 'LIST'
    ofType: null | IntrospectionField['type']
  }
}

interface IntrospectionModelType {
  __type: {
    name: string
    fields: IntrospectionField[]
  }
}

export interface Vocabulary {
  [schema: string]: {
    [field: string]: {
      value: string,
      label: string
    }[]
  }
}

export interface Services {
  graphQL: {
    fetch<T>(operation: GraphQLRequest, endpoint?: string): Promise<T>
    mutate(operation: GraphQLRequest, endpoint?: string): Promise<GraphQLMutationResult>
    fetchMetadataFor(model: string): Promise<IntrospectionModelType>
  },
  metadata: {
    fetchVocabulary(): Promise<Vocabulary>
  },
  errorClasses: {
    GraphQLNetworkError: GraphQLError,
    GraphQLExecutionError: GraphQLError
  },
}

export interface Profile {
  tenantId: string
  userId: string
  username: string
  roles: string[]
}

export interface Credentials {
  apiServer: string
  apiAccessToken: string

  vendor: { type: 'skedulo', url: string, token: null } | { type: 'salesforce', url: string, token: string }
}

export interface Navigation {
  registerRouteHandler: (routeHandler: (routeState: {
    routes: string | string[],
    params: { [paramName: string]: any }
  }) => void) => void
  setParentRoute: (route: string) => void
}

declare const skedInjected: {
  Services: Services,
  context?: string | string[],
  params: { [paramName: string]: any }
  profile: Profile,
  credentials: Credentials
  navigation: Navigation
}

export const Services = skedInjected.Services
export const context = skedInjected.context
export const params = skedInjected.params
export const profile = skedInjected.profile
export const credentials = skedInjected.credentials
export const navigation = skedInjected.navigation

Nested types

RealtimeJobChangeSet = Pick<Job, 'UID' | 'JobStatus' | 'Urgency' | 'Locked' | 'Start' | 'Duration' | 'End' | 'Address' | 'GeoLatitude' | 'GeoLongitude'>
RealtimeJobAllocationChangeSet = Pick<IJobAllocation, 'UID' | 'Status' | 'JobId' | 'ResourceId'>
ExtendedRealtimeJobAllocation = RealtimeJobAllocation & { JobId: string, ResourceId: string }

interface IRealtimeChannel {
  getJobStream: () => Observable<RealtimeJobChangeSet[]>,
  getJobAllocationStream: () => Observable<RealtimeJobAllocationChangeSet[]>,
  getUpdatedJobStream: () => Observable<RealtimeJobChangeSet[]>,
  getCreatedJobStream: () => Observable<RealtimeJobChangeSet[]>,
  getUpdatedJobAllocationStream: () => Observable<RealtimeJobAllocationChangeSet[]>,
  getCreatedJobAllocationStream: () => Observable<RealtimeJobAllocationChangeSet[]>
}

This approach allows you to provide credentials and context that is used to instantiate services for the page and allows you to access the Skedulo GraphQL schema.