Skip to content
wyxosPublic

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

2 watching

Forks

Repository files navigation

SHIFT PHP for Laravel

Latest Version on Packagist MIT Licensed

wyxos/shift-php adds issue reporting and task follow-up inside a Laravel application. It ships the /shift dashboard, an optional in-app report widget, task and thread routes, external collaborator lookup, browser-based installation, and cleaned backend error reporting.

A Laravel app user reports an issue from the page where it happened, the package sends the page URL, route, environment, and user details to the portal, and the developer follows up on the resulting task.

Installation

composer require wyxos/shift-php
php artisan install:shift

The default installer uses browser verification:

  • Reads APP_ENV and APP_URL from the Laravel app.
  • Creates an install session in the portal.
  • Prints a verification URL and short code for browser approval.
  • Waits for approval through Reverb when available, with polling fallback.
  • Lets you choose a project or create one when the account has no available option.
  • Writes SHIFT_TOKEN and SHIFT_PROJECT to the app .env.
  • Registers the current app environment and URL.
  • Uses the package resolver automatically. It exposes application users in local only and asks for an application resolver in every other environment.
  • Publishes config and frontend assets.

If SHIFT_TOKEN and SHIFT_PROJECT already exist, the installer keeps those values and skips browser verification.

For manual token setup, run:

php artisan install:shift --manual

Configuration

Typical .env values:

SHIFT_URL=https://shift.wyxos.com
SHIFT_TOKEN=your-shift-api-token
SHIFT_PROJECT=your-shift-project-token

SHIFT_ERROR_REPORTING_ENABLED=true
SHIFT_WORKSPACE_ENABLED=true

Hosted portal:

SHIFT_URL=https://shift.wyxos.com

Local or self-hosted portal:

SHIFT_URL=https://shift.test

Local, .test, .local, localhost, and private IP portal URLs are treated as private by the package client, so SSL verification is skipped for package-to-portal requests. The active portal still needs network access to the app URL when it calls the app for collaborator lookup.

Publish the config when you need to customize middleware, widget behavior, or error-reporting settings:

php artisan vendor:publish --tag=shift

The package default collaborator resolver is deliberately environment-aware:

  • In local, it reads the configured Laravel authentication user model and exposes users with an email address.
  • Outside local, it returns a controlled configuration-required response instead of enumerating application users.

To expose eligible users in a non-local environment, add an application resolver and select it in config/shift.php:

<?php

namespace App\Services;

use App\Models\User;
use Wyxos\Shift\Contracts\ResolvesShiftCollaborators;

class ShiftCollaboratorResolver implements ResolvesShiftCollaborators
{
    public function resolve(?string $search = null): iterable
    {
        return User::query()
            ->whereNotNull('email')
            ->when($search, fn ($query, $search) => $query->where('email', 'like', "%{$search}%"))
            ->get()
            ->map(fn (User $user) => [
                'id' => (string) $user->getKey(),
                'name' => $user->name ?: $user->email,
                'email' => $user->email,
            ]);
    }
}
'collaborators' => [
    'resolver' => App\Services\ShiftCollaboratorResolver::class,
],

The custom class may also extend Wyxos\Shift\Collaborators\DefaultCollaboratorResolver and override resolve().

Versions before this default was introduced wrote SHIFT_COLLABORATORS_RESOLVER=App\Services\ShiftCollaboratorResolver and generated that class. A missing legacy class automatically falls back to the package default. If the generated class still exists, it remains an explicit application override; remove that setting and class to adopt the package default, or replace its resolve() method with the production eligibility rules you intend.

Important config keys:

return [
    'token' => env('SHIFT_TOKEN'),
    'project' => env('SHIFT_PROJECT'),
    'url' => env('SHIFT_URL', 'https://shift.wyxos.com'),

    'workspace' => [
        'enabled' => env('SHIFT_WORKSPACE_ENABLED', true),
    ],

    'routes' => [
        'prefix' => 'shift',
        'middleware' => ['web', 'auth'],
    ],

    'widget' => [
        'enabled' => env('SHIFT_WIDGET_ENABLED', true),
        'routes' => [
            'middleware' => ['web'],
        ],
    ],

    'errors' => [
        'enabled' => env('SHIFT_ERROR_REPORTING_ENABLED', true),
        'endpoint' => env('SHIFT_ERROR_REPORTING_ENDPOINT', '/api/errors'),
        'timeout' => env('SHIFT_ERROR_REPORTING_TIMEOUT', 3),
    ],
];

Task Creation From A Laravel App

After installation, the dashboard is available at:

/shift

It uses the configured route middleware, which defaults to web and auth. Authenticated users can create tasks, edit task details, comment in threads, upload attachments, and manage collaborators for the linked project.

Set SHIFT_WORKSPACE_ENABLED=false to make the embedded workspace and its authenticated /shift/api/** routes return 404. Signed notification delivery, project-token collaborator lookup, the widget, and backend error reporting remain independent of this setting.

For a lightweight report form inside the host app, use the widget endpoint:

POST /shift/api/widget/tasks

Example browser submission:

await fetch('/shift/api/widget/tasks', {
    method: 'POST',
    credentials: 'same-origin',
    headers: {
        'Content-Type': 'application/json',
        'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
    },
    body: JSON.stringify({
        kind: 'issue',
        title: 'Invoice export does not download',
        description: 'I clicked Export CSV on invoice INV-1047 and no file downloaded.',
        anonymous: false,
        metadata: {
            page_url: window.location.href,
            page_title: document.title,
            referrer: document.referrer || null,
        },
    }),
});

For the authenticated dashboard and task routes, the package passes these requests through to SHIFT:

  • GET /shift/api/tasks
  • POST /shift/api/tasks
  • GET /shift/api/tasks/{id}
  • PUT /shift/api/tasks/{id}
  • PATCH /shift/api/tasks/{id}/collaborators
  • GET /shift/api/tasks/{taskId}/threads
  • POST /shift/api/tasks/{taskId}/threads

Error Reports

When SHIFT_ERROR_REPORTING_ENABLED=true, the package reports Laravel exceptions and failures inside the SHIFT SDK to the configured portal. SDK reports appear under the consuming app's existing project and environment. Select SDK errors in the portal's App errors page to see them across projects you can access.

The portal controls alert frequency per project. New errors and completed errors that return alert immediately. For an open error, the next occurrence at or after the configured alert cooldown sends another alert, even if events continued throughout the cooldown. Repeated events do not restart it. The default is one hour; all events remain in the error history. This policy applies on the portal without a consumer SDK upgrade.

The /shift workspace captures Vue errors, SDK JavaScript errors and unhandled promise rejections, navigation or asset-loading failures, and unexpected API action failures. SDK PHP exceptions are reported even when a controller catches them to return an error response. Expected validation, authentication and permission failures are excluded from SDK diagnostics.

Browser reports go through the authenticated, CSRF-protected POST /shift/api/errors endpoint. The portal token stays on the server. SDK diagnostics include the installed package version, operation and a correlation ID when available. JavaScript stack frames retain line and column positions. Reports also identify the UI build actually loaded, independently of the PHP package version.

Reporting is best effort and uses the configured timeout (three seconds by default). Delivery failures preserve the original response and write a local diagnostic with the exception class, operation and correlation ID. Local diagnostics omit exception messages and request data. Failed reports are not queued for later delivery.

The workspace suppresses duplicate exceptions and sends at most twenty distinct reports per page load. The browser endpoint accepts twenty requests per minute per user, and PHP SDK reports are limited to twenty attempts per signature per minute when the app's limiter cache is available. These limits use separate SDK counters. The widget does not install global browser error listeners on consuming-app pages.

Capture starts when the workspace entry script runs. A blocked or missing entry script, browser termination, or an unavailable network can prevent delivery. Visual layout problems do not produce exception reports.

Original browser source

Production UI builds keep source maps and exact generated JavaScript in the private .build/shift-ui/<build-id> directory. Published SDK assets contain no source maps or source-map URLs. Keep the private artifact for each distributed build, including older builds that consuming apps may still serve.

SDK releases register and verify the exact build on every configured SHIFT instance before creating or pushing the release tag. Consuming apps keep using their existing SHIFT URL and credentials; they need no source-map configuration.

Configure the release machine once with an ignored .shift-sdk-release.json in the package checkout:

{
  "schema_version": 1,
  "targets": [
    {
      "name": "Production SHIFT",
      "transport": "ssh",
      "host": "shift-production",
      "projectPath": "/srv/shift",
      "php": "php"
    }
  ]
}

Use an existing SSH alias that signs in as the portal's application user. Registration uses private temporary files and the portal's shift:sdk-build:import and shift:sdk-build:verify commands. Deploy these commands to each target before the first automated release. No new upload endpoint or publishing token is required.

The release stops if any target fails registration, returns the wrong build receipt, or cannot verify its stored checksums. Maps already registered on other targets remain available. Fix the target and retry the same artifact without rebuilding, tagging or publishing:

npm run release -- --register-source-maps

Add --dry-run to inspect registration targets without connecting. --source-map-targets /private/path/targets.json or SHIFT_SDK_RELEASE_TARGETS_FILE selects another release-level configuration. Keep target files private and outside published package contents.

For a separately managed SHIFT instance, add it as a release target once. Manual import remains available:

php artisan shift:sdk-build:import /private/path/to/build-id

The portal verifies artifact checksums and stores an immutable copy outside its public directory. When it receives an SDK browser report, it resolves stack positions to the original Vue or TypeScript source and records a short, scrubbed source excerpt. Error Reports and the existing MCP event tool expose these diagnostics under the same task permissions.

Missing, invalid or mismatched artifacts leave the compiled trace available and show an explicit resolution status. SHIFT does not fetch scripts or source maps from URLs in reports. Resolution happens when the event is received; importing a map later does not rewrite earlier events.

The build identifier belongs to the JavaScript executing in the browser. It remains distinct from the installed PHP SDK version and the consuming application's release. Build artifacts also record source revisions and whether those checkouts contained changes; the captured source content identifies the code built from modified checkouts.

The package detects the revision from deployment commit metadata or the app Git checkout. If the checkout has an exact tag for that revision, the tag is sent as the release.

Data Sent

Task and dashboard requests include:

  • Project token.
  • Authenticated app user name, email, and ID when available.
  • App environment from APP_ENV.
  • App URL from APP_URL.
  • User-entered task fields such as title, description, status, priority, comments, attachments, and collaborator choices.

Widget submissions include:

  • Report kind, title, and description.
  • Anonymous flag.
  • Page details supplied by the widget, such as current page URL, page title, and referrer.
  • Authenticated app user details, or guest name/email when guest details are submitted and anonymous mode is not used.
  • Linked app name, environment, and URL.

SDK diagnostics include cleaned error messages, stack frames, the workspace path, operation, SDK version, app release, environment, and authenticated user details when available. They omit form bodies, request queries, source-code excerpts, and browser DOM contents. Normal consuming-app Laravel reports retain the backend request context listed below.

Backend error reports include:

  • Project token.
  • App environment, app URL, release, and revision.
  • Exception class and cleaned message.
  • Stack frames and limited source code for in-app files.
  • Request method, URL, path, referrer, IP, user agent, query, and body after scrubbing.
  • PHP and Laravel versions.
  • Authenticated user details when available.

The error cleaner removes common sensitive fields such as password, token, authorization, and cookie values. You should still avoid adding secrets or sensitive customer data to task descriptions, widget details, or exception messages.

Local Testing Path

php artisan config:clear
php artisan shift:test

shift:test creates a QA task in the configured project. Use a local or self-hosted portal unless you intentionally want that task in a hosted project.

Troubleshooting

SHIFT configuration missing

Run the installer or set both SHIFT_TOKEN and SHIFT_PROJECT, then clear cached config:

php artisan install:shift
php artisan config:clear

Browser verification cannot run

The default installer needs an interactive terminal. In CI or non-interactive setup, obtain an API token and project token first, then run:

php artisan install:shift --manual

Local or private URL warning

The installer warns when APP_URL is local or private. Task submission still works when the package can reach the portal, but external collaborator lookup requires that portal to reach the app's /shift/api/collaborators/external endpoint.

/shift loads old assets or a blank UI

In a consuming Laravel app, publish the current package assets:

php artisan shift:publish --group=public

Widget returns 401 or 403

Check whether the portal project allows widget reports and whether guest submissions are allowed. If guest submissions are disabled, the app user must be authenticated through the configured widget guard.

Task creation returns 422

Check the validation response from the portal. Common causes are missing title/description, an invalid project token, or project configuration that does not allow the requested report path.

Error reports do not appear

Check that error reporting is enabled and fully configured:

php artisan config:clear
php artisan tinker
>>> config('shift.errors.enabled')
>>> config('shift.url')
>>> filled(config('shift.token'))
>>> filled(config('shift.project'))

The reporter is intentionally silent on network failures and failed portal responses so it does not interfere with application error handling.

License

MIT Wyxos. See LICENSE.md for details.

Widget request limits

The SDK limits its widget config, session-user, login and task-submission routes to a shared 30 requests per minute per authenticated user or guest IP by default. Its shift-widget: counter prefix keeps this budget separate from unrelated application throttles. Laravel still uses the consuming application's configured limiter cache store.

Applications only need to supply their own authentication or authorization middleware in shift.widget.routes.middleware. The SDK applies its default even when an older published config lacks the new setting. Remove a redundant app-owned throttle:30,1 entry to use the package default. Existing explicit Laravel throttle middleware (including its fully qualified class or subclasses) remains an application override and is not stacked with another package limiter.

Set SHIFT_WIDGET_THROTTLE="60,1,shift-widget:" to customize the attempts, decay minutes and counter prefix. Published configuration can set shift.widget.routes.throttle directly; explicit false disables the package default. Existing config files are preserved during dependency updates, so add the environment-backed setting if needed. Rebuild the application's route and configuration caches after changes. Signed callbacks, collaborator lookup and workspace routes retain their separate authorization and are outside this widget budget.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages