{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://openspatials.com/developers/spec/0.5/directory.schema.json",
  "title": "OpenSpatials directory list, version 0.2-draft",
  "description": "Spec: src/spec/core/APP-PROFILE-AND-DIRECTORY.md. A static list of entries. Each world's own profile decides support; the entry's openspatials is only a hint. profile may be left out: tools then look in the play page's own folder (./openspatials-world.json) and at <origin of play>/.well-known/openspatials-site.json last. Limit 256 KiB, checked on the bytes before parsing. Unknown fields are allowed and ignored. Its noCode and gameUrl definitions are shared by the registry's catalog and listing schemas.",
  "type": "object",
  "allOf": [
    {
      "$ref": "#/$defs/noCode"
    }
  ],
  "required": [
    "openspatials",
    "games"
  ],
  "properties": {
    "openspatials": {
      "description": "Spec version of the list format.",
      "x-openspatials-reason": "unknown-version",
      "enum": [
        "0.1-draft",
        "0.2-draft"
      ]
    },
    "games": {
      "type": "array",
      "maxItems": 500,
      "items": {
        "$ref": "#/$defs/entry"
      }
    }
  },
  "$defs": {
    "entry": {
      "type": "object",
      "required": [
        "name",
        "play"
      ],
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 64,
          "pattern": "\\S"
        },
        "profile": {
          "description": "Address of the game's profile, at the origin or next to the page.",
          "$ref": "#/$defs/gameUrl"
        },
        "play": {
          "description": "The crossing entry point.",
          "$ref": "#/$defs/gameUrl"
        },
        "engine": {
          "type": "string",
          "maxLength": 32,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9 ._+-]*$"
        },
        "openspatials": {
          "description": "Optional hint only. Never used to decide support.",
          "type": "string",
          "maxLength": 32
        },
        "thumbnail": {
          "description": "Optional picture. An empty string means none.",
          "anyOf": [
            {
              "const": ""
            },
            {
              "$ref": "#/$defs/gameUrl"
            }
          ]
        },
        "added": {
          "type": "string",
          "format": "date-time"
        }
      }
    },
    "gameUrl": {
      "description": "An absolute https address, or http on a loopback host for local testing. A relative address is invalid-file; an absolute address with another scheme, a private network host or user info is unsafe-file.",
      "type": "string",
      "maxLength": 2048,
      "pattern": "^[A-Za-z][A-Za-z0-9+.-]*:\\S*$",
      "if": {
        "type": "string",
        "pattern": "^[A-Za-z][A-Za-z0-9+.-]*:"
      },
      "then": {
        "x-openspatials-reason": "unsafe-file",
        "anyOf": [
          {
            "$ref": "#/$defs/httpsUrl"
          },
          {
            "$ref": "#/$defs/loopbackHttpUrl"
          }
        ]
      }
    },
    "httpsUrl": {
      "type": "string",
      "pattern": "^https://(?!(?:10|0)\\.\\d)(?!192\\.168\\.)(?!169\\.254\\.)(?!172\\.(?:1[6-9]|2\\d|3[01])\\.)(?!\\[[fF][cdeCDE])[^\\s/?#@\\\\]+(?:[/?#]\\S*)?$"
    },
    "loopbackHttpUrl": {
      "type": "string",
      "pattern": "^http://(?:127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|localhost|\\[::1\\])(?::\\d{1,5})?(?:[/?#]\\S*)?$"
    },
    "noCode": {
      "description": "04 section 1, closed list, at any depth: a key named script, scripts, eval or javascript; a key that is 'on' followed by a capital letter; a string value starting with javascript:, vbscript: or data:text/html (any case, after leading white space).",
      "x-openspatials-reason": "unsafe-file",
      "propertyNames": {
        "not": {
          "pattern": "^(?:script|scripts|eval|javascript|on[A-Z].*)$"
        }
      },
      "additionalProperties": {
        "$ref": "#/$defs/noCode"
      },
      "items": {
        "$ref": "#/$defs/noCode"
      },
      "not": {
        "type": "string",
        "pattern": "^\\s*(?:[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]:|[Vv][Bb][Ss][Cc][Rr][Ii][Pp][Tt]:|[Dd][Aa][Tt][Aa]:[Tt][Ee][Xx][Tt]/[Hh][Tt][Mm][Ll])"
      }
    }
  }
}
